* 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 600pxmaster
@@ -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_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_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'; | |||
@@ -278,3 +279,10 @@ export function insertEmojiCompose(position, emoji) { | |||
emoji, | |||
}; | |||
}; | |||
export function changeComposing(value) { | |||
return { | |||
type: COMPOSE_COMPOSING_CHANGE, | |||
value, | |||
}; | |||
} |
@@ -1,6 +1,8 @@ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
import Avatar from '../../../components/avatar'; | |||
import IconButton from '../../../components/icon_button'; | |||
import Permalink from '../../../components/permalink'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
@@ -9,6 +11,7 @@ export default class NavigationBar extends ImmutablePureComponent { | |||
static propTypes = { | |||
account: ImmutablePropTypes.map.isRequired, | |||
onClose: PropTypes.func.isRequired, | |||
}; | |||
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> | |||
</div> | |||
<IconButton title='' icon='close' onClick={this.props.onClose} /> | |||
</div> | |||
); | |||
} | |||
@@ -11,6 +11,7 @@ import SearchContainer from './containers/search_container'; | |||
import Motion from 'react-motion/lib/Motion'; | |||
import spring from 'react-motion/lib/spring'; | |||
import SearchResultsContainer from './containers/search_results_container'; | |||
import { changeComposing } from '../../actions/compose'; | |||
const messages = defineMessages({ | |||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, | |||
@@ -47,6 +48,14 @@ export default class Compose extends React.PureComponent { | |||
this.props.dispatch(unmountCompose()); | |||
} | |||
onFocus = () => { | |||
this.props.dispatch(changeComposing(true)); | |||
} | |||
onBlur = () => { | |||
this.props.dispatch(changeComposing(false)); | |||
} | |||
render () { | |||
const { multiColumn, showSearch, intl } = this.props; | |||
@@ -82,8 +91,8 @@ export default class Compose extends React.PureComponent { | |||
<SearchContainer /> | |||
<div className='drawer__pager'> | |||
<div className='drawer__inner'> | |||
<NavigationContainer /> | |||
<div className='drawer__inner' onFocus={this.onFocus}> | |||
<NavigationContainer onClose={this.onBlur} /> | |||
<ComposeFormContainer /> | |||
</div> | |||
@@ -43,6 +43,7 @@ import '../../components/status'; | |||
const mapStateToProps = state => ({ | |||
systemFontUi: state.getIn(['meta', 'system_font_ui']), | |||
isComposing: state.getIn(['compose', 'is_composing']), | |||
}); | |||
@connect(mapStateToProps) | |||
@@ -52,6 +53,7 @@ export default class UI extends React.PureComponent { | |||
dispatch: PropTypes.func.isRequired, | |||
children: PropTypes.node, | |||
systemFontUi: PropTypes.bool, | |||
isComposing: PropTypes.bool, | |||
}; | |||
state = { | |||
@@ -133,6 +135,19 @@ export default class UI extends React.PureComponent { | |||
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 () { | |||
window.removeEventListener('resize', this.handleResize); | |||
document.removeEventListener('dragenter', this.handleDragEnter); | |||
@@ -20,6 +20,7 @@ import { | |||
COMPOSE_SPOILERNESS_CHANGE, | |||
COMPOSE_SPOILER_TEXT_CHANGE, | |||
COMPOSE_VISIBILITY_CHANGE, | |||
COMPOSE_COMPOSING_CHANGE, | |||
COMPOSE_EMOJI_INSERT, | |||
} from '../actions/compose'; | |||
import { TIMELINE_DELETE } from '../actions/timelines'; | |||
@@ -37,6 +38,7 @@ const initialState = ImmutableMap({ | |||
focusDate: null, | |||
preselectDate: null, | |||
in_reply_to: null, | |||
is_composing: false, | |||
is_submitting: false, | |||
is_uploading: false, | |||
progress: 0, | |||
@@ -146,7 +148,9 @@ export default function compose(state = initialState, action) { | |||
case COMPOSE_MOUNT: | |||
return state.set('mounted', true); | |||
case COMPOSE_UNMOUNT: | |||
return state.set('mounted', false); | |||
return state | |||
.set('mounted', false) | |||
.set('is_composing', false); | |||
case COMPOSE_SENSITIVITY_CHANGE: | |||
return state | |||
.set('sensitive', !state.get('sensitive')) | |||
@@ -169,6 +173,8 @@ export default function compose(state = initialState, action) { | |||
return state | |||
.set('text', action.text) | |||
.set('idempotencyKey', uuid()); | |||
case COMPOSE_COMPOSING_CHANGE: | |||
return state.set('is_composing', action.value); | |||
case COMPOSE_REPLY: | |||
return state.withMutations(map => { | |||
map.set('in_reply_to', action.status.get('id')); | |||
@@ -1177,6 +1177,11 @@ | |||
.permalink { | |||
text-decoration: none; | |||
} | |||
.icon-button { | |||
pointer-events: none; | |||
opacity: 0; | |||
} | |||
} | |||
.navigation-bar__profile { | |||
@@ -3723,3 +3728,66 @@ noscript { | |||
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; | |||
} | |||
} | |||
} | |||
} |