From afde39040138072b65b6e4a95490546d77909876 Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Sun, 9 Jun 2019 11:29:54 +0300 Subject: [PATCH] Cover PanelTransition with types --- src/components/MeasureHeight.js | 8 +- src/components/accounts/index.js | 2 +- src/components/auth/PanelTransition.js | 160 ++++++++++++++++++++----- src/components/ui/Panel.js | 7 +- 4 files changed, 141 insertions(+), 36 deletions(-) diff --git a/src/components/MeasureHeight.js b/src/components/MeasureHeight.js index 0a0aa26..3112a23 100644 --- a/src/components/MeasureHeight.js +++ b/src/components/MeasureHeight.js @@ -10,8 +10,8 @@ import { omit, debounce } from 'functions'; * On each component update the `shouldMeasure` prop is being called and depending of * the value returned will be decided whether to call `onMeasure`. * By default `shouldMeasure` will compare the old and new values of the `state` prop. - * Both `shouldMeasure` and `state` can be used to reduce the amount of meausres, which - * will recude the count of forced reflows in browser. + * Both `shouldMeasure` and `state` can be used to reduce the amount of measures, which + * will reduce the count of forced reflows in browser. * * Usage: * */ -type ChildState = { [key: string]: any }; +type ChildState = mixed; export default class MeasureHeight extends PureComponent<{ - shouldMeasure: (prevState: any, newState: any) => bool, + shouldMeasure: (prevState: ChildState, newState: ChildState) => bool, onMeasure: (height: number) => void, state: ChildState }> { diff --git a/src/components/accounts/index.js b/src/components/accounts/index.js index d9d814c..79ee918 100644 --- a/src/components/accounts/index.js +++ b/src/components/accounts/index.js @@ -1,3 +1,3 @@ // @flow +export type { State as AccountsState, Account } from './reducer'; export { default as AccountSwitcher } from './AccountSwitcher'; -export type { Account } from './reducer'; diff --git a/src/components/auth/PanelTransition.js b/src/components/auth/PanelTransition.js index 7941473..c3d5361 100644 --- a/src/components/auth/PanelTransition.js +++ b/src/components/auth/PanelTransition.js @@ -1,3 +1,7 @@ +// @flow +import type { User } from 'components/user'; +import type { AccountsState } from 'components/accounts'; +import type { Node, Element } from 'react'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; @@ -56,7 +60,64 @@ if (process.env.NODE_ENV !== 'production') { }, {}); } -class PanelTransition extends Component { +type ValidationError = string | { + type: string, + payload: { [key: string]: any }, +}; + +type AnimationProps = {| + opacitySpring: number, + transformSpring: number, +|}; + +type AnimationContext = { + key: string, + style: AnimationProps, + data: { + Title: Node, + Body: Element, + Footer: Node, + Links: Node, + hasBackButton: bool, + } +}; + +type Props = { + // context props + auth: { + error: string | { + type: string, + payload: {[key: string]: any}, + }, + isLoading: bool, + login: string, + }, + user: User, + accounts: AccountsState, + setErrors: ({ [key: string]: ValidationError }) => void, + clearErrors: () => void, + resolve: () => void, + reject: () => void, + + + // local props + Title: Node, + Body: typeof Component, + Footer: Node, + Links: Node, + children: Node +}; + +type State = { + contextHeight: number, + panelId: string | void, + prevPanelId: string | void, + isHeightDirty: bool, + forceHeight: 1 | 0, + direction: 'X' | 'Y', +}; + +class PanelTransition extends Component { static displayName = 'PanelTransition'; static propTypes = { @@ -106,9 +167,20 @@ class PanelTransition extends Component { state = { contextHeight: 0, - panelId: this.props.Body && this.props.Body.type.panelId + panelId: this.props.Body && (this.props.Body: any).type.panelId, + isHeightDirty: false, + forceHeight: 0, + direction: 'X', + prevPanelId: undefined, }; + isHeightMeasured: bool = false; + wasAutoFocused: bool = false; + body: null | { + autoFocus: () => void, + onFormSubmit: () => void, + } = null; + timerIds = []; // this is a list of a probably running timeouts to clean on unmount getChildContext() { @@ -136,8 +208,8 @@ class PanelTransition extends Component { } componentWillReceiveProps(nextProps) { - const nextPanel = nextProps.Body && nextProps.Body.type.panelId; - const prevPanel = this.props.Body && this.props.Body.type.panelId; + const nextPanel = nextProps.Body && (nextProps.Body: any).type.panelId; + const prevPanel = this.props.Body && (this.props.Body: any).type.panelId; if (nextPanel !== prevPanel) { const direction = this.getDirection(nextPanel, prevPanel); @@ -177,7 +249,10 @@ class PanelTransition extends Component { throw new Error('Title, Body, Footer and Links are required'); } - const {panelId, hasGoBack} = Body.type; + const {panelId, hasGoBack}: { + panelId: string, + hasGoBack: bool, + } = (Body: any).type; const formHeight = this.state[`formHeight${panelId}`] || 0; @@ -255,7 +330,10 @@ class PanelTransition extends Component { onFormSubmit = () => { this.props.clearErrors(); - this.body.onFormSubmit(); + + if (this.body) { + this.body.onFormSubmit(); + } }; onFormInvalid = (errors) => this.props.setErrors(errors); @@ -271,7 +349,10 @@ class PanelTransition extends Component { * * @return {object} */ - getTransitionStyles({key}, options = {}) { + getTransitionStyles({key}, options = {}): {| + transformSpring: number, + opacitySpring: number, + |} { const {isLeave = false} = options; const {panelId, prevPanelId} = this.state; @@ -279,6 +360,11 @@ class PanelTransition extends Component { const fromRight = 1; const currentContext = contexts.find((context) => context.includes(key)); + + if (!currentContext) { + throw new Error(`Can not find settings for ${key} panel`); + } + let sign = currentContext.indexOf(panelId) > currentContext.indexOf(prevPanelId) ? fromRight : fromLeft; @@ -294,8 +380,14 @@ class PanelTransition extends Component { }; } - getDirection(next, prev) { - return contexts.find((context) => context.includes(prev)).includes(next) ? 'X' : 'Y'; + getDirection(next, prev): 'X' | 'Y' { + const context = contexts.find((context) => context.includes(prev)); + + if (!context) { + throw new Error(`Can not find context for transition ${prev} -> ${next}`); + } + + return context.includes(next) ? 'X' : 'Y'; } onUpdateHeight = (height, key) => { @@ -340,13 +432,17 @@ class PanelTransition extends Component { shouldMeasureHeight() { const errorString = Object.values(this.props.auth.error || {}) - .reduce((acc, item) => { - if (typeof item === 'string') { - return acc + item; - } + .reduce( + // $FlowFixMe + (acc, item: ValidationError) => { + if (typeof item === 'string') { + return acc + item; + } - return acc + item.type; - }, ''); + return acc + item.type; + }, + '' + ); return [ errorString, @@ -356,7 +452,7 @@ class PanelTransition extends Component { ].join(''); } - getHeader({key, style, data}) { + getHeader({key, style, data}: AnimationContext) { const {Title} = data; const {transformSpring} = style; @@ -366,7 +462,7 @@ class PanelTransition extends Component { hasBackButton = hasBackButton(this.props); } - style = { + const transitionStyle = { ...this.getDefaultTransitionStyles(key, style), opacity: 1 // reset default }; @@ -391,7 +487,7 @@ class PanelTransition extends Component { ); return ( -
+
{hasBackButton ? backButton : null}
{Title} @@ -400,19 +496,20 @@ class PanelTransition extends Component { ); } - getBody({key, style, data}) { + getBody({key, style, data}: AnimationContext) { const {Body} = data; const {transformSpring} = style; const {direction} = this.state; let transform = this.translate(transformSpring, direction); let verticalOrigin = 'top'; + if (direction === 'Y') { verticalOrigin = 'bottom'; transform = {}; } - style = { + const transitionStyle = { ...this.getDefaultTransitionStyles(key, style), top: 'auto', // reset default [verticalOrigin]: 0, @@ -422,7 +519,7 @@ class PanelTransition extends Component { return ( this.onUpdateHeight(height, key)} > @@ -435,25 +532,25 @@ class PanelTransition extends Component { ); } - getFooter({key, style, data}) { + getFooter({key, style, data}: AnimationContext) { const {Footer} = data; - style = this.getDefaultTransitionStyles(key, style); + const transitionStyle = this.getDefaultTransitionStyles(key, style); return ( -
+
{Footer}
); } - getLinks({key, style, data}) { + getLinks({key, style, data}: AnimationContext) { const {Links} = data; - style = this.getDefaultTransitionStyles(key, style); + const transitionStyle = this.getDefaultTransitionStyles(key, style); return ( -
+
{Links}
); @@ -466,7 +563,14 @@ class PanelTransition extends Component { * * @return {object} */ - getDefaultTransitionStyles(key, {opacitySpring}) { + getDefaultTransitionStyles(key: string, {opacitySpring}: $ReadOnly): {| + position: string, + top: number, + left: number, + width: string, + opacity: number, + pointerEvents: string, + |} { return { position: 'absolute', top: 0, diff --git a/src/components/ui/Panel.js b/src/components/ui/Panel.js index e250053..59c3152 100644 --- a/src/components/ui/Panel.js +++ b/src/components/ui/Panel.js @@ -1,4 +1,5 @@ // @flow +import type { Node } from 'react'; import React, { Component } from 'react'; import classNames from 'classnames'; @@ -9,9 +10,9 @@ import styles from './panel.scss'; import icons from './icons.scss'; export function Panel(props: { - title: string, - icon: string, - children: * + title?: string, + icon?: string, + children: Node, }) { let { title, icon } = props;