summaryrefslogtreecommitdiffhomepage
path: root/app
diff options
context:
space:
mode:
authoranderklander <anderklander@gmail.com>2018-02-21 14:46:07 +0100
committerAndrej Mihajlov <and@mullvad.net>2018-02-26 16:15:52 +0100
commit608029ca1c403101a88ec45d52e44dc1f22e434f (patch)
treef2f7bb91c19f6d01d259e785242dbd967bc6392f /app
parent47a78129cd2f953fe4dca33a04c138ac2093a52e (diff)
downloadmullvadvpn-608029ca1c403101a88ec45d52e44dc1f22e434f.tar.xz
mullvadvpn-608029ca1c403101a88ec45d52e44dc1f22e434f.zip
Add TransitionContainer using ReactXP animations
Diffstat (limited to 'app')
-rw-r--r--app/assets/css/style.css1
-rw-r--r--app/assets/css/transitions.css91
-rw-r--r--app/components/TransitionContainer.js201
-rw-r--r--app/routes.js6
-rw-r--r--app/transitions.js48
5 files changed, 222 insertions, 125 deletions
diff --git a/app/assets/css/style.css b/app/assets/css/style.css
index 5021d7d1a9..7d667f67e3 100644
--- a/app/assets/css/style.css
+++ b/app/assets/css/style.css
@@ -3,7 +3,6 @@
@import 'fonts.css';
@import 'global.css';
@import 'buttons.css';
-@import 'transitions.css';
/* app */
@import '../../components/PlatformWindow.css';
diff --git a/app/assets/css/transitions.css b/app/assets/css/transitions.css
deleted file mode 100644
index 239be5b63b..0000000000
--- a/app/assets/css/transitions.css
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * CSS rules for transitions using React-router and CSSTransitionGroup
- */
-
-div[class*="-transition-leave"], div[class*="-transition-enter"] {
- /* keep animated .layout divs pinned to viewport boundaries */
- position: absolute;
- top: 0;
- left: 0;
- width: 100vw;
-
- /* disable UI interaction during transitions */
- pointer-events: none;
-}
-
-.transition-container {
- position: relative;
- width: 100vw;
- height: 100vh;
- overflow: hidden;
-}
-
-/* New view slides bottom top */
-
-.slide-up-transition-leave { z-index: 0; }
-.slide-up-transition-enter {
- transform: translateY(100vh);
- z-index: 1;
-}
-
-.slide-up-transition-enter.slide-up-transition-enter-active {
- transform: translateY(0);
- transition: transform 450ms ease;
-}
-
-/* New view slides top bottom */
-
-.slide-down-transition-enter { z-index: 0; }
-.slide-down-transition-leave {
- transform: translateY(0);
- z-index: 1;
-}
-
-.slide-down-transition-leave.slide-down-transition-leave-active {
- transform: translateY(100vh);
- transition: transform 450ms ease;
-}
-
-/* New view slids right to left */
-
-.push-transition-leave {
- transform: translateX(0vw);
- z-index: 0;
-}
-
-.push-transition-enter {
- transform: translateX(100vw);
- z-index: 1;
-}
-
-.push-transition-leave.push-transition-leave-active {
- transform: translateX(-50vw);
- transition: transform 450ms ease;
-}
-
-.push-transition-enter.push-transition-enter-active {
- transform: translateX(0);
- transition: transform 450ms ease;
-}
-
-/* New view slides left to right */
-
-.pop-transition-enter {
- transform: translateX(-50vw);
- z-index: 0;
-}
-
-.pop-transition-leave {
- transform: translateX(0);
- z-index: 1;
-}
-
-.pop-transition-enter.pop-transition-enter-active {
- transform: translateX(0vw);
- transition: transform 450ms ease;
-}
-
-.pop-transition-leave.pop-transition-leave-active {
- transform: translateX(100vw);
- transition: transform 450ms ease;
-} \ No newline at end of file
diff --git a/app/components/TransitionContainer.js b/app/components/TransitionContainer.js
new file mode 100644
index 0000000000..c4a011a978
--- /dev/null
+++ b/app/components/TransitionContainer.js
@@ -0,0 +1,201 @@
+// @flow
+import * as React from 'react';
+import { Styles, Component, Animated, View, Types, UserInterface } from 'reactxp';
+import type { TransitionGroupProps } from '../transitions';
+
+type TransitionContainerProps = {
+ children: React.Node,
+ ...TransitionGroupProps
+};
+
+type State = {
+ previousChildren: ?React.Node,
+ childrenAnimation: Types.AnimatedViewStyleRuleSet,
+ previousChildrenAnimation: Types.AnimatedViewStyleRuleSet,
+ animationStyles: Types.AnimatedViewStyleRuleSet,
+ dimensions: Types.Dimensions,
+};
+
+export default class TransitionContainer extends Component<TransitionContainerProps, State> {
+
+ constructor(props: TransitionContainerProps) {
+ super(props);
+
+ const dimensions = UserInterface.measureWindow();
+
+ this.state = {
+ dimensions,
+ animationStyles: {
+ style: Styles.createAnimatedViewStyle({
+ position: 'absolute',
+ width: dimensions.width,
+ height: dimensions.height,
+ }),
+ allowPointerEvents: Styles.createAnimatedViewStyle({
+ pointerEvents: 'auto',
+ })
+ }
+ };
+ }
+
+ componentWillReceiveProps(nextProps: TransitionContainerProps) {
+ switch (nextProps.name){
+ case 'slide-up':
+ this.slideUpTransition(nextProps);
+ break;
+ case 'slide-down':
+ this.slideDownTransition(nextProps);
+ break;
+ case 'push':
+ this.pushTransition(nextProps);
+ break;
+ case 'pop':
+ this.popTransition(nextProps);
+ break;
+ default:
+ break;
+ }
+ }
+
+ onFinishedAnimation() {
+ this.setState({
+ childrenAnimation: this.state.animationStyles.allowPointerEvents,
+ previousChildren: null
+ });
+ }
+
+ slideUpTransition(nextProps: TransitionContainerProps) {
+ const currentTranslationValue = Animated.createValue(this.state.dimensions.height);
+ this.setState({
+ previousChildren: this.props.children,
+ childrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 1,
+ transform: [{ translateY: currentTranslationValue }]
+ }),
+ previousChildrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 0,
+ transform: [{ translateY: 0 }]
+ }),
+ }, () => {
+ Animated.timing(currentTranslationValue, {
+ toValue: 0,
+ easing: Animated.Easing.InOut(),
+ duration: nextProps.duration,
+ useNativeDriver: true,
+ }).start(() => this.onFinishedAnimation());
+ });
+ }
+
+ slideDownTransition(nextProps: TransitionContainerProps) {
+ const previousTranslationValue = Animated.createValue(0);
+ this.setState({
+ previousChildren: this.props.children,
+ childrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 0,
+ transform: [{ translateY: 0 }]
+ }),
+ previousChildrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 1,
+ transform: [{ translateY: previousTranslationValue }]
+ }),
+ }, () => {
+ Animated.timing(previousTranslationValue, {
+ toValue: this.state.dimensions.height,
+ easing: Animated.Easing.InOut(),
+ duration: nextProps.duration,
+ useNativeDriver: true,
+ }).start(() => this.onFinishedAnimation());
+ });
+ }
+
+ pushTransition(nextProps: TransitionContainerProps) {
+ const currentTranslationValue = Animated.createValue(this.state.dimensions.width);
+ const previousTranslationValue = Animated.createValue(0);
+ this.setState({
+ previousChildren: this.props.children,
+ childrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 1,
+ transform: [{ translateX: currentTranslationValue }]
+ }),
+ previousChildrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 0,
+ transform: [{ translateX: previousTranslationValue }]
+ }),
+ }, () => {
+ const compositeAnimation = Animated.parallel([
+ Animated.timing(currentTranslationValue, {
+ toValue: 0,
+ easing: Animated.Easing.InOut(),
+ duration: nextProps.duration,
+ useNativeDriver: true,
+ }),
+ Animated.timing(previousTranslationValue, {
+ toValue: - this.state.dimensions.width / 2,
+ easing: Animated.Easing.InOut(),
+ duration: nextProps.duration,
+ useNativeDriver: true,
+ })
+ ]);
+ compositeAnimation.start(() => this.onFinishedAnimation());
+ });
+ }
+
+ popTransition(nextProps: TransitionContainerProps) {
+ const currentTranslationValue = Animated.createValue(- this.state.dimensions.width / 2 );
+ const previousTranslationValue = Animated.createValue(0);
+ this.setState({
+ previousChildren: this.props.children,
+ childrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 0,
+ transform: [{ translateX: currentTranslationValue }]
+ }),
+ previousChildrenAnimation: Styles.createAnimatedViewStyle({
+ pointerEvents: 'none',
+ zIndex: 1,
+ transform: [{ translateX: previousTranslationValue }]
+ }),
+ }, () => {
+ const compositeAnimation = Animated.parallel([
+ Animated.timing(currentTranslationValue, {
+ toValue: 0,
+ easing: Animated.Easing.InOut(),
+ duration: nextProps.duration,
+ useNativeDriver: true,
+ }),
+ Animated.timing(previousTranslationValue, {
+ toValue: this.state.dimensions.width,
+ easing: Animated.Easing.InOut(),
+ duration: nextProps.duration,
+ useNativeDriver: true,
+ })
+ ]);
+ compositeAnimation.start(() => this.onFinishedAnimation());
+ });
+ }
+
+ render() {
+ const { children } = this.props;
+ const { previousChildren, childrenAnimation, previousChildrenAnimation } = this.state;
+ return (
+ <View style={{flex:1}}>
+
+ { previousChildren &&
+ (<Animated.View style={[this.state.animationStyles.style, previousChildrenAnimation]}>
+ { previousChildren }
+ </Animated.View>) }
+
+ <Animated.View style={[this.state.animationStyles.style, childrenAnimation]}>
+ { children }
+ </Animated.View>
+
+ </View>
+ );
+ }
+} \ No newline at end of file
diff --git a/app/routes.js b/app/routes.js
index f9917f17bf..b5e1f8aa07 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -2,7 +2,7 @@
import * as React from 'react';
import { Switch, Route, Redirect } from 'react-router';
-import { CSSTransitionGroup } from 'react-transition-group';
+import TransitionContainer from './components/TransitionContainer';
import PlatformWindow from './components/PlatformWindow';
import LoginPage from './containers/LoginPage';
import ConnectPage from './containers/ConnectPage';
@@ -92,7 +92,7 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar
return (
<PlatformWindow>
- <CSSTransitionGroup component="div" className="transition-container" { ...transitionProps }>
+ <TransitionContainer { ...transitionProps }>
<Switch key={ location.key } location={ location }>
<LoginRoute exact path="/" component={ LoginPage } />
<PrivateRoute exact path="/connect" component={ ConnectPage } />
@@ -103,7 +103,7 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar
<PublicRoute exact path="/settings/support" component={ SupportPage } />
<PrivateRoute exact path="/select-location" component={ SelectLocationPage } />
</Switch>
- </CSSTransitionGroup>
+ </TransitionContainer>
</PlatformWindow>
);
}} />
diff --git a/app/transitions.js b/app/transitions.js
index 58e692f99c..3e825c2968 100644
--- a/app/transitions.js
+++ b/app/transitions.js
@@ -1,16 +1,10 @@
// @flow
-
import TransitionRule from './lib/transition-rule';
import type { TransitionFork, TransitionDescriptor } from './lib/transition-rule';
-export type CSSTransitionGroupProps = {
- transitionName: string,
- transitionEnterTimeout: number,
- transitionLeaveTimeout: number,
- transitionEnter: boolean,
- transitionLeave: boolean,
- transitionAppear?: boolean,
- transitionAppearTimeout?: number
+export type TransitionGroupProps = {
+ name: string,
+ duration: number,
};
type TransitionMap = {
@@ -23,21 +17,21 @@ type TransitionMap = {
const transitions: TransitionMap = {
slide: {
forward: {
- name: 'slide-up-transition',
+ name: 'slide-up',
duration: 450
},
backward: {
- name: 'slide-down-transition',
+ name: 'slide-down',
duration: 450
}
},
push: {
forward: {
- name: 'push-transition',
+ name: 'push',
duration: 450
},
backward: {
- name: 'pop-transition',
+ name: 'pop',
duration: 450
}
}
@@ -57,12 +51,12 @@ const transitionRules = [
];
/**
- * Calculate CSSTransitionGroup props.
+ * Calculate TransitionGroup props.
*
* @param {string} [fromRoute] - source route
* @param {string} toRoute - target route
*/
-export function getTransitionProps(fromRoute: ?string, toRoute: string): CSSTransitionGroupProps {
+export function getTransitionProps(fromRoute: ?string, toRoute: string): TransitionGroupProps {
// ignore initial transition and transition between the same routes
if(!fromRoute || fromRoute === toRoute) {
return noTransitionProps();
@@ -71,7 +65,7 @@ export function getTransitionProps(fromRoute: ?string, toRoute: string): CSSTran
for(const rule of transitionRules) {
const match = rule.match(fromRoute, toRoute);
if(match) {
- return toCSSTransitionGroupProps(match.descriptor);
+ return toTransitionGroupProps(match.descriptor);
}
}
@@ -79,30 +73,24 @@ export function getTransitionProps(fromRoute: ?string, toRoute: string): CSSTran
}
/**
- * Integrate TransitionDescriptor into CSSTransitionGroupProps
+ * Integrate TransitionDescriptor into TransitionGroupProps
* @param {TransitionDescriptor} descriptor
*/
-function toCSSTransitionGroupProps(descriptor: TransitionDescriptor): CSSTransitionGroupProps {
+function toTransitionGroupProps(descriptor: TransitionDescriptor): TransitionGroupProps {
const {name, duration} = descriptor;
return {
- transitionName: name,
- transitionEnterTimeout: duration,
- transitionLeaveTimeout: duration,
- transitionEnter: true,
- transitionLeave: true
+ name: name,
+ duration: duration,
};
}
/**
- * Returns default props with animations disabled
+ * Returns default props with no animation
*/
-function noTransitionProps(): CSSTransitionGroupProps {
+function noTransitionProps(): TransitionGroupProps {
return {
- transitionName: '',
- transitionEnterTimeout: 0,
- transitionLeaveTimeout: 0,
- transitionEnter: false,
- transitionLeave: false
+ name: '',
+ duration: 0,
};
}