Start navigation changes
This commit is contained in:
parent
61043357b5
commit
e0f97f4e99
8 changed files with 342 additions and 9 deletions
93
app/soapbox/components/primary_navigation.js
Normal file
93
app/soapbox/components/primary_navigation.js
Normal file
|
@ -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 (
|
||||||
|
<div className='column-header__wrapper primary-navigation__wrapper'>
|
||||||
|
<h1 className='column-header primary-navigation'>
|
||||||
|
<NavLink to='/' exact className='btn grouped'>
|
||||||
|
<Icon id='home' className='primary-navigation__icon' />
|
||||||
|
<FormattedMessage id='tabs_bar.home' defaultMessage='Home' />
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
{account && <NavLink key='notifications' className='btn grouped' to='/notifications' data-preview-title-id='column.notifications'>
|
||||||
|
<IconWithCounter icon='bell' count={notificationCount} />
|
||||||
|
<FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' />
|
||||||
|
</NavLink>}
|
||||||
|
|
||||||
|
{(features.chats && account) && <NavLink key='chats' className='btn grouped' to='/chats' data-preview-title-id='column.chats'>
|
||||||
|
<IconWithCounter icon='comment' count={chatsCount} />
|
||||||
|
<FormattedMessage id='tabs_bar.chats' defaultMessage='Chats' />
|
||||||
|
</NavLink>}
|
||||||
|
|
||||||
|
{(account && isStaff(account)) && <NavLink key='dashboard' className='btn grouped' to='/admin' data-preview-title-id='tabs_bar.dashboard'>
|
||||||
|
<IconWithCounter icon='tachometer' count={dashboardCount} />
|
||||||
|
<FormattedMessage id='tabs_bar.dashboard' defaultMessage='Dashboard' />
|
||||||
|
</NavLink>}
|
||||||
|
|
||||||
|
{/* <NavLink to='/timeline/local' className='btn grouped'>
|
||||||
|
<Icon id={federating ? 'users' : 'globe-w'} fixedWidth className='column-header__icon' />
|
||||||
|
{federating ? siteTitle : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
|
||||||
|
</NavLink> */}
|
||||||
|
|
||||||
|
{/* federating && <NavLink to='/timeline/fediverse' className='btn grouped'>
|
||||||
|
<Icon id='fediverse' fixedWidth className='column-header__icon' />
|
||||||
|
<FormattedMessage id='tabs_bar.fediverse' defaultMessage='Fediverse' />
|
||||||
|
</NavLink> */}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
79
app/soapbox/components/sub_navigation.js
Normal file
79
app/soapbox/components/sub_navigation.js
Normal file
|
@ -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 (
|
||||||
|
<div className='sub-navigation'>
|
||||||
|
<button
|
||||||
|
className='sub-navigation__back'
|
||||||
|
onClick={this.handleBackClick}
|
||||||
|
onKeyUp={this.handleBackKeyUp}
|
||||||
|
>
|
||||||
|
<Icon id='chevron-left' />
|
||||||
|
<FormattedMessage id='sub_navigation.back' defaultMessage='Back' />
|
||||||
|
</button>
|
||||||
|
<div className='sub-navigation__message'>
|
||||||
|
{intl.formatMessage(message)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
app/soapbox/components/thumb_navigation.js
Normal file
44
app/soapbox/components/thumb_navigation.js
Normal file
|
@ -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 (
|
||||||
|
<div className='thumb-navigation'>
|
||||||
|
<NavLink to='/' exact className='thumb-navigation__link'>
|
||||||
|
<Icon id='home' />
|
||||||
|
<span>
|
||||||
|
<FormattedMessage id='navigation.home' defaultMessage='Home' />
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink to='/search' className='thumb-navigation__link'>
|
||||||
|
<Icon id='search' />
|
||||||
|
<span>
|
||||||
|
<FormattedMessage id='navigation.search' defaultMessage='Search' />
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink to='/notifications' className='thumb-navigation__link'>
|
||||||
|
<Icon id='bell' />
|
||||||
|
<span>
|
||||||
|
<FormattedMessage id='navigation.notifications' defaultMessage='Notifications' />
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink to='/chats' className='thumb-navigation__link'>
|
||||||
|
<Icon id='comment' />
|
||||||
|
<span>
|
||||||
|
<FormattedMessage id='navigation.chats' defaultMessage='Chats' />
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -45,6 +45,8 @@ import ProfileHoverCard from 'soapbox/components/profile_hover_card';
|
||||||
import { getAccessToken } from 'soapbox/utils/auth';
|
import { getAccessToken } from 'soapbox/utils/auth';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
import { fetchCustomEmojis } from 'soapbox/actions/custom_emojis';
|
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 { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -651,6 +653,7 @@ class UI extends React.PureComponent {
|
||||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||||
<div className={classnames} ref={this.setRef} style={style}>
|
<div className={classnames} ref={this.setRef} style={style}>
|
||||||
<TabsBar />
|
<TabsBar />
|
||||||
|
<SubNavigation />
|
||||||
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange} soapbox={soapbox}>
|
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange} soapbox={soapbox}>
|
||||||
{children}
|
{children}
|
||||||
</SwitchingColumnsArea>
|
</SwitchingColumnsArea>
|
||||||
|
@ -668,6 +671,7 @@ class UI extends React.PureComponent {
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
)}
|
)}
|
||||||
<ProfileHoverCard />
|
<ProfileHoverCard />
|
||||||
|
<ThumbNavigation />
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import BundleContainer from '../features/ui/containers/bundle_container';
|
import BundleContainer from '../features/ui/containers/bundle_container';
|
||||||
import ComposeFormContainer from '../features/compose/containers/compose_form_container';
|
import ComposeFormContainer from '../features/compose/containers/compose_form_container';
|
||||||
import Avatar from '../components/avatar';
|
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 WhoToFollowPanel from 'soapbox/features/ui/components/who_to_follow_panel';
|
||||||
import TrendsPanel from 'soapbox/features/ui/components/trends_panel';
|
import TrendsPanel from 'soapbox/features/ui/components/trends_panel';
|
||||||
import PromoPanel from 'soapbox/features/ui/components/promo_panel';
|
import PromoPanel from 'soapbox/features/ui/components/promo_panel';
|
||||||
|
@ -57,13 +58,7 @@ class HomePage extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
|
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
|
||||||
<div className='columns-area__panels__pane__inner'>
|
<div className='columns-area__panels__pane__inner'>
|
||||||
<UserPanel accountId={me} key='user-panel' />
|
<PrimaryNavigation />
|
||||||
{showFundingPanel && <FundingPanel key='funding-panel' />}
|
|
||||||
{showCryptoDonatePanel && (
|
|
||||||
<BundleContainer fetchComponent={CryptoDonatePanel}>
|
|
||||||
{Component => <Component limit={cryptoLimit} key='crypto-panel' />}
|
|
||||||
</BundleContainer>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -86,10 +81,17 @@ class HomePage extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='columns-area__panels__pane columns-area__panels__pane--right'>
|
<div className='columns-area__panels__pane columns-area__panels__pane--right'>
|
||||||
<div className='columns-area__panels__pane__inner'>
|
<div className='columns-area__panels__pane__inner'>
|
||||||
|
{/* <UserPanel accountId={me} key='user-panel' /> */}
|
||||||
{showTrendsPanel && <TrendsPanel limit={3} key='trends-panel' />}
|
{showTrendsPanel && <TrendsPanel limit={3} key='trends-panel' />}
|
||||||
{showWhoToFollowPanel && <WhoToFollowPanel limit={5} key='wtf-panel' />}
|
{showWhoToFollowPanel && <WhoToFollowPanel limit={5} key='wtf-panel' />}
|
||||||
{me ? <FeaturesPanel key='features-panel' /> : <SignUpPanel key='sign-up-panel' />}
|
{me ? <FeaturesPanel key='features-panel' /> : <SignUpPanel key='sign-up-panel' />}
|
||||||
<PromoPanel key='promo-panel' />
|
<PromoPanel key='promo-panel' />
|
||||||
|
{showFundingPanel && <FundingPanel key='funding-panel' />}
|
||||||
|
{showCryptoDonatePanel && (
|
||||||
|
<BundleContainer fetchComponent={CryptoDonatePanel}>
|
||||||
|
{Component => <Component limit={cryptoLimit} key='crypto-panel' />}
|
||||||
|
</BundleContainer>
|
||||||
|
)}
|
||||||
<LinkFooter key='link-footer' />
|
<LinkFooter key='link-footer' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
@import 'dyslexic';
|
@import 'dyslexic';
|
||||||
@import 'demetricator';
|
@import 'demetricator';
|
||||||
@import 'chats';
|
@import 'chats';
|
||||||
|
@import 'navigation';
|
||||||
|
|
||||||
// COMPONENTS
|
// COMPONENTS
|
||||||
@import 'components/buttons';
|
@import 'components/buttons';
|
||||||
|
|
110
app/styles/navigation.scss
Normal file
110
app/styles/navigation.scss
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -358,7 +358,7 @@
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 14px;
|
bottom: 64px;
|
||||||
right: 14px;
|
right: 14px;
|
||||||
width: 61px;
|
width: 61px;
|
||||||
height: 61px;
|
height: 61px;
|
||||||
|
|
Loading…
Reference in a new issue