summaryrefslogtreecommitdiffhomepage
path: root/gui/packages/components/src
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2018-08-23 17:40:59 +0300
committerAndrej Mihajlov <and@mullvad.net>2018-08-24 15:01:50 +0300
commit3a9d8e1251562467de443b506a73d56471e84c01 (patch)
tree0cb921d1f667d071b7ba3830a420e3273397c645 /gui/packages/components/src
parentdbf0d70aa8d69d3300bc56d89b24e839654199e9 (diff)
downloadmullvadvpn-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.js141
-rw-r--r--gui/packages/components/src/Accordion.tsx139
-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';