diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-08-23 17:40:59 +0300 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-08-24 15:01:50 +0300 |
| commit | 3a9d8e1251562467de443b506a73d56471e84c01 (patch) | |
| tree | 0cb921d1f667d071b7ba3830a420e3273397c645 /gui/packages/components/src | |
| parent | dbf0d70aa8d69d3300bc56d89b24e839654199e9 (diff) | |
| download | mullvadvpn-3a9d8e1251562467de443b506a73d56471e84c01.tar.xz mullvadvpn-3a9d8e1251562467de443b506a73d56471e84c01.zip | |
Migrate components to TypeScript
Diffstat (limited to 'gui/packages/components/src')
| -rw-r--r-- | gui/packages/components/src/Accordion.js | 141 | ||||
| -rw-r--r-- | gui/packages/components/src/Accordion.tsx | 139 | ||||
| -rw-r--r-- | gui/packages/components/src/index.ts (renamed from gui/packages/components/src/index.js) | 2 |
3 files changed, 139 insertions, 143 deletions
diff --git a/gui/packages/components/src/Accordion.js b/gui/packages/components/src/Accordion.js deleted file mode 100644 index 6be18b00b1..0000000000 --- a/gui/packages/components/src/Accordion.js +++ /dev/null @@ -1,141 +0,0 @@ -// @flow - -import * as React from 'react'; -import { Component, View, Styles, Animated, UserInterface } from 'reactxp'; - -type Props = { - height: number | 'auto', - animationDuration?: number, - children?: React.Node, -}; - -type State = { - animatedValue: ?Animated.Value, -}; - -const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' }); - -export default class Accordion extends Component<Props, State> { - static defaultProps = { - height: 'auto', - animationDuration: 350, - }; - - state: State = { - animatedValue: null, - animation: null, - }; - - _containerView: ?React.Node; - _contentHeight = 0; - _animation = (null: ?Animated.CompositeAnimation); - - constructor(props: Props) { - super(props); - - // set the initial height if it's known - if (typeof props.height === 'number') { - this.state = { - animatedValue: Animated.createValue(props.height), - }; - } - } - - componentWillUnmount() { - if (this._animation) { - this._animation.stop(); - } - } - - shouldComponentUpdate(nextProps: Props, nextState: State) { - return ( - nextState.animatedValue !== this.state.animatedValue || - nextProps.height !== this.props.height || - nextProps.children !== this.props.children - ); - } - - componentDidUpdate(prevProps: Props, _prevState: State) { - if (prevProps.height !== this.props.height) { - this._animateHeightChanges(); - } - } - - render() { - const { - style: style, - height: _height, - children, - animationDuration: _animationDuration, - ...otherProps - } = this.props; - const containerStyles = [style]; - - if (this.state.animatedValue !== null) { - const animatedStyle = Styles.createAnimatedViewStyle({ - height: this.state.animatedValue, - }); - - containerStyles.push(containerOverflowStyle, animatedStyle); - } - - return ( - <Animated.View - {...otherProps} - style={containerStyles} - ref={(node) => (this._containerView = node)}> - <View onLayout={this._contentLayoutDidChange}>{children}</View> - </Animated.View> - ); - } - - async _animateHeightChanges() { - const containerView = this._containerView; - if (!containerView) { - return; - } - - if (this._animation) { - this._animation.stop(); - this._animation = null; - } - - try { - const layout = await UserInterface.measureLayoutRelativeToWindow(containerView); - const fromValue = this.state.animatedValue || Animated.createValue(layout.height); - const toValue = this.props.height === 'auto' ? this._contentHeight : this.props.height; - - // calculate the animation duration based on travel distance - const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this._contentHeight); - const duration = Math.ceil(this.props.animationDuration * multiplier); - - const animation = Animated.timing(fromValue, { - toValue: toValue, - easing: Animated.Easing.InOut(), - duration: duration, - useNativeDriver: true, - }); - - this._animation = animation; - this.setState({ animatedValue: fromValue }, () => { - animation.start(this._onAnimationEnd); - }); - } catch (error) { - // TODO: log error - } - } - - _onAnimationEnd = ({ finished }) => { - if (finished) { - this._animation = null; - - // reset height after transition to let element layout naturally - // if animation finished without interruption - if (this.props.height === 'auto') { - this.setState({ animatedValue: null }); - } - } - }; - - _contentLayoutDidChange = ({ height }) => (this._contentHeight = height); -} diff --git a/gui/packages/components/src/Accordion.tsx b/gui/packages/components/src/Accordion.tsx new file mode 100644 index 0000000000..370cce7e28 --- /dev/null +++ b/gui/packages/components/src/Accordion.tsx @@ -0,0 +1,139 @@ +import * as React from 'react'; +import { Animated, Component, Styles, Types, UserInterface, View } from 'reactxp'; + +interface IProps { + height: number | 'auto'; + animationDuration?: number; + style?: Types.AnimatedViewStyleRuleSet; + children?: React.ReactNode; +} + +interface IState { + animatedValue: Animated.Value | null; +} + +const containerOverflowStyle = Styles.createViewStyle({ overflow: 'hidden' }); + +export default class Accordion extends Component<IProps, IState> { + public static defaultProps = { + height: 'auto', + animationDuration: 350, + }; + + public state: IState = { + animatedValue: null, + }; + + private containerRef = React.createRef<Animated.View>(); + private contentHeight = 0; + private animation: Types.Animated.CompositeAnimation | null = null; + + constructor(props: IProps) { + super(props); + + // set the initial height if it's known + if (typeof props.height === 'number') { + this.state = { + animatedValue: Animated.createValue(props.height), + }; + } + } + + public componentWillUnmount() { + if (this.animation) { + this.animation.stop(); + } + } + + public shouldComponentUpdate(nextProps: IProps, nextState: IState) { + return ( + nextState.animatedValue !== this.state.animatedValue || + nextProps.height !== this.props.height || + nextProps.children !== this.props.children + ); + } + + public componentDidUpdate(prevProps: IProps, prevState: IState) { + if (prevProps.height !== this.props.height) { + this.animateHeightChanges(); + } + } + + public render() { + const { style, height, children, animationDuration, ...otherProps } = this.props; + const containerStyles = [style]; + + if (this.state.animatedValue !== null) { + const animatedStyle = Styles.createAnimatedViewStyle({ + height: this.state.animatedValue, + }); + + containerStyles.push(containerOverflowStyle, animatedStyle); + } + + return ( + <Animated.View + {...otherProps} + style={containerStyles} + ref={ + /* Fix: cast to any because reactxp has out of date annotations + See: https://github.com/Microsoft/reactxp/issues/784 + */ + this.containerRef as any + }> + <View onLayout={this.contentLayoutDidChange}>{children}</View> + </Animated.View> + ); + } + + private async animateHeightChanges() { + const containerView = this.containerRef.current; + if (!containerView) { + return; + } + + if (this.animation) { + this.animation.stop(); + this.animation = null; + } + + try { + const layout = await UserInterface.measureLayoutRelativeToWindow(containerView); + const fromValue = this.state.animatedValue || Animated.createValue(layout.height); + const toValue = this.props.height === 'auto' ? this.contentHeight : this.props.height; + + // calculate the animation duration based on travel distance + const multiplier = Math.abs(toValue - layout.height) / Math.max(1, this.contentHeight); + const duration = Math.ceil(this.props.animationDuration! * multiplier); + + const animation = Animated.timing(fromValue, { + toValue, + easing: Animated.Easing.InOut(), + duration, + useNativeDriver: true, + }); + + this.animation = animation; + this.setState({ animatedValue: fromValue }, () => { + animation.start(this.onAnimationEnd); + }); + } catch (error) { + // TODO: log error + } + } + + private onAnimationEnd = ({ finished }: Types.Animated.EndResult) => { + if (finished) { + this.animation = null; + + // reset height after transition to let element layout naturally + // if animation finished without interruption + if (this.props.height === 'auto') { + this.setState({ animatedValue: null }); + } + } + }; + + private contentLayoutDidChange = ({ height }: Types.ViewOnLayoutEvent) => + (this.contentHeight = height); +} diff --git a/gui/packages/components/src/index.js b/gui/packages/components/src/index.ts index 10cc634888..2a416a4f1d 100644 --- a/gui/packages/components/src/index.js +++ b/gui/packages/components/src/index.ts @@ -1,3 +1 @@ -// @flow - export { default as Accordion } from './Accordion'; |
