Skip to content
This repository has been archived by the owner on Oct 1, 2019. It is now read-only.

Commit

Permalink
Fix incorrect disappearing sidebar modal with RTL enabled
Browse files Browse the repository at this point in the history
Change the way login and register modals are animated, make the transitions fully configurable and predictable
  • Loading branch information
artkravchenko committed Sep 13, 2017
1 parent e187a24 commit d33f3ea
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 42 deletions.
13 changes: 3 additions & 10 deletions src/components/contextual/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import React, { PropTypes } from 'react';
import omit from 'lodash/omit';
import { browserHistory, createMemoryHistory } from 'react-router';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import TransitionGroup from 'react-transition-group/TransitionGroup';

import getRouteFor from '../../selectors/contextual-routes';
import Login from './wrappers/login';
Expand Down Expand Up @@ -117,16 +117,9 @@ export default class ContextualRoutes extends React.Component {
}

return (
<CSSTransitionGroup
component="div"
transitionName="sidebar-modal__overlay--transition"
transitionAppear={false}
transitionEnter={false}
transitionLeave
transitionLeaveTimeout={250}
>
<TransitionGroup>
{component}
</CSSTransitionGroup>
</TransitionGroup>
);
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/components/contextual/wrappers/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
import { connect } from 'react-redux';

import createSelector from '../../../selectors/createSelector';
import wrapWithTransition from '../../../utils/transition';
import { v2 as Login } from '../../login';

const mapStateToProps = createSelector(
state => state.getIn(['current_user', 'id']),
is_logged_in => ({ isVisible: !is_logged_in })
is_logged_in => ({ is_logged_in })
);

export default connect(mapStateToProps)(Login);
export default wrapWithTransition(
connect(mapStateToProps, null, null, { withRef: true })(Login)
);
7 changes: 5 additions & 2 deletions src/components/contextual/wrappers/register.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
import { connect } from 'react-redux';

import createSelector from '../../../selectors/createSelector';
import wrapWithTransition from '../../../utils/transition';
import { v2 as Register } from '../../register';

const mapStateToProps = createSelector(
state => state.getIn(['current_user', 'id']),
is_logged_in => ({ isVisible: !is_logged_in })
is_logged_in => ({ is_logged_in })
);

export default connect(mapStateToProps)(Register);
export default wrapWithTransition(
connect(mapStateToProps, null, null, { withRef: true })(Register)
);
29 changes: 19 additions & 10 deletions src/components/login/v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import isEqual from 'lodash/isEqual';
import memoize from 'memoizee';
import t from 't8on';

Expand Down Expand Up @@ -80,11 +79,12 @@ function ActionTag({ url, translate, ...props }) {

const id1Cached = memoize(x => x, { simplified: true });

class LoginComponentV2 extends React.Component {
class LoginComponentV2 extends React.PureComponent {
static displayName = 'LoginComponentV2';

static propTypes = {
dispatch: PropTypes.func,
is_logged_in: PropTypes.bool,
onSubmit: PropTypes.func,
// eslint-disable-next-line react/no-unused-prop-types
triggers: PropTypes.shape({
Expand All @@ -100,7 +100,8 @@ class LoginComponentV2 extends React.Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
isChecked: false
isChecked: false,
isVisible: true
};

this.triggers = new ActionsTrigger(
Expand All @@ -109,10 +110,12 @@ class LoginComponentV2 extends React.Component {
);
}

shouldComponentUpdate(nextProps, nextState) {
return nextProps !== this.props
|| !isEqual(nextState, this.state);
}
componentWillLeave = (callback) => {
this.setState(
state => ({ ...state, isVisible: false }),
() => setTimeout(() => callback(), 250)
);
};

handleSubmit = async (isValid, username, password) => {
this.props.dispatch(removeAllMessages());
Expand All @@ -133,6 +136,10 @@ class LoginComponentV2 extends React.Component {
};

render() {
if (this.props.is_logged_in) {
return false;
}

const { locale, messages, onClose } = this.props;

const translate = t.translateTo(locale);
Expand Down Expand Up @@ -168,10 +175,10 @@ class LoginComponentV2 extends React.Component {

return (
<div className={cn}>
<Modal.Overlay isVisible={this.props.isVisible}>
<Modal.Overlay isVisible={this.state.isVisible}>
<Modal.Main
innerClassName="form__container sidebar-form__container form__main"
isVisible={this.props.isVisible}
isVisible={this.state.isVisible}
rtl={rtl}
onCloseTo={onClose && onClose.to}
>
Expand Down Expand Up @@ -222,4 +229,6 @@ const mapStateToProps = createSelector(
({ locale, messages: messages.toList() })
);

export default connect(mapStateToProps)(LoginComponentV2);
export default connect(
mapStateToProps, null, null, { withRef: true }
)(LoginComponentV2);
29 changes: 19 additions & 10 deletions src/components/register/v2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import isEqual from 'lodash/isEqual';
import memoize from 'memoizee';
import t from 't8on';

Expand Down Expand Up @@ -50,11 +49,12 @@ const MESSAGE_CLOSE_ICON = {

const id1Cached = memoize(x => x, { simplified: true });

class RegisterComponentV2 extends React.Component {
class RegisterComponentV2 extends React.PureComponent {
static displayName = 'RegisterComponentV2';

static propTypes = {
dispatch: PropTypes.func,
is_logged_in: PropTypes.bool,
// eslint-disable-next-line react/no-unused-prop-types
triggers: PropTypes.shape({
uploadPicture: PropTypes.func,
Expand All @@ -69,7 +69,8 @@ class RegisterComponentV2 extends React.Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
isChecked: false
isChecked: false,
isVisible: true
};

this.triggers = new ActionsTrigger(
Expand All @@ -78,10 +79,12 @@ class RegisterComponentV2 extends React.Component {
);
}

shouldComponentUpdate(nextProps, nextState) {
return nextProps !== this.props
|| !isEqual(nextState, this.state);
}
componentWillLeave = (callback) => {
this.setState(
state => ({ ...state, isVisible: false }),
() => setTimeout(() => callback(), 250)
);
};

handleSubmit = async (isValid, ...args) => {
this.props.dispatch(removeAllMessages());
Expand All @@ -93,6 +96,10 @@ class RegisterComponentV2 extends React.Component {
};

render() {
if (this.props.is_logged_in) {
return false;
}

const { locale, messages, onClose } = this.props;

const translate = t.translateTo(locale);
Expand Down Expand Up @@ -132,10 +139,10 @@ class RegisterComponentV2 extends React.Component {

return (
<div className={cn}>
<Modal.Overlay isVisible={this.props.isVisible}>
<Modal.Overlay isVisible={this.state.isVisible}>
<Modal.Main
innerClassName="form__container sidebar-form__container"
isVisible={this.props.isVisible}
isVisible={this.state.isVisible}
rtl={rtl}
onCloseTo={onClose && onClose.to}
>
Expand Down Expand Up @@ -190,4 +197,6 @@ const mapStateToProps = createSelector(
({ locale, signupSucceed, messages: messages.toList() })
);

export default connect(mapStateToProps)(RegisterComponentV2);
export default connect(
mapStateToProps, null, null, { withRef: true }
)(RegisterComponentV2);
11 changes: 3 additions & 8 deletions src/less/blocks/sidebar-modal.less
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,10 @@
&-leave {
opacity: 1;

&.sidebar-modal__overlay--transition-leave-active {
&-active {
opacity: 0.01;
transition: opacity 0.25s ease-in-out;
}

.sidebar-modal__main {
transform: translateX(100%);
transition: transform 0.25s ease-in-out;
}
}
}
}
Expand Down Expand Up @@ -101,15 +96,15 @@
&--transition {
&-appear {
transform: translateX(100%);
&.sidebar-modal__main--transition-appear-active {
&-active {
transform: translateX(0%);
transition: transform 0.25s ease-in-out;
}
}

&-leave {
transform: translateX(0%);
&.sidebar-modal__main--transition-leave-active {
&-active {
transform: translateX(100%);
transition: transform 0.25s ease-in-out;
}
Expand Down
59 changes: 59 additions & 0 deletions src/utils/transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const findNearestAncestorSatifying = (doneCondition, needNext, getNext) => {
return function iterate(ancestor) {
if (!ancestor) {
return undefined;
}
if (doneCondition(ancestor)) {
return ancestor;
}
if (!needNext(ancestor)) {
return undefined;
}

return iterate(getNext(ancestor));
};
};

const TRANSITION_LIFECYCLE_HOOKS = [
'componentWillAppear', 'componentDidAppear',
'componentWillEnter', 'componentDidEnter',
'componentWillLeave', 'componentDidLeave'
];

const getAncestorTransitionLifecycleHooks = (object) => {
const component = findNearestAncestorSatifying(
ancestor => TRANSITION_LIFECYCLE_HOOKS.some(hookName => hookName in ancestor),
ancestor => 'getWrappedInstance' in ancestor,
ancestor => ancestor.getWrappedInstance()
)(object);

if (!component) {
return undefined;
}

return TRANSITION_LIFECYCLE_HOOKS.reduce(
(acc, hookName) => (acc[hookName] = component[hookName], acc), {}
);
};

/**
* Keep in mind the possibility of methods' `this` bind to change.
* Define them as arrow functions, like `componentWillEnter = (callback) => ...`
*/
export default function wrapWithTransition(WrappedComponent) {
const componentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component';

return class Transition extends WrappedComponent {
static displayName = `Transition(${componentName})`;

// Refs are first available now
componentDidMount() {
const ancestorTransitionHooks = getAncestorTransitionLifecycleHooks(this);
if (ancestorTransitionHooks) {
Object.assign(this, ancestorTransitionHooks);
}
}
};
}

0 comments on commit d33f3ea

Please sign in to comment.