feat(compose): More space on mobile devices (#4282)

* feat(compose): More space on mobile devices

* feat(compose): Hide navigation when typing on mobile devices

* fix(compose): Make animation faster

* fix(navigation_bar): Remove hardcoded title

* fix(compose): Prevent accidental bluring

* fix(compose): Increase max-height to 600px
This commit is contained in:
Sorin Davidoi 2017-07-21 01:38:24 +02:00 committed by Eugen Rochko
parent 4b911fea03
commit c1bc5e14eb
6 changed files with 114 additions and 3 deletions

View file

@ -27,6 +27,7 @@ export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
@ -278,3 +279,10 @@ export function insertEmojiCompose(position, emoji) {
emoji, emoji,
}; };
}; };
export function changeComposing(value) {
return {
type: COMPOSE_COMPOSING_CHANGE,
value,
};
}

View file

@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Avatar from '../../../components/avatar'; import Avatar from '../../../components/avatar';
import IconButton from '../../../components/icon_button';
import Permalink from '../../../components/permalink'; import Permalink from '../../../components/permalink';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -9,6 +11,7 @@ export default class NavigationBar extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
onClose: PropTypes.func.isRequired,
}; };
render () { render () {
@ -25,6 +28,8 @@ export default class NavigationBar extends ImmutablePureComponent {
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a> <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
</div> </div>
<IconButton title='' icon='close' onClick={this.props.onClose} />
</div> </div>
); );
} }

View file

@ -11,6 +11,7 @@ import SearchContainer from './containers/search_container';
import Motion from 'react-motion/lib/Motion'; import Motion from 'react-motion/lib/Motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container'; import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
const messages = defineMessages({ const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@ -47,6 +48,14 @@ export default class Compose extends React.PureComponent {
this.props.dispatch(unmountCompose()); this.props.dispatch(unmountCompose());
} }
onFocus = () => {
this.props.dispatch(changeComposing(true));
}
onBlur = () => {
this.props.dispatch(changeComposing(false));
}
render () { render () {
const { multiColumn, showSearch, intl } = this.props; const { multiColumn, showSearch, intl } = this.props;
@ -82,8 +91,8 @@ export default class Compose extends React.PureComponent {
<SearchContainer /> <SearchContainer />
<div className='drawer__pager'> <div className='drawer__pager'>
<div className='drawer__inner'> <div className='drawer__inner' onFocus={this.onFocus}>
<NavigationContainer /> <NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer /> <ComposeFormContainer />
</div> </div>

View file

@ -43,6 +43,7 @@ import '../../components/status';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
systemFontUi: state.getIn(['meta', 'system_font_ui']), systemFontUi: state.getIn(['meta', 'system_font_ui']),
isComposing: state.getIn(['compose', 'is_composing']),
}); });
@connect(mapStateToProps) @connect(mapStateToProps)
@ -52,6 +53,7 @@ export default class UI extends React.PureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
children: PropTypes.node, children: PropTypes.node,
systemFontUi: PropTypes.bool, systemFontUi: PropTypes.bool,
isComposing: PropTypes.bool,
}; };
state = { state = {
@ -133,6 +135,19 @@ export default class UI extends React.PureComponent {
this.props.dispatch(refreshNotifications()); this.props.dispatch(refreshNotifications());
} }
shouldComponentUpdate (nextProps) {
if (nextProps.isComposing !== this.props.isComposing) {
// Avoid expensive update just to toggle a class
this.node.classList.toggle('is-composing', nextProps.isComposing);
return false;
}
// Why isn't this working?!?
// return super.shouldComponentUpdate(nextProps, nextState);
return true;
}
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
document.removeEventListener('dragenter', this.handleDragEnter); document.removeEventListener('dragenter', this.handleDragEnter);

View file

@ -20,6 +20,7 @@ import {
COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILERNESS_CHANGE,
COMPOSE_SPOILER_TEXT_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE,
COMPOSE_VISIBILITY_CHANGE, COMPOSE_VISIBILITY_CHANGE,
COMPOSE_COMPOSING_CHANGE,
COMPOSE_EMOJI_INSERT, COMPOSE_EMOJI_INSERT,
} from '../actions/compose'; } from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines'; import { TIMELINE_DELETE } from '../actions/timelines';
@ -37,6 +38,7 @@ const initialState = ImmutableMap({
focusDate: null, focusDate: null,
preselectDate: null, preselectDate: null,
in_reply_to: null, in_reply_to: null,
is_composing: false,
is_submitting: false, is_submitting: false,
is_uploading: false, is_uploading: false,
progress: 0, progress: 0,
@ -146,7 +148,9 @@ export default function compose(state = initialState, action) {
case COMPOSE_MOUNT: case COMPOSE_MOUNT:
return state.set('mounted', true); return state.set('mounted', true);
case COMPOSE_UNMOUNT: case COMPOSE_UNMOUNT:
return state.set('mounted', false); return state
.set('mounted', false)
.set('is_composing', false);
case COMPOSE_SENSITIVITY_CHANGE: case COMPOSE_SENSITIVITY_CHANGE:
return state return state
.set('sensitive', !state.get('sensitive')) .set('sensitive', !state.get('sensitive'))
@ -169,6 +173,8 @@ export default function compose(state = initialState, action) {
return state return state
.set('text', action.text) .set('text', action.text)
.set('idempotencyKey', uuid()); .set('idempotencyKey', uuid());
case COMPOSE_COMPOSING_CHANGE:
return state.set('is_composing', action.value);
case COMPOSE_REPLY: case COMPOSE_REPLY:
return state.withMutations(map => { return state.withMutations(map => {
map.set('in_reply_to', action.status.get('id')); map.set('in_reply_to', action.status.get('id'));

View file

@ -1177,6 +1177,11 @@
.permalink { .permalink {
text-decoration: none; text-decoration: none;
} }
.icon-button {
pointer-events: none;
opacity: 0;
}
} }
.navigation-bar__profile { .navigation-bar__profile {
@ -3723,3 +3728,66 @@ noscript {
margin: 20px 0; margin: 20px 0;
} }
} }
@media screen and (max-width: 1024px) and (max-height: 600px) {
$duration: 400ms;
$delay: 100ms;
.tabs-bar,
.search {
will-change: margin-top;
transition: margin-top $duration $delay;
}
.navigation-bar {
will-change: padding-bottom;
transition: padding-bottom $duration $delay;
}
.navigation-bar {
& > a:first-child {
will-change: margin-top, margin-left, width;
transition: margin-top $duration $delay, margin-left $duration ($duration + $delay);
}
& > .navigation-bar__profile-edit {
will-change: margin-top;
transition: margin-top $duration $delay;
}
& > .icon-button {
will-change: opacity;
transition: opacity $duration $delay;
}
}
.is-composing {
.tabs-bar,
.search {
margin-top: -50px;
}
.navigation-bar {
padding-bottom: 0;
& > a:first-child {
margin-top: -50px;
margin-left: -40px;
}
.navigation-bar__profile {
padding-top: 2px;
}
.navigation-bar__profile-edit {
position: absolute;
margin-top: -50px;
}
.icon-button {
pointer-events: auto;
opacity: 1;
}
}
}
}