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 600pxmain
parent
4b911fea03
commit
c1bc5e14eb
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue