Make navigation panel retractable on mobile and tablet

This commit is contained in:
Darius Kazemi 2022-12-30 18:38:35 -08:00
parent 6231d4b7ee
commit 2d6a98a43b
8 changed files with 303 additions and 95 deletions

View File

@ -1,6 +1,7 @@
import React from 'react';
import ComposeFormContainer from './containers/compose_form_container';
import NavigationContainer from './containers/navigation_container';
import ColumnHeader from 'mastodon/components/column_header';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
@ -37,25 +38,6 @@ const mapStateToProps = (state, ownProps) => ({
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : false,
});
let instanceMascot;
if (mascot) {
instanceMascot = <img alt='' draggable='false' src={mascot} />;
} else {
instanceMascot = <svg id='hometownlogo' width="2400" height="460" viewBox="0 0 2400 460" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<g>
<g>
<path d="m326.20431,287.85649l-302.73044,0c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436s8.81738,19.10436 19.10436,19.10436l302.73044,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436s-8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m326.20431,351.04783l-302.73044,0c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436s8.81738,19.10436 19.10436,19.10436l302.73044,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436s-8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m326.20431,415.70867l-302.73044,0c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436s8.81738,19.10436 19.10436,19.10436l302.73044,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436s-8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m456.99565,287.85649c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436l0,129.32173c0,10.28698 8.81738,19.10436 19.10436,19.10436s19.10436,-8.81738 19.10436,-19.10436l0,-129.32173c-1.46955,-11.75653 -10.28698,-19.10436 -19.10436,-19.10436z"/>
<path d="m392.33476,287.85649c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436l0,129.32173c0,10.28698 8.81738,19.10436 19.10436,19.10436s19.10436,-8.81738 19.10436,-19.10436l0,-129.32173c-1.46955,-11.75653 -8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m440.83045,205.56085c19.10436,-10.28698 29.39129,-36.73911 29.39129,-82.29564c0,-52.90436 -13.22609,-114.62609 -48.49564,-114.62609s-48.49564,61.72173 -48.49564,114.62609c0,45.55653 10.28698,72.00871 29.39129,82.29564l0,35.26955c0,10.28698 8.81738,19.10436 19.10436,19.10436s19.10436,-8.81738 19.10436,-19.10436l0,-35.26955l-0.00002,0zm-19.10436,-154.30436c5.87827,11.75653 11.75653,36.73911 11.75653,72.00871c0,36.73911 -7.34782,49.9652 -11.75653,49.9652s-11.75653,-13.22609 -11.75653,-49.9652c1.46955,-35.26955 7.34782,-60.25218 11.75653,-72.00871z"/>
<path d="m342.36956,123.26521c0,-1.46955 -1.46955,-1.46955 -1.46955,-2.93911l-47.02609,-60.25218c-2.93911,-4.40871 -8.81738,-7.34782 -14.69564,-7.34782l-23.51307,0l0,-27.92173c0,-10.28698 -8.81738,-19.10436 -19.10436,-19.10436s-19.10436,8.81738 -19.10436,19.10436l0,29.39129l-57.31307,0l-16.1652,0l-76.41738,0c-5.87827,0 -10.28698,2.93911 -14.69564,7.34782l-47.02609,60.25218c0,1.46955 -1.46955,1.46955 -1.46955,2.93911c0,0 -1.46955,1.46955 -1.46955,1.46955c1.46955,1.46955 1.46955,4.40871 1.46955,5.87827l0,108.74782c0,10.28698 8.81738,19.10436 19.10436,19.10436l76.41738,0l108.74782,0l117.56525,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436l0,-108.74782c0,-2.93911 0,-4.40871 -1.46955,-5.87827c-1.46955,-1.46955 -1.46955,-1.46955 -1.46955,-2.93911l-0.00005,0l-0.00002,0zm-224.84351,99.93045l-76.41738,0l0,-72.00871l149.89564,0l0,72.00871l-73.47827,0l0.00001,0zm99.93045,-108.74782l-17.6348,-23.51307l70.53916,0l17.6348,23.51307l-70.53916,0zm-64.66089,-23.51307l17.6348,23.51307l-110.21738,0l17.6348,-23.51307l74.94782,0l-0.00005,0l0.00001,0zm74.94782,60.25218l80.82609,0l0,72.00871l-80.82609,0l0,-72.00871z"/>
</g>
</g>
</svg>;
}
export default @connect(mapStateToProps)
@injectIntl
class Compose extends React.PureComponent {
@ -155,6 +137,12 @@ class Compose extends React.PureComponent {
return (
<Column onFocus={this.onFocus}>
<ColumnHeader
icon='pencil'
title={intl.formatMessage(messages.compose)}
onClick={this.handleHeaderClick}
multiColumn={multiColumn}
/>
<NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer />

View File

@ -54,7 +54,7 @@ class Explore extends React.PureComponent {
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader
icon={isSearching ? 'search' : 'hashtag'}
icon='search'
title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)}
onClick={this.handleHeaderClick}
multiColumn={multiColumn}

View File

@ -4,8 +4,8 @@ import { NavLink } from 'react-router-dom';
import Icon from 'mastodon/components/icon';
import classNames from 'classnames';
const ColumnLink = ({ icon, text, to, href, method, badge, transparent, ...other }) => {
const className = classNames('column-link', { 'column-link--transparent': transparent });
const ColumnLink = ({ icon, text, to, href, method, badge, transparent, button, onClick, ...other }) => {
const className = classNames('column-link', { 'column-link--transparent': transparent, 'column-link--button': button });
const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null;
const iconElement = typeof icon === 'string' ? <Icon id={icon} fixedWidth className='column-link__icon' /> : icon;
@ -17,6 +17,14 @@ const ColumnLink = ({ icon, text, to, href, method, badge, transparent, ...other
{badgeElement}
</a>
);
} else if (button) {
return (
<button className={className} onClick={onClick} title={text} {...other}>
{iconElement}
<span>{text}</span>
{badgeElement}
</button>
);
} else {
return (
<NavLink to={to} className={className} title={text} {...other}>
@ -36,6 +44,8 @@ ColumnLink.propTypes = {
method: PropTypes.string,
badge: PropTypes.node,
transparent: PropTypes.bool,
button: PropTypes.bool,
onClick: PropTypes.function,
};
export default ColumnLink;

View File

@ -151,10 +151,8 @@ export default class ColumnsArea extends ImmutablePureComponent {
<div className='columns-area columns-area--mobile'>{children}</div>
</div>
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
<div className='columns-area__panels__pane__inner'>
<NavigationPanel />
</div>
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational columns-area__panels__pane__inner'>
<NavigationPanel />
</div>
</div>
);

View File

@ -19,6 +19,7 @@ const messages = defineMessages({
explore: { id: 'explore.title', defaultMessage: 'Explore' },
local: { id: 'tabs_bar.local_timeline', defaultMessage: 'Local' },
federated: { id: 'tabs_bar.federated_timeline', defaultMessage: 'Federated' },
menu: { id: 'navigation_bar.menu', defaultMessage: 'Menu' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
@ -33,14 +34,20 @@ const messages = defineMessages({
const Account = connect(state => ({
account: state.getIn(['accounts', me]),
}))(({ account }) => (
<Permalink className='column-link column-link--transparent' href={account.get('url')} to={`/@${account.get('acct')}`} title={account.get('acct')}>
<Avatar account={account} size={24} />
<Permalink className='column-link column-link--transparent navigation-panel--profile' href={account.get('url')} to={`/@${account.get('acct')}`} title={account.get('acct')}>
<Avatar account={account} size={32} inline />
<span>Profile</span>
</Permalink>
));
export default @injectIntl
class NavigationPanel extends React.Component {
constructor() {
super();
this.handleMenuToggle = this.handleMenuToggle.bind(this);
}
static contextTypes = {
router: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired,
@ -50,62 +57,86 @@ class NavigationPanel extends React.Component {
intl: PropTypes.object.isRequired,
};
state = {
retracted: false,
};
handleMenuToggle() {
this.setState({
retracted: !this.state.retracted,
});
const mainContent = document.querySelector('.columns-area--mobile');
if (!this.state.retracted) {
mainContent.classList.add('navigation-panel--retracted');
mainContent.classList.remove('navigation-panel--extended');
} else {
mainContent.classList.add('navigation-panel--extended');
mainContent.classList.remove('navigation-panel--retracted');
}
};
render () {
const { intl } = this.props;
const { signedIn, disabledAccountId } = this.context.identity;
const isWideSingleColumnLayout = document.querySelector('.columns-area__panels__pane--compositional') && window.getComputedStyle(document.querySelector('.columns-area__panels__pane--compositional')).display !== 'none';
return (
<div className='navigation-panel'>
<ColumnLink transparent button onClick={this.handleMenuToggle} icon='bars' text={intl.formatMessage(messages.menu)} />
{signedIn && (
<React.Fragment>
<Account />
<ColumnLink id='navigation-panel__publish' transparent to='/publish' icon='pencil' text={intl.formatMessage(messages.publish)} />
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
<ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} />
<FollowRequestsColumnLink />
</React.Fragment>
)}
{ (isWideSingleColumnLayout || !this.state.retracted) && <div id='navigation-retractable'>
{signedIn && (
<React.Fragment>
<Account />
<ColumnLink id='navigation-panel__publish' transparent to='/publish' icon='pencil' text={intl.formatMessage(messages.publish)} />
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
<ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} />
<FollowRequestsColumnLink />
</React.Fragment>
)}
{showTrends ? (
<ColumnLink transparent to='/explore' icon='search' text={intl.formatMessage(messages.explore)} />
) : (
<ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />
)}
{showTrends ? (
<ColumnLink transparent to='/explore' icon='search' text={intl.formatMessage(messages.explore)} />
) : (
<ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />
)}
{(signedIn || timelinePreview) && (
<>
<ColumnLink transparent to='/public/local' icon='users' text={intl.formatMessage(messages.local)} />
<ColumnLink transparent exact to='/public' icon='globe' text={intl.formatMessage(messages.federated)} />
</>
)}
{(signedIn || timelinePreview) && (
<>
<ColumnLink transparent to='/public/local' icon='users' text={intl.formatMessage(messages.local)} />
<ColumnLink transparent exact to='/public' icon='globe' text={intl.formatMessage(messages.federated)} />
</>
)}
{!signedIn && (
<div className='navigation-panel__sign-in-banner'>
{!signedIn && (
<div className='navigation-panel__sign-in-banner'>
<hr />
{ disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner /> }
</div>
)}
{signedIn && (
<React.Fragment>
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
<ListPanel />
<hr />
<ColumnLink transparent href='/settings/preferences' icon='cog' text={intl.formatMessage(messages.preferences)} />
</React.Fragment>
)}
<div className='navigation-panel__legal'>
<hr />
{ disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner /> }
<ColumnLink transparent href='/about' icon='ellipsis-h' text={intl.formatMessage(messages.about)} />
</div>
)}
{signedIn && (
<React.Fragment>
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
<ListPanel />
<hr />
<ColumnLink transparent href='/settings/preferences' icon='cog' text={intl.formatMessage(messages.preferences)} />
</React.Fragment>
)}
<div className='navigation-panel__legal'>
<hr />
<ColumnLink transparent href='/about' icon='ellipsis-h' text={intl.formatMessage(messages.about)} />
</div>
}
<NavigationPortal />
</div>

View File

@ -386,6 +386,7 @@
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",
"navigation_bar.menu": "Menu",
"navigation_bar.mutes": "Muted users",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Pinned posts",

View File

@ -1181,3 +1181,35 @@ button.icon-button.disabled i.fa-retweet {
color: darken($ui-highlight-color, 30%);
}
}
// Tablet layout
@media screen and (max-width: $no-gap-breakpoint - 1px) {
.tabs-bar__wrapper {
z-index: 2;
}
.columns-area--mobile {
z-index: 0;
}
.column-header {
box-sizing: border-box;
height: 48px;
}
}
// Mobile layout
@media screen and (max-width: $no-gap-breakpoint - 285px - 1px) {
.tabs-bar__wrapper {
z-index: 2;
}
.columns-area--mobile {
z-index: 0;
}
.column-header {
box-sizing: border-box;
height: 48px;
}
}

View File

@ -2626,30 +2626,6 @@ $ui-header-height: 0px;
}
}
@media screen and (max-width: $no-gap-breakpoint - 285px - 1px) {
$sidebar-width: 55px;
.columns-area__panels__main {
width: calc(100% - $sidebar-width);
}
.columns-area__panels__pane--navigational {
min-width: $sidebar-width;
.columns-area__panels__pane__inner {
width: $sidebar-width;
}
.column-link span {
display: none;
}
.list-panel {
display: none;
}
}
}
.explore__search-header {
display: none;
}
@ -8787,6 +8763,8 @@ div.status__content--with-spoiler {
padding: 8px 6px;
border-radius: 4px;
color: $primary-text-color;
font-size: 16px;
margin-right: 8px;
}
}
@ -8796,3 +8774,173 @@ div.status__content--with-spoiler {
color: lighten($ui-highlight-color, 30%);
}
}
.navigation-panel--profile {
padding: 10px;
}
.column-link--button {
border: 0;
border-bottom: 1px solid lighten($ui-base-color, 8%);
box-sizing: border-box;
height: 48px;
text-align: left;
}
@media screen and (min-width: $no-gap-breakpoint) {
$sidebar-width: 100%;
.columns-area__panels__pane--compositional {
.columns-area__panels__pane__inner {
position: fixed;
}
}
.columns-area__panels__pane__inner {
position: relative;
}
.navigation-panel {
position: fixed;
top: 0;
bottom: 0;
}
.navigation-panel--profile {
display: none;
}
#navigation-panel__publish {
display: none;
}
.column-link--button {
display: none;
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
$sidebar-width: 285px;
.tabs-bar__wrapper {
z-index: 1;
width: calc(100% - $sidebar-width);
}
.columns-area__panels__main {
width: 100%;
}
.columns-area--mobile {
// z-index: -1;
}
.columns-area--mobile.navigation-panel--retracted {
width: 100%;
margin: 0 auto auto 0;
}
.columns-area--mobile.navigation-panel--extended {
// z-index: -1;
width: calc(100% - $sidebar-width);
margin: 0;
color: red !important;
}
.columns-area__panels__pane__inner {
width: 0;
height: 0;
min-width: 0;
}
.column-header__collapsible {
width: 100vw;
}
.navigation-panel {
width: $sidebar-width;
z-index: 1000;
position: fixed;
right: 0;
height: auto !important;
box-sizing: border-box;
}
.column-link__icon {
margin-right: 13px;
}
.account__avatar-inline {
margin-right: 8px;
}
}
@media screen and (max-width: $no-gap-breakpoint - 285px - 1px) {
$sidebar-width: 55px;
.tabs-bar__wrapper {
z-index: 1;
width: calc(100% - $sidebar-width);
}
.columns-area__panels__main {
width: 100%;
}
.columns-area--mobile {
// z-index: -1;
}
.columns-area--mobile.navigation-panel--extended {
// z-index: -1;
width: calc(100% - $sidebar-width);
margin: 0;
}
.columns-area--mobile.navigation-panel--retracted {
width: 100%;
margin: 0 auto auto 0;
}
.columns-area__panels__pane__inner {
width: 0;
height: 0;
min-width: 0;
}
.column-header__collapsible {
width: 100vw;
}
.navigation-panel {
width: $sidebar-width;
z-index: 1000;
position: fixed;
right: 0;
height: auto !important;
box-sizing: border-box;
}
.columns-area__panels__pane--navigational {
min-width: $sidebar-width;
.columns-area__panels__pane__inner {
width: $sidebar-width;
}
.column-link span {
display: none;
}
.list-panel {
display: none;
}
}
}
#navigation-retractable {
position: relative;
overflow-y: auto;
bottom: 0;
height: 100vh;
}