[Glitch] Fix intermediary responsive layout, accessibility on navigation in web UI

Port 0765324622 to glitch-soc

Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
main
Eugen Rochko 2022-10-09 15:55:32 +02:00 committed by Claire
parent dea951cce8
commit c36f28ba77
9 changed files with 104 additions and 44 deletions

View File

@ -70,6 +70,8 @@ export default class Avatar extends React.PureComponent {
onMouseLeave={this.handleMouseLeave} onMouseLeave={this.handleMouseLeave}
style={style} style={style}
data-avatar-of={account && `@${account.get('acct')}`} data-avatar-of={account && `@${account.get('acct')}`}
role='img'
aria-label={account.get('acct')}
/> />
); );
} }

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
const Logo = () => ( const Logo = () => (
<svg viewBox='0 0 261 66' className='logo'> <svg viewBox='0 0 261 66' className='logo' role='img'>
<title>Mastodon</title>
<use xlinkHref='#logo-symbol-wordmark' /> <use xlinkHref='#logo-symbol-wordmark' />
</svg> </svg>
); );

View File

@ -1,26 +1,29 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Link } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import Icon from 'flavours/glitch/components/icon'; import Icon from 'flavours/glitch/components/icon';
import classNames from 'classnames';
const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => { const ColumnLink = ({ icon, text, to, onClick, href, method, badge, transparent, ...other }) => {
const className = classNames('column-link', { 'column-link--transparent': transparent });
const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null; 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;
if (href) { if (href) {
return ( return (
<a href={href} className='column-link' data-method={method}> <a href={href} className={className} data-method={method} title={text} {...other}>
<Icon id={icon} fixedWidth className='column-link__icon' /> {iconElement}
{text} {text}
{badgeElement} {badgeElement}
</a> </a>
); );
} else if (to) { } else if (to) {
return ( return (
<Link to={to} className='column-link'> <NavLink to={to} className={className} title={text} {...other}>
<Icon id={icon} fixedWidth className='column-link__icon' /> {iconElement}
{text} {text}
{badgeElement} {badgeElement}
</Link> </NavLink>
); );
} else { } else {
const handleOnClick = (e) => { const handleOnClick = (e) => {
@ -29,8 +32,8 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => {
return onClick(e); return onClick(e);
} }
return ( return (
<a href='#' onClick={onClick && handleOnClick} className='column-link' tabIndex='0'> <a href='#' onClick={onClick && handleOnClick} className={className} title={text} {...other} tabIndex='0'>
<Icon id={icon} fixedWidth className='column-link__icon' /> {iconElement}
{text} {text}
{badgeElement} {badgeElement}
</a> </a>
@ -39,13 +42,14 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => {
}; };
ColumnLink.propTypes = { ColumnLink.propTypes = {
icon: PropTypes.string.isRequired, icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
text: PropTypes.string.isRequired, text: PropTypes.string.isRequired,
to: PropTypes.string, to: PropTypes.string,
onClick: PropTypes.func, onClick: PropTypes.func,
href: PropTypes.string, href: PropTypes.string,
method: PropTypes.string, method: PropTypes.string,
badge: PropTypes.node, badge: PropTypes.node,
transparent: PropTypes.bool,
}; };
export default ColumnLink; export default ColumnLink;

View File

@ -2,22 +2,27 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { NavLink, withRouter } from 'react-router-dom'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
import IconWithBadge from 'flavours/glitch/components/icon_with_badge'; import IconWithBadge from 'flavours/glitch/components/icon_with_badge';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import { FormattedMessage } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
text: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
});
const mapStateToProps = state => ({ const mapStateToProps = state => ({
count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
}); });
export default @withRouter export default @injectIntl
@connect(mapStateToProps) @connect(mapStateToProps)
class FollowRequestsNavLink extends React.Component { class FollowRequestsColumnLink extends React.Component {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
count: PropTypes.number.isRequired, count: PropTypes.number.isRequired,
intl: PropTypes.object.isRequired,
}; };
componentDidMount () { componentDidMount () {
@ -27,13 +32,20 @@ class FollowRequestsNavLink extends React.Component {
} }
render () { render () {
const { count } = this.props; const { count, intl } = this.props;
if (count === 0) { if (count === 0) {
return null; return null;
} }
return <NavLink className='column-link column-link--transparent' to='/follow_requests'><IconWithBadge className='column-link__icon' id='user-plus' count={count} /><FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' /></NavLink>; return (
<ColumnLink
transparent
to='/follow_requests'
icon={<IconWithBadge className='column-link__icon' id='user-plus' count={count} />}
text={intl.formatMessage(messages.text)}
/>
);
} }
} }

View File

@ -11,8 +11,7 @@ import { connect } from 'react-redux';
const Account = connect(state => ({ const Account = connect(state => ({
account: state.getIn(['accounts', me]), account: state.getIn(['accounts', me]),
}))(({ account }) => ( }))(({ account }) => (
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`}> <Permalink href={account.get('url')} to={`/@${account.get('acct')}`} title={account.get('acct')}>
<span style={{ display: 'none' }}>{account.get('acct')}</span>
<Avatar account={account} size={35} /> <Avatar account={account} size={35} />
</Permalink> </Permalink>
)); ));

View File

@ -1,17 +1,34 @@
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types';
import { Link, NavLink } from 'react-router-dom'; import { defineMessages, injectIntl } from 'react-intl';
import Icon from 'flavours/glitch/components/icon'; import { Link } from 'react-router-dom';
import TrendsContainer from 'flavours/glitch/features/getting_started/containers/trends_container'; import TrendsContainer from 'flavours/glitch/features/getting_started/containers/trends_container';
import { showTrends, timelinePreview } from 'flavours/glitch/initial_state'; import { showTrends, timelinePreview } from 'flavours/glitch/initial_state';
import FollowRequestsNavLink from './follow_requests_nav_link'; import FollowRequestsColumnLink from './follow_requests_column_link';
import ListPanel from './list_panel'; import ListPanel from './list_panel';
import NotificationsCounterIcon from './notifications_counter_icon'; import NotificationsCounterIcon from './notifications_counter_icon';
import SignInBanner from './sign_in_banner'; import SignInBanner from './sign_in_banner';
import { preferencesLink, relationshipsLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink, relationshipsLink } from 'flavours/glitch/utils/backend_links';
import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
export default class NavigationPanel extends React.Component { const messages = defineMessages({
home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
explore: { id: 'explore.title', defaultMessage: 'Explore' },
local: { id: 'tabs_bar.local_timeline', defaultMessage: 'Local' },
federated: { id: 'tabs_bar.federated_timeline', defaultMessage: 'Federated' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
about: { id: 'navigation_bar.about', defaultMessage: 'About' },
app_settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
});
export default @injectIntl
class NavigationPanel extends React.Component {
static contextTypes = { static contextTypes = {
router: PropTypes.object.isRequired, router: PropTypes.object.isRequired,
@ -23,24 +40,24 @@ export default class NavigationPanel extends React.Component {
}; };
render() { render() {
const { intl, onOpenSettings } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.context.identity;
const { onOpenSettings } = this.props;
return ( return (
<div className='navigation-panel'> <div className='navigation-panel'>
{signedIn && ( {signedIn && (
<React.Fragment> <React.Fragment>
<NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink> <ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
<NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink> <ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} />
<FollowRequestsNavLink /> <FollowRequestsColumnLink />
</React.Fragment> </React.Fragment>
)} )}
<NavLink className='column-link column-link--transparent' to='/explore' data-preview-title-id='explore.title' data-preview-icon='hashtag'><Icon className='column-link__icon' id='hashtag' fixedWidth /><FormattedMessage id='explore.title' defaultMessage='Explore' /></NavLink> <ColumnLink transparent to='/explore' icon='hashtag' text={intl.formatMessage(messages.explore)} />
{signedIn || timelinePreview && ( {signedIn || timelinePreview && (
<> <>
<NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink> <ColumnLink transparent to='/public/local' icon='users' text={intl.formatMessage(messages.local)} />
<NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink> <ColumnLink transparent exact to='/public' icon='globe' text={intl.formatMessage(messages.federated)} />
</> </>
)} )}
@ -53,17 +70,18 @@ export default class NavigationPanel extends React.Component {
{signedIn && ( {signedIn && (
<React.Fragment> <React.Fragment>
<NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink> <ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
<NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink> <ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink> <ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
<ListPanel /> <ListPanel />
<hr /> <hr />
{!!preferencesLink && <a className='column-link column-link--transparent' href={preferencesLink} target='_blank'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>} {!!preferencesLink && <ColumnLink transparent href={preferencesLink} icon='cog' text={intl.formatMessage(messages.preferences)} />}
<a className='column-link column-link--transparent' href='#' onClick={onOpenSettings}><Icon className='column-link__icon' id='cogs' fixedWidth /><FormattedMessage id='navigation_bar.app_settings' defaultMessage='App settings' /></a> <ColumnLink transparent href='#' onClick={onOpenSettings} icon='cogs' text={intl.formatMessage(messages.app_settings)} />
{!!relationshipsLink && <a className='column-link column-link--transparent' href={relationshipsLink} target='_blank'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>} {!!relationshipsLink && <ColumnLink transparent href={relationshipsLink} icon='users' text={intl.formatMessage(messages.followsAndFollowers)} />}
</React.Fragment> </React.Fragment>
)} )}

View File

@ -214,6 +214,9 @@ $ui-header-height: 55px;
font-size: 16px; font-size: 16px;
padding: 15px; padding: 15px;
text-decoration: none; text-decoration: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover, &:hover,
&:focus, &:focus,

View File

@ -275,12 +275,14 @@
} }
@media screen and (max-width: $no-gap-breakpoint - 1px) { @media screen and (max-width: $no-gap-breakpoint - 1px) {
$sidebar-width: 285px;
.with-fab .scrollable .item-list:last-child { .with-fab .scrollable .item-list:last-child {
padding-bottom: 5.25rem; padding-bottom: 5.25rem;
} }
.columns-area__panels__main { .columns-area__panels__main {
width: calc(100% - 55px); width: calc(100% - $sidebar-width);
} }
.columns-area__panels { .columns-area__panels {
@ -288,10 +290,10 @@
} }
.columns-area__panels__pane--navigational { .columns-area__panels__pane--navigational {
min-width: 55px; min-width: $sidebar-width;
.columns-area__panels__pane__inner { .columns-area__panels__pane__inner {
width: 55px; width: $sidebar-width;
} }
.navigation-panel { .navigation-panel {
@ -301,7 +303,6 @@
height: 100vh; height: 100vh;
} }
.column-link span,
.navigation-panel__sign-in-banner, .navigation-panel__sign-in-banner,
.navigation-panel__logo, .navigation-panel__logo,
.getting-started__trends { .getting-started__trends {
@ -326,11 +327,31 @@
} }
} }
@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;
}
}
}
.explore__search-header { .explore__search-header {
display: none; display: none;
} }
@media screen and (max-width: $no-gap-breakpoint + 285px - 1px) { @media screen and (max-width: $no-gap-breakpoint - 1px) {
.columns-area__panels__pane--compositional { .columns-area__panels__pane--compositional {
display: none; display: none;
} }

View File

@ -51,7 +51,7 @@ $media-modal-media-max-width: 100%;
// put margins on top and bottom of image to avoid the screen covered by image. // put margins on top and bottom of image to avoid the screen covered by image.
$media-modal-media-max-height: 80%; $media-modal-media-max-height: 80%;
$no-gap-breakpoint: 890px; $no-gap-breakpoint: 1175px;
$font-sans-serif: 'mastodon-font-sans-serif' !default; $font-sans-serif: 'mastodon-font-sans-serif' !default;
$font-display: 'mastodon-font-display' !default; $font-display: 'mastodon-font-display' !default;