Fix scroll to top in single column UI (#11463)
parent
8b9d0a0533
commit
2dee293c4c
|
@ -8,10 +8,11 @@ export default class Column extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
|
bindToDocument: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollTop () {
|
scrollTop () {
|
||||||
const scrollable = this.node.querySelector('.scrollable');
|
const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable');
|
||||||
|
|
||||||
if (!scrollable) {
|
if (!scrollable) {
|
||||||
return;
|
return;
|
||||||
|
@ -33,11 +34,19 @@ export default class Column extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
if (this.props.bindToDocument) {
|
||||||
|
document.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||||
|
} else {
|
||||||
|
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.node.removeEventListener('wheel', this.handleWheel);
|
if (this.props.bindToDocument) {
|
||||||
|
document.removeEventListener('wheel', this.handleWheel);
|
||||||
|
} else {
|
||||||
|
this.node.removeEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
export default class ColumnBackButton extends React.PureComponent {
|
export default class ColumnBackButton extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -9,6 +10,10 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
if (window.history && window.history.length === 1) {
|
if (window.history && window.history.length === 1) {
|
||||||
this.context.router.history.push('/');
|
this.context.router.history.push('/');
|
||||||
|
@ -18,12 +23,20 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
const { multiColumn } = this.props;
|
||||||
|
|
||||||
|
const component = (
|
||||||
<button onClick={this.handleClick} className='column-back-button'>
|
<button onClick={this.handleClick} className='column-back-button'>
|
||||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (multiColumn) {
|
||||||
|
return component;
|
||||||
|
} else {
|
||||||
|
return createPortal(component, document.getElementById('tabs-bar__portal'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
isAccount: PropTypes.bool,
|
isAccount: PropTypes.bool,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -116,7 +117,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props;
|
const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount, multiColumn } = this.props;
|
||||||
const { width } = this.state;
|
const { width } = this.state;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
|
@ -143,7 +144,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}>
|
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}>
|
||||||
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
||||||
|
|
|
@ -100,7 +100,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
|
|
||||||
<StatusList
|
<StatusList
|
||||||
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='ban' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='blocks'
|
scrollKey='blocks'
|
||||||
|
|
|
@ -105,7 +105,7 @@ class CommunityTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='users'
|
icon='users'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -75,7 +75,7 @@ class DirectTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='envelope'
|
icon='envelope'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -58,7 +58,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
|
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='domain_blocks'
|
scrollKey='domain_blocks'
|
||||||
|
|
|
@ -74,7 +74,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='star'
|
icon='star'
|
||||||
title={intl.formatMessage(messages.heading)}
|
title={intl.formatMessage(messages.heading)}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='favourites'
|
scrollKey='favourites'
|
||||||
|
|
|
@ -57,7 +57,7 @@ class FollowRequests extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='user-plus' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='user-plus' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='follow_requests'
|
scrollKey='follow_requests'
|
||||||
|
|
|
@ -78,7 +78,7 @@ class Followers extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='followers'
|
scrollKey='followers'
|
||||||
|
|
|
@ -78,7 +78,7 @@ class Following extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='following'
|
scrollKey='following'
|
||||||
|
|
|
@ -148,7 +148,7 @@ class GettingStarted extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.menu)}>
|
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.menu)}>
|
||||||
{multiColumn && <div className='column-header__wrapper'>
|
{multiColumn && <div className='column-header__wrapper'>
|
||||||
<h1 className='column-header'>
|
<h1 className='column-header'>
|
||||||
<button>
|
<button>
|
||||||
|
|
|
@ -135,7 +135,7 @@ class HashtagTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={`#${id}`}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='hashtag'
|
icon='hashtag'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -98,7 +98,7 @@ class HomeTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='home'
|
icon='home'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -18,10 +18,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl } = this.props;
|
const { intl, multiColumn } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='question' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
|
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
|
||||||
<table>
|
<table>
|
||||||
|
|
|
@ -148,14 +148,14 @@ class ListTimeline extends React.PureComponent {
|
||||||
} else if (list === false) {
|
} else if (list === false) {
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
<MissingIndicator />
|
<MissingIndicator />
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={title}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='list-ul'
|
icon='list-ul'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class Lists extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='list-ul' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
|
|
||||||
<NewListForm />
|
<NewListForm />
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Mutes extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='mutes'
|
scrollKey='mutes'
|
||||||
|
|
|
@ -198,7 +198,7 @@ class Notifications extends React.PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='bell'
|
icon='bell'
|
||||||
active={isUnread}
|
active={isUnread}
|
||||||
|
|
|
@ -47,7 +47,7 @@ class PinnedStatuses extends ImmutablePureComponent {
|
||||||
const { intl, shouldUpdateScroll, statusIds, hasMore, multiColumn } = this.props;
|
const { intl, shouldUpdateScroll, statusIds, hasMore, multiColumn } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
|
<Column bindToDocument={!multiColumn} icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<StatusList
|
<StatusList
|
||||||
statusIds={statusIds}
|
statusIds={statusIds}
|
||||||
|
|
|
@ -105,7 +105,7 @@ class PublicTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='globe'
|
icon='globe'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='reblogs'
|
scrollKey='reblogs'
|
||||||
|
|
|
@ -471,7 +471,7 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.detailedStatus)}>
|
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
showBackButton
|
showBackButton
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
|
|
|
@ -2411,6 +2411,8 @@ a.account__display-name {
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-back-button {
|
.column-back-button {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
background: lighten($ui-base-color, 4%);
|
background: lighten($ui-base-color, 4%);
|
||||||
color: $highlight-text-color;
|
color: $highlight-text-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
Reference in New Issue