diff --git a/app/soapbox/components/primary_navigation.js b/app/soapbox/components/primary_navigation.js
new file mode 100644
index 000000000..a1c3b5362
--- /dev/null
+++ b/app/soapbox/components/primary_navigation.js
@@ -0,0 +1,93 @@
+'use strict';
+
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage } from 'react-intl';
+import { NavLink, withRouter } from 'react-router-dom';
+import Icon from 'soapbox/components/icon';
+import IconWithCounter from 'soapbox/components/icon_with_counter';
+import { getFeatures } from 'soapbox/utils/features';
+import { getSoapboxConfig } from 'soapbox/actions/soapbox';
+import { isStaff } from 'soapbox/utils/accounts';
+
+const mapStateToProps = state => {
+ const me = state.get('me');
+ const reportsCount = state.getIn(['admin', 'openReports']).count();
+ const approvalCount = state.getIn(['admin', 'awaitingApproval']).count();
+ const instance = state.get('instance');
+ const features = getFeatures(instance);
+
+ return {
+ account: state.getIn(['accounts', me]),
+ logo: getSoapboxConfig(state).get('logo'),
+ notificationCount: state.getIn(['notifications', 'unread']),
+ chatsCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
+ dashboardCount: reportsCount + approvalCount,
+ features: getFeatures(instance),
+ siteTitle: state.getIn(['instance', 'title']),
+ federating: features.federating,
+ };
+};
+
+export default @withRouter @connect(mapStateToProps)
+class PrimaryNavigation extends React.PureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ logo: PropTypes.string,
+ account: ImmutablePropTypes.map,
+ dashboardCount: PropTypes.number,
+ notificationCount: PropTypes.number,
+ chatsCount: PropTypes.number,
+ features: PropTypes.object.isRequired,
+ siteTitle: PropTypes.string,
+ federating: PropTypes.bool,
+ };
+
+ render() {
+ const { account, features, notificationCount, chatsCount, dashboardCount } = this.props;
+
+ return (
+
+
+
+
+
+
+
+ {account &&
+
+
+ }
+
+ {(features.chats && account) &&
+
+
+ }
+
+ {(account && isStaff(account)) &&
+
+
+ }
+
+ {/*
+
+ {federating ? siteTitle : }
+ */}
+
+ {/* federating &&
+
+
+ */}
+
+
+ );
+ }
+
+}
diff --git a/app/soapbox/components/sub_navigation.js b/app/soapbox/components/sub_navigation.js
new file mode 100644
index 000000000..28a92e5ff
--- /dev/null
+++ b/app/soapbox/components/sub_navigation.js
@@ -0,0 +1,79 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
+import Icon from 'soapbox/components/icon';
+import { withRouter } from 'react-router-dom';
+import { matchPath } from 'react-router-dom';
+
+const routes = [
+ ['status', { path: '/@:username/posts/:statusId' }],
+ ['account', { path: '/@:username' }],
+];
+
+const findRouteType = path => {
+ const route = routes.find(v => matchPath(path, v[1])) || [];
+ return route[0];
+};
+
+const messages = defineMessages({
+ status: { id: 'sub_navigation.status', defaultMessage: 'Post' },
+ account: { id: 'sub_navigation.account', defaultMessage: 'Profile' },
+});
+
+export default @withRouter
+@injectIntl
+class SubNavigation extends React.PureComponent {
+
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+ }
+
+ static contextTypes = {
+ router: PropTypes.object.isRequired,
+ }
+
+ handleBackClick = () => {
+ if (window.history && window.history.length === 1) {
+ this.context.router.history.push('/');
+ } else {
+ this.context.router.history.goBack();
+ }
+ }
+
+ handleBackKeyUp = (e) => {
+ if (e.key === 'Enter') {
+ this.handleClick();
+ }
+ }
+
+ getMessage = () => {
+ const path = this.context.router.history.location.pathname;
+ const type = findRouteType(path);
+
+ return messages[type] || null;
+ }
+
+ render() {
+ const { intl } = this.props;
+ const message = this.getMessage();
+
+ if (!message) return null;
+
+ return (
+
+
+
+ {intl.formatMessage(message)}
+
+
+ );
+ }
+
+}
diff --git a/app/soapbox/components/thumb_navigation.js b/app/soapbox/components/thumb_navigation.js
new file mode 100644
index 000000000..e8b1f3bb9
--- /dev/null
+++ b/app/soapbox/components/thumb_navigation.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import { NavLink, withRouter } from 'react-router-dom';
+import { FormattedMessage } from 'react-intl';
+import Icon from 'soapbox/components/icon';
+
+export default
+@withRouter
+class ThumbNavigation extends React.PureComponent {
+
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js
index af09692bb..a897d1602 100644
--- a/app/soapbox/features/ui/index.js
+++ b/app/soapbox/features/ui/index.js
@@ -45,6 +45,8 @@ import ProfileHoverCard from 'soapbox/components/profile_hover_card';
import { getAccessToken } from 'soapbox/utils/auth';
import { getFeatures } from 'soapbox/utils/features';
import { fetchCustomEmojis } from 'soapbox/actions/custom_emojis';
+import ThumbNavigation from 'soapbox/components/thumb_navigation';
+import SubNavigation from 'soapbox/components/sub_navigation';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import {
@@ -651,6 +653,7 @@ class UI extends React.PureComponent {
+
{children}
@@ -668,6 +671,7 @@ class UI extends React.PureComponent {
)}
+
);
diff --git a/app/soapbox/pages/home_page.js b/app/soapbox/pages/home_page.js
index b39403c2f..bebfe40c2 100644
--- a/app/soapbox/pages/home_page.js
+++ b/app/soapbox/pages/home_page.js
@@ -5,7 +5,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import BundleContainer from '../features/ui/containers/bundle_container';
import ComposeFormContainer from '../features/compose/containers/compose_form_container';
import Avatar from '../components/avatar';
-import UserPanel from 'soapbox/features/ui/components/user_panel';
+// import UserPanel from 'soapbox/features/ui/components/user_panel';
+import PrimaryNavigation from 'soapbox/components/primary_navigation';
import WhoToFollowPanel from 'soapbox/features/ui/components/who_to_follow_panel';
import TrendsPanel from 'soapbox/features/ui/components/trends_panel';
import PromoPanel from 'soapbox/features/ui/components/promo_panel';
@@ -57,13 +58,7 @@ class HomePage extends ImmutablePureComponent {
-
- {showFundingPanel &&
}
- {showCryptoDonatePanel && (
-
- {Component => }
-
- )}
+
@@ -86,10 +81,17 @@ class HomePage extends ImmutablePureComponent {
+ {/*
*/}
{showTrendsPanel &&
}
{showWhoToFollowPanel &&
}
{me ?
:
}
+ {showFundingPanel &&
}
+ {showCryptoDonatePanel && (
+
+ {Component => }
+
+ )}
diff --git a/app/styles/application.scss b/app/styles/application.scss
index 9b9bbff36..844e88a4c 100644
--- a/app/styles/application.scss
+++ b/app/styles/application.scss
@@ -28,6 +28,7 @@
@import 'dyslexic';
@import 'demetricator';
@import 'chats';
+@import 'navigation';
// COMPONENTS
@import 'components/buttons';
diff --git a/app/styles/navigation.scss b/app/styles/navigation.scss
new file mode 100644
index 000000000..f02d14a44
--- /dev/null
+++ b/app/styles/navigation.scss
@@ -0,0 +1,110 @@
+.primary-navigation__wrapper {
+ .primary-navigation {
+ flex-direction: column;
+
+ > button,
+ > .btn {
+ justify-content: flex-start;
+ font-size: 19px;
+ font-weight: bold;
+
+ > i.fa,
+ > .icon-with-counter {
+ width: 25px;
+ margin-right: 5px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ > .svg-icon {
+ width: 25px;
+ margin-right: 5px;
+ }
+
+ &::before {
+ left: 0;
+ border-radius: 999px;
+ }
+ }
+
+ &__icon {
+ margin-right: 5px;
+ }
+ }
+}
+
+.thumb-navigation {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 50px;
+ display: flex;
+ background: var(--foreground-color);
+ justify-content: space-around;
+ border-top: 1px solid hsla(var(--primary-text-color_hsl), 0.2);
+ border-radius: 0;
+ z-index: 999;
+
+ &__link {
+ padding: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--primary-text-color);
+ text-decoration: none;
+ font-size: 20px;
+
+ span {
+ display: none;
+ }
+ }
+
+ @media (min-width: 895px) {
+ display: none;
+ }
+}
+
+.sub-navigation {
+ position: sticky;
+ top: 50px;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 30px;
+ padding: 5px;
+ display: flex;
+ color: var(--primary-text-color--faint);
+ background: var(--foreground-color);
+ justify-content: space-around;
+ border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2);
+ border-radius: 0;
+ z-index: 999;
+
+ &__back {
+ margin-right: auto;
+ background: transparent;
+ border: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--primary-text-color--faint);
+
+ .svg-icon {
+ margin-right: 7px;
+ }
+ }
+
+ &__message {
+ position: absolute;
+ align-self: center;
+ justify-self: center;
+ font-weight: bold;
+ }
+
+ @media screen and (min-width: 895px) {
+ display: none;
+ }
+}
diff --git a/app/styles/ui.scss b/app/styles/ui.scss
index 8a111f31e..760122aae 100644
--- a/app/styles/ui.scss
+++ b/app/styles/ui.scss
@@ -358,7 +358,7 @@
z-index: 1000;
display: none;
position: fixed;
- bottom: 14px;
+ bottom: 64px;
right: 14px;
width: 61px;
height: 61px;