Skip to content

Commit 9e4a9ef

Browse files
Gargronhiyuki2578
authored andcommitted
Add forceSingleColumn prop to <UI /> (mastodon#10807)
* Move TabsBar rendering logic from CSS to the ColumnsArea component * Add forceSingleColumn mode * Add unread notifications counter to tabs bar * Add toggle to control `forceSingleColumn` * Increase paddings in mobile layout responsively at large sizes
1 parent 83ae7df commit 9e4a9ef

9 files changed

Lines changed: 296 additions & 118 deletions

File tree

app/javascript/mastodon/components/autosuggest_input.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
4949
autoFocus: PropTypes.bool,
5050
className: PropTypes.string,
5151
id: PropTypes.string,
52-
searchTokens: PropTypes.list,
52+
searchTokens: ImmutablePropTypes.list,
5353
maxLength: PropTypes.number,
5454
};
5555

app/javascript/mastodon/features/compose/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ class Compose extends React.PureComponent {
106106
<div className='drawer__pager'>
107107
{!isSearchPage && <div className='drawer__inner' onFocus={this.onFocus}>
108108
<NavigationContainer onClose={this.onBlur} />
109+
109110
<ComposeFormContainer />
110-
{multiColumn && (
111-
<div className='drawer__inner__mastodon'>
112-
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
113-
</div>
114-
)}
111+
112+
<div className='drawer__inner__mastodon'>
113+
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
114+
</div>
115115
</div>}
116116

117117
<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}>

app/javascript/mastodon/features/getting_started/index.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import PropTypes from 'prop-types';
88
import ImmutablePropTypes from 'react-immutable-proptypes';
99
import ImmutablePureComponent from 'react-immutable-pure-component';
1010
import { me, invitesEnabled, version, profile_directory, repository, source_url } from '../../initial_state';
11-
import { fetchFollowRequests } from '../../actions/accounts';
11+
import { fetchFollowRequests } from 'mastodon/actions/accounts';
12+
import { changeSetting } from 'mastodon/actions/settings';
1213
import { List as ImmutableList } from 'immutable';
1314
import { Link } from 'react-router-dom';
1415
import NavigationBar from '../compose/components/navigation_bar';
1516
import Icon from 'mastodon/components/icon';
17+
import Toggle from 'react-toggle';
1618

1719
const messages = defineMessages({
1820
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
@@ -39,10 +41,12 @@ const messages = defineMessages({
3941
const mapStateToProps = state => ({
4042
myAccount: state.getIn(['accounts', me]),
4143
unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
44+
forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false),
4245
});
4346

4447
const mapDispatchToProps = dispatch => ({
4548
fetchFollowRequests: () => dispatch(fetchFollowRequests()),
49+
changeForceSingleColumn: checked => dispatch(changeSetting(['forceSingleColumn'], checked)),
4650
});
4751

4852
const badgeDisplay = (number, limit) => {
@@ -67,6 +71,8 @@ class GettingStarted extends ImmutablePureComponent {
6771
fetchFollowRequests: PropTypes.func.isRequired,
6872
unreadFollowRequests: PropTypes.number,
6973
unreadNotifications: PropTypes.number,
74+
forceSingleColumn: PropTypes.bool,
75+
changeForceSingleColumn: PropTypes.func.isRequired,
7076
};
7177

7278
componentDidMount () {
@@ -77,8 +83,12 @@ class GettingStarted extends ImmutablePureComponent {
7783
}
7884
}
7985

86+
handleForceSingleColumnChange = ({ target }) => {
87+
this.props.changeForceSingleColumn(target.checked);
88+
}
89+
8090
render () {
81-
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
91+
const { intl, myAccount, multiColumn, unreadFollowRequests, forceSingleColumn } = this.props;
8292

8393
const navItems = [];
8494
let i = 1;
@@ -177,6 +187,11 @@ class GettingStarted extends ImmutablePureComponent {
177187
</p>
178188
</div>
179189
</div>
190+
191+
<label className='navigational-toggle'>
192+
<FormattedMessage id='getting_started.use_simple_layout' defaultMessage='Use simple layout' />
193+
<Toggle checked={forceSingleColumn} onChange={this.handleForceSingleColumnChange} />
194+
</label>
180195
</Column>
181196
);
182197
}

app/javascript/mastodon/features/ui/components/columns_area.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
55
import ImmutablePureComponent from 'react-immutable-pure-component';
66

77
import ReactSwipeableViews from 'react-swipeable-views';
8-
import { links, getIndex, getLink } from './tabs_bar';
8+
import TabsBar, { links, getIndex, getLink } from './tabs_bar';
99
import { Link } from 'react-router-dom';
1010

1111
import BundleContainer from '../containers/bundle_container';
@@ -139,7 +139,7 @@ class ColumnsArea extends ImmutablePureComponent {
139139
<ColumnLoading title={title} icon={icon} />;
140140

141141
return (
142-
<div className='columns-area' key={index}>
142+
<div className='columns-area columns-area--mobile' key={index}>
143143
{view}
144144
</div>
145145
);
@@ -164,13 +164,17 @@ class ColumnsArea extends ImmutablePureComponent {
164164
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
165165

166166
return columnIndex !== -1 ? [
167+
<TabsBar key='tabs' />,
168+
167169
<ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
168170
{links.map(this.renderView)}
169171
</ReactSwipeableViews>,
170172

171173
floatingActionButton,
172174
] : [
173-
<div className='columns-area'>{children}</div>,
175+
<TabsBar key='tabs' />,
176+
177+
<div key='content' className='columns-area columns-area--mobile'>{children}</div>,
174178

175179
floatingActionButton,
176180
];
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { connect } from 'react-redux';
4+
import Icon from 'mastodon/components/icon';
5+
6+
const mapStateToProps = state => ({
7+
count: state.getIn(['notifications', 'unread']),
8+
});
9+
10+
const formatNumber = num => num > 99 ? '99+' : num;
11+
12+
const NotificationsCounterIcon = ({ count }) => (
13+
<i className='icon-with-badge'>
14+
<Icon id='bell' fixedWidth />
15+
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
16+
</i>
17+
);
18+
19+
NotificationsCounterIcon.propTypes = {
20+
count: PropTypes.number.isRequired,
21+
};
22+
23+
export default connect(mapStateToProps)(NotificationsCounterIcon);

app/javascript/mastodon/features/ui/components/tabs_bar.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import { FormattedMessage, injectIntl } from 'react-intl';
55
import { debounce } from 'lodash';
66
import { isUserTouching } from '../../../is_mobile';
77
import Icon from 'mastodon/components/icon';
8+
import NotificationsCounterIcon from './notifications_counter_icon';
89

910
export const links = [
1011
<NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
11-
<NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><Icon id='bell' fixedWidth /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
12+
<NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
1213

1314
<NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
1415
<NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,

app/javascript/mastodon/features/ui/index.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { Redirect, withRouter } from 'react-router-dom';
77
import PropTypes from 'prop-types';
88
import NotificationsContainer from './containers/notifications_container';
99
import LoadingBarContainer from './containers/loading_bar_container';
10-
import TabsBar from './components/tabs_bar';
1110
import ModalContainer from './containers/modal_container';
1211
import { isMobile } from '../../is_mobile';
1312
import { debounce } from 'lodash';
@@ -63,6 +62,7 @@ const mapStateToProps = state => ({
6362
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
6463
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
6564
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
65+
forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false),
6666
});
6767

6868
const keyMap = {
@@ -101,6 +101,7 @@ class SwitchingColumnsArea extends React.PureComponent {
101101
children: PropTypes.node,
102102
location: PropTypes.object,
103103
onLayoutChange: PropTypes.func.isRequired,
104+
forceSingleColumn: PropTypes.bool,
104105
};
105106

106107
state = {
@@ -139,12 +140,13 @@ class SwitchingColumnsArea extends React.PureComponent {
139140
}
140141

141142
render () {
142-
const { children } = this.props;
143+
const { children, forceSingleColumn } = this.props;
143144
const { mobile } = this.state;
144-
const redirect = mobile ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
145+
const singleColumn = forceSingleColumn || mobile;
146+
const redirect = singleColumn ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
145147

146148
return (
147-
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
149+
<ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn}>
148150
<WrappedSwitch>
149151
{redirect}
150152
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
@@ -205,6 +207,7 @@ class UI extends React.PureComponent {
205207
location: PropTypes.object,
206208
intl: PropTypes.object.isRequired,
207209
dropdownMenuIsOpen: PropTypes.bool,
210+
forceSingleColumn: PropTypes.bool,
208211
};
209212

210213
state = {
@@ -453,7 +456,7 @@ class UI extends React.PureComponent {
453456

454457
render () {
455458
const { draggingOver } = this.state;
456-
const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
459+
const { children, isComposing, location, dropdownMenuIsOpen, forceSingleColumn } = this.props;
457460

458461
const handlers = {
459462
help: this.handleHotkeyToggleHelp,
@@ -479,9 +482,7 @@ class UI extends React.PureComponent {
479482
return (
480483
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
481484
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
482-
<TabsBar />
483-
484-
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
485+
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange} forceSingleColumn={forceSingleColumn}>
485486
{children}
486487
</SwitchingColumnsArea>
487488

app/javascript/mastodon/reducers/settings.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const initialState = ImmutableMap({
1414

1515
skinTone: 1,
1616

17+
forceSingleColumn: false,
18+
1719
home: ImmutableMap({
1820
shows: ImmutableMap({
1921
reblog: true,

0 commit comments

Comments
 (0)