From ea0c8a0f4a997791ed4999c0f8d8b5cec487089a Mon Sep 17 00:00:00 2001 From: Benoit Dion Date: Sun, 7 Apr 2019 19:13:31 -0400 Subject: [PATCH] Migrate app/react-native to typescript --- app/react-native/package.json | 7 +- app/react-native/src/index.js | 13 -- app/react-native/src/index.ts | 13 ++ ...solute-positioned-keyboard-aware-view.tsx} | 38 ++++-- .../OnDeviceUI/addons/{index.js => index.tsx} | 14 +-- .../OnDeviceUI/addons/{list.js => list.tsx} | 28 ++--- .../OnDeviceUI/addons/{tab.js => tab.tsx} | 15 ++- .../components/OnDeviceUI/addons/wrapper.js | 37 ------ .../components/OnDeviceUI/addons/wrapper.tsx | 33 +++++ .../OnDeviceUI/{animation.js => animation.ts} | 31 ++--- .../OnDeviceUI/{index.js => index.tsx} | 113 ++++++++---------- .../OnDeviceUI/navigation/{bar.js => bar.tsx} | 16 ++- .../navigation/{button.js => button.tsx} | 18 ++- .../navigation/{consts.js => constants.ts} | 0 .../navigation/{index.js => index.tsx} | 33 +++-- ...bility-button.js => visibility-button.tsx} | 11 +- .../OnDeviceUI/{panel.js => panel.tsx} | 14 +-- .../OnDeviceUI/{style.js => style.ts} | 6 +- .../StoryListView/{index.js => index.tsx} | 98 +++++++-------- .../StoryListView/{style.js => style.ts} | 6 +- .../src/preview/components/StoryView/index.js | 104 ---------------- .../preview/components/StoryView/index.tsx | 104 ++++++++++++++++ .../StoryView/{style.js => style.ts} | 5 +- .../src/preview/{index.js => index.tsx} | 74 +++++++----- app/react-native/tsconfig.json | 9 ++ yarn.lock | 8 ++ 26 files changed, 425 insertions(+), 423 deletions(-) delete mode 100644 app/react-native/src/index.js create mode 100644 app/react-native/src/index.ts rename app/react-native/src/preview/components/OnDeviceUI/{absolute-positioned-keyboard-aware-view.js => absolute-positioned-keyboard-aware-view.tsx} (80%) rename app/react-native/src/preview/components/OnDeviceUI/addons/{index.js => index.tsx} (75%) rename app/react-native/src/preview/components/OnDeviceUI/addons/{list.js => list.tsx} (64%) rename app/react-native/src/preview/components/OnDeviceUI/addons/{tab.js => tab.tsx} (67%) delete mode 100644 app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.js create mode 100644 app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.tsx rename app/react-native/src/preview/components/OnDeviceUI/{animation.js => animation.ts} (69%) rename app/react-native/src/preview/components/OnDeviceUI/{index.js => index.tsx} (71%) rename app/react-native/src/preview/components/OnDeviceUI/navigation/{bar.js => bar.tsx} (77%) rename app/react-native/src/preview/components/OnDeviceUI/navigation/{button.js => button.tsx} (73%) rename app/react-native/src/preview/components/OnDeviceUI/navigation/{consts.js => constants.ts} (100%) rename app/react-native/src/preview/components/OnDeviceUI/navigation/{index.js => index.tsx} (78%) rename app/react-native/src/preview/components/OnDeviceUI/navigation/{visibility-button.js => visibility-button.tsx} (76%) rename app/react-native/src/preview/components/OnDeviceUI/{panel.js => panel.tsx} (62%) rename app/react-native/src/preview/components/OnDeviceUI/{style.js => style.ts} (89%) rename app/react-native/src/preview/components/StoryListView/{index.js => index.tsx} (59%) rename app/react-native/src/preview/components/StoryListView/{style.js => style.ts} (86%) delete mode 100644 app/react-native/src/preview/components/StoryView/index.js create mode 100644 app/react-native/src/preview/components/StoryView/index.tsx rename app/react-native/src/preview/components/StoryView/{style.js => style.ts} (61%) rename app/react-native/src/preview/{index.js => index.tsx} (80%) create mode 100644 app/react-native/tsconfig.json diff --git a/app/react-native/package.json b/app/react-native/package.json index 26590e555741..d1f6c22ed701 100644 --- a/app/react-native/package.json +++ b/app/react-native/package.json @@ -18,7 +18,6 @@ }, "license": "MIT", "main": "dist/index.js", - "jsnext:main": "src/index.js", "scripts": { "prepare": "node ../../scripts/prepare.js" }, @@ -26,15 +25,15 @@ "@storybook/addons": "5.1.0-alpha.22", "@storybook/channel-websocket": "5.1.0-alpha.22", "@storybook/channels": "5.1.0-alpha.22", + "@storybook/api": "5.1.0-alpha.22", "@storybook/client-api": "5.1.0-alpha.22", "@storybook/core-events": "5.1.0-alpha.22", - "core-js": "^2.6.5", - "prop-types": "^15.7.2", "react-native-swipe-gestures": "^1.0.3", "rn-host-detect": "^1.1.5" }, "devDependencies": { - "react-native": "^0.57.8" + "react-native": "^0.57.8", + "@types/react-native": "^0.57.42" }, "peerDependencies": { "react": "*", diff --git a/app/react-native/src/index.js b/app/react-native/src/index.js deleted file mode 100644 index 7da4d7a188a9..000000000000 --- a/app/react-native/src/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import Preview from './preview'; - -const preview = new Preview(); - -export const storiesOf = preview.storiesOf.bind(preview); -export const setAddon = preview.setAddon.bind(preview); -export const addDecorator = preview.addDecorator.bind(preview); -export const addParameters = preview.addParameters.bind(preview); -export const clearDecorators = preview.clearDecorators.bind(preview); -export const configure = preview.configure.bind(preview); -export const getStorybook = preview.getStorybook.bind(preview); -export const getStorybookUI = preview.getStorybookUI.bind(preview); -export const raw = preview.raw.bind(preview); diff --git a/app/react-native/src/index.ts b/app/react-native/src/index.ts new file mode 100644 index 000000000000..5ab573d3f77e --- /dev/null +++ b/app/react-native/src/index.ts @@ -0,0 +1,13 @@ +import Preview from './preview'; + +const preview = new Preview(); + +export const storiesOf = preview.api().storiesOf.bind(preview); +export const setAddon = preview.api().setAddon.bind(preview); +export const addDecorator = preview.api().addDecorator.bind(preview); +export const addParameters = preview.api().addParameters.bind(preview); +export const clearDecorators = preview.api().clearDecorators.bind(preview); +export const configure = preview.configure; +export const getStorybook = preview.api().getStorybook.bind(preview); +export const getStorybookUI = preview.getStorybookUI; +export const raw = preview.api().raw.bind(preview); diff --git a/app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.js b/app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.tsx similarity index 80% rename from app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.js rename to app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.tsx index 454926269351..cb3468dd5e22 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.js +++ b/app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.tsx @@ -1,12 +1,31 @@ import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { Platform, Keyboard, Dimensions, View } from 'react-native'; - +import { + Platform, + Keyboard, + Dimensions, + View, + EmitterSubscription, + LayoutChangeEvent, + KeyboardEvent, +} from 'react-native'; import style from './style'; +export interface PreviewDimens { + previewWidth: number; + previewHeight: number; +} + +type Props = { + onLayout: (dimens: PreviewDimens) => void; +} & PreviewDimens; + // Android changes screen size when keyboard opens. // To avoid issues we use absolute positioned element with predefined screen size -export default class AbsolutePositionedKeyboardAwareView extends PureComponent { +export default class AbsolutePositionedKeyboardAwareView extends PureComponent { + keyboardDidShowListener: EmitterSubscription; + keyboardDidHideListener: EmitterSubscription; + keyboardOpen: boolean; + componentWillMount() { this.keyboardDidShowListener = Keyboard.addListener( 'keyboardDidShow', @@ -25,7 +44,7 @@ export default class AbsolutePositionedKeyboardAwareView extends PureComponent { Dimensions.removeEventListener('change', this.removeKeyboardOnOrientationChange); } - keyboardDidShowHandler = e => { + keyboardDidShowHandler = (e: KeyboardEvent) => { if (Platform.OS === 'android') { const { previewWidth } = this.props; // There is bug in RN android that keyboardDidShow event is called simply when you go from portrait to landscape. @@ -50,7 +69,7 @@ export default class AbsolutePositionedKeyboardAwareView extends PureComponent { } }; - onLayoutHandler = ({ nativeEvent }) => { + onLayoutHandler = ({ nativeEvent }: LayoutChangeEvent) => { if (!this.keyboardOpen) { const { width, height } = nativeEvent.layout; const { onLayout } = this.props; @@ -80,10 +99,3 @@ export default class AbsolutePositionedKeyboardAwareView extends PureComponent { ); } } - -AbsolutePositionedKeyboardAwareView.propTypes = { - children: PropTypes.node.isRequired, - previewWidth: PropTypes.number.isRequired, - previewHeight: PropTypes.number.isRequired, - onLayout: PropTypes.func.isRequired, -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/index.js b/app/react-native/src/preview/components/OnDeviceUI/addons/index.tsx similarity index 75% rename from app/react-native/src/preview/components/OnDeviceUI/addons/index.js rename to app/react-native/src/preview/components/OnDeviceUI/addons/index.tsx index d88b9910e94d..5549fc7f18fa 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/addons/index.js +++ b/app/react-native/src/preview/components/OnDeviceUI/addons/index.tsx @@ -1,22 +1,22 @@ import React, { PureComponent } from 'react'; import { View, Text } from 'react-native'; import addons from '@storybook/addons'; - import AddonsList from './list'; import AddonWrapper from './wrapper'; import style from '../style'; -export default class Addons extends PureComponent { - constructor() { - super(); - this.panels = addons.getElements('panel'); +export default class Addons extends PureComponent<{}, { addonSelected: string }> { + panels = addons.getElements('panel'); + + constructor(props: {}) { + super(props); this.state = { addonSelected: Object.keys(this.panels)[0] || null, }; } - onPressAddon = addonSelected => { + onPressAddon = (addonSelected: string) => { this.setState({ addonSelected }); }; @@ -26,7 +26,7 @@ export default class Addons extends PureComponent { if (Object.keys(this.panels).length === 0) { return ( - No onDevice addons loaded. + No addons loaded. ); } diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/list.js b/app/react-native/src/preview/components/OnDeviceUI/addons/list.tsx similarity index 64% rename from app/react-native/src/preview/components/OnDeviceUI/addons/list.js rename to app/react-native/src/preview/components/OnDeviceUI/addons/list.tsx index 3e55ed82de46..b2073e9d5c62 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/addons/list.js +++ b/app/react-native/src/preview/components/OnDeviceUI/addons/list.tsx @@ -1,7 +1,6 @@ import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { View, ScrollView, StyleSheet } from 'react-native'; - +import { Collection } from '@storybook/addons'; import Button from '../navigation/button'; const style = StyleSheet.create({ @@ -13,12 +12,18 @@ const style = StyleSheet.create({ }, }); -export default class AddonList extends PureComponent { - renderTab = (id, title) => { +export interface Props { + panels: Collection; + addonSelected: string; + onPressAddon: (id: string) => void; +} + +export default class AddonList extends PureComponent { + renderTab = (id: string, title: string) => { const { addonSelected, onPressAddon } = this.props; return ( - ); @@ -30,21 +35,10 @@ export default class AddonList extends PureComponent { return ( - + {addonKeys.map(id => this.renderTab(id, panels[id].title))} ); } } - -AddonList.propTypes = { - panels: PropTypes.objectOf( - PropTypes.shape({ - title: PropTypes.string.isRequired, - render: PropTypes.func.isRequired, - }).isRequired - ).isRequired, - onPressAddon: PropTypes.func.isRequired, - addonSelected: PropTypes.string.isRequired, -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/tab.js b/app/react-native/src/preview/components/OnDeviceUI/addons/tab.tsx similarity index 67% rename from app/react-native/src/preview/components/OnDeviceUI/addons/tab.js rename to app/react-native/src/preview/components/OnDeviceUI/addons/tab.tsx index 3d3a9d94d68e..990ae82ac0f8 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/addons/tab.js +++ b/app/react-native/src/preview/components/OnDeviceUI/addons/tab.tsx @@ -1,10 +1,15 @@ import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { TouchableOpacity, Text } from 'react-native'; import style from '../style'; -export default class Tab extends PureComponent { +export interface Props { + id: string; + title: string; + onPress: (id: string) => void; +} + +export default class Tab extends PureComponent { onPressHandler = () => { const { onPress, id } = this.props; onPress(id); @@ -19,9 +24,3 @@ export default class Tab extends PureComponent { ); } } - -Tab.propTypes = { - onPress: PropTypes.func.isRequired, - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.js b/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.js deleted file mode 100644 index 1b01c00a0c14..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.js +++ /dev/null @@ -1,37 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { View, ScrollView } from 'react-native'; - -import style from '../style'; - -export default class Wrapper extends PureComponent { - render() { - const { panels, addonSelected } = this.props; - - const addonKeys = Object.keys(panels); - - return addonKeys.map(id => { - const selected = addonSelected === id; - - return ( - - {panels[id].render({ active: selected })} - - ); - }); - } -} - -Wrapper.propTypes = { - panels: PropTypes.objectOf( - PropTypes.shape({ - title: PropTypes.string.isRequired, - render: PropTypes.func.isRequired, - }).isRequired - ).isRequired, - addonSelected: PropTypes.string, -}; - -Wrapper.defaultProps = { - addonSelected: '', -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.tsx b/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.tsx new file mode 100644 index 000000000000..a1d341074882 --- /dev/null +++ b/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.tsx @@ -0,0 +1,33 @@ +import React, { PureComponent } from 'react'; +import { View, ScrollView } from 'react-native'; +import { Collection } from '@storybook/addons'; +import style from '../style'; + +export interface Props { + panels: Collection; + addonSelected: string; +} + +export default class Wrapper extends PureComponent { + static defaultProps = { + addonSelected: '', + }; + + render() { + const { panels, addonSelected } = this.props; + + const addonKeys = Object.keys(panels); + + return addonKeys.map(id => { + const selected = addonSelected === id; + + return ( + + + {panels[id].render({ active: selected, key: id })} + + + ); + }); + } +} diff --git a/app/react-native/src/preview/components/OnDeviceUI/animation.js b/app/react-native/src/preview/components/OnDeviceUI/animation.ts similarity index 69% rename from app/react-native/src/preview/components/OnDeviceUI/animation.js rename to app/react-native/src/preview/components/OnDeviceUI/animation.ts index 90b64697650a..62d43abcce30 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/animation.js +++ b/app/react-native/src/preview/components/OnDeviceUI/animation.ts @@ -1,10 +1,11 @@ -import { NAVIGATOR, PREVIEW, ADDONS } from './navigation/consts'; +import { NAVIGATOR, PREVIEW, ADDONS } from './navigation/constants'; +import { Animated, ViewProps } from 'react-native'; const PREVIEW_SCALE = 0.3; -const panelWidth = width => width * (1 - PREVIEW_SCALE - 0.05); +const panelWidth = (width: number) => width * (1 - PREVIEW_SCALE - 0.05); -export function getNavigatorPanelPosition(animatedValue, previewWidth) { +export const getNavigatorPanelPosition = (animatedValue: Animated.Value, previewWidth: number) => { return [ { transform: [ @@ -18,9 +19,9 @@ export function getNavigatorPanelPosition(animatedValue, previewWidth) { width: panelWidth(previewWidth), }, ]; -} +}; -export function getAddonPanelPosition(animatedValue, previewWidth) { +export const getAddonPanelPosition = (animatedValue: Animated.Value, previewWidth: number) => { return [ { transform: [ @@ -34,14 +35,14 @@ export function getAddonPanelPosition(animatedValue, previewWidth) { width: panelWidth(previewWidth), }, ]; -} +}; -export function getPreviewPosition( - animatedValue, - previewWidth, - previewHeight, - slideBetweenAnimation -) { +export const getPreviewPosition = ( + animatedValue: Animated.Value, + previewWidth: number, + previewHeight: number, + slideBetweenAnimation: boolean +) => { const translateX = previewWidth / 2 - (previewWidth * PREVIEW_SCALE) / 2 - 6; const translateY = -(previewHeight / 2 - (previewHeight * PREVIEW_SCALE) / 2 - 12); @@ -61,9 +62,9 @@ export function getPreviewPosition( }, ], }; -} +}; -export function getPreviewScale(animatedValue, slideBetweenAnimation) { +export const getPreviewScale = (animatedValue: Animated.Value, slideBetweenAnimation: boolean) => { return { transform: [ { @@ -74,4 +75,4 @@ export function getPreviewScale(animatedValue, slideBetweenAnimation) { }, ], }; -} +}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/index.js b/app/react-native/src/preview/components/OnDeviceUI/index.tsx similarity index 71% rename from app/react-native/src/preview/components/OnDeviceUI/index.js rename to app/react-native/src/preview/components/OnDeviceUI/index.tsx index 969ad7156ff6..caf0e7220c21 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/index.js +++ b/app/react-native/src/preview/components/OnDeviceUI/index.tsx @@ -1,35 +1,54 @@ import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { Keyboard, KeyboardAvoidingView, Platform, Animated, TouchableOpacity } from 'react-native'; import Events from '@storybook/core-events'; - +import addons from '@storybook/addons'; +import Channel from '@storybook/channels'; import StoryListView from '../StoryListView'; import StoryView from '../StoryView'; import Addons from './addons'; import Panel from './panel'; import Navigation from './navigation'; -import AbsolutePositionedKeyboardAwareView from './absolute-positioned-keyboard-aware-view'; - -import { PREVIEW } from './navigation/consts'; - +import AbsolutePositionedKeyboardAwareView, { + PreviewDimens, +} from './absolute-positioned-keyboard-aware-view'; +import { PREVIEW } from './navigation/constants'; import { getPreviewPosition, getPreviewScale, getAddonPanelPosition, getNavigatorPanelPosition, } from './animation'; - import style from './style'; const ANIMATION_DURATION = 300; const IS_IOS = Platform.OS === 'ios'; -export default class OnDeviceUI extends PureComponent { - constructor(props) { - super(props); +interface OnDeviceUIProps { + stories: any; + url?: string; + tabOpen?: number; + isUIHidden?: boolean; + getInitialStory?: (...args: any[]) => any; + shouldDisableKeyboardAvoidingView?: boolean; + keyboardAvoidingViewVerticalOffset?: number; +} - const tabOpen = props.tabOpen || PREVIEW; +interface OnDeviceUIState { + selection: any; + storyFn: any; + tabOpen: number; + slideBetweenAnimation: boolean; + previewWidth: number; + previewHeight: number; +} + +export default class OnDeviceUI extends PureComponent { + animatedValue: Animated.Value; + channel: Channel; + constructor(props: OnDeviceUIProps) { + super(props); + const tabOpen = props.tabOpen || PREVIEW; this.state = { tabOpen, slideBetweenAnimation: false, @@ -38,34 +57,29 @@ export default class OnDeviceUI extends PureComponent { previewWidth: 0, previewHeight: 0, }; - this.animatedValue = new Animated.Value(tabOpen); - this.forceRender = this.forceUpdate.bind(this); + this.channel = addons.getChannel(); } async componentWillMount() { - const { events, getInitialStory } = this.props; - + const { getInitialStory } = this.props; if (getInitialStory) { const story = await getInitialStory(); - this.setState({ selection: story || {}, storyFn: story ? story.storyFn : null, }); } - - events.on(Events.SELECT_STORY, this.handleStoryChange); - events.on(Events.FORCE_RE_RENDER, this.forceRender); + this.channel.on(Events.SELECT_STORY, this.handleStoryChange); + this.channel.on(Events.FORCE_RE_RENDER, this.forceReRender); } componentWillUnmount() { - const { events } = this.props; - events.removeListener(Events.SELECT_STORY, this.handleStoryChange); - events.removeListener(Events.FORCE_RE_RENDER, this.forceRender); + this.channel.removeListener(Events.SELECT_STORY, this.handleStoryChange); + this.channel.removeListener(Events.FORCE_RE_RENDER, this.forceReRender); } - onLayout = ({ previewWidth, previewHeight }) => { + onLayout = ({ previewWidth, previewHeight }: PreviewDimens) => { this.setState({ previewWidth, previewHeight }); }; @@ -73,12 +87,15 @@ export default class OnDeviceUI extends PureComponent { this.handleToggleTab(PREVIEW); }; - handleStoryChange = selection => { + forceReRender = () => { + this.forceUpdate(); + }; + + handleStoryChange = (selection: any) => { const { selection: prevSelection } = this.state; if (selection.kind === prevSelection.kind && selection.story === prevSelection.story) { this.handleToggleTab(PREVIEW); } - this.setState({ selection: { kind: selection.kind, @@ -88,25 +105,21 @@ export default class OnDeviceUI extends PureComponent { }); }; - handleToggleTab = newTabOpen => { + handleToggleTab = (newTabOpen: number) => { const { tabOpen } = this.state; - if (newTabOpen === tabOpen) { return; } - Animated.timing(this.animatedValue, { toValue: newTabOpen, duration: ANIMATION_DURATION, useNativeDriver: true, }).start(); - this.setState({ tabOpen: newTabOpen, // True if swiping between navigator and addons slideBetweenAnimation: tabOpen + newTabOpen === PREVIEW, }); - // close the keyboard opened from a TextInput from story list or knobs if (newTabOpen === PREVIEW) { Keyboard.dismiss(); @@ -116,12 +129,12 @@ export default class OnDeviceUI extends PureComponent { render() { const { stories, - events, url, isUIHidden, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, } = this.props; + const { tabOpen, slideBetweenAnimation, @@ -162,14 +175,18 @@ export default class OnDeviceUI extends PureComponent { disabled={tabOpen === PREVIEW} onPress={this.handleOpenPreview} > - + @@ -178,7 +195,6 @@ export default class OnDeviceUI extends PureComponent { - void; +} + +export default class Bar extends PureComponent { render() { const { index, onPress } = this.props; return ( @@ -35,8 +38,3 @@ export default class Bar extends PureComponent { ); } } - -Bar.propTypes = { - onPress: PropTypes.func.isRequired, - index: PropTypes.number.isRequired, -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/navigation/button.js b/app/react-native/src/preview/components/OnDeviceUI/navigation/button.tsx similarity index 73% rename from app/react-native/src/preview/components/OnDeviceUI/navigation/button.js rename to app/react-native/src/preview/components/OnDeviceUI/navigation/button.tsx index 94e1c343f88c..911bd2e166c5 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/navigation/button.js +++ b/app/react-native/src/preview/components/OnDeviceUI/navigation/button.tsx @@ -1,5 +1,4 @@ import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { View, StyleSheet, Text, TouchableOpacity } from 'react-native'; const style = StyleSheet.create({ @@ -15,7 +14,13 @@ const style = StyleSheet.create({ }, }); -export default class Button extends PureComponent { +interface Props { + id: number | string; + active: boolean; + onPress: (id: number | string) => void; +} + +export default class Button extends PureComponent { onPress = () => { const { onPress, id } = this.props; onPress(id); @@ -34,7 +39,7 @@ export default class Button extends PureComponent { }, ]} > - {children.toUpperCase()} + {children} void; +} + +export default class Navigation extends PureComponent { + static defaultProps = { + initialUiVisible: true, + }; + state = { + isUIVisible: this.props.initialUiVisible, + }; handleToggleUI = () => { const { isUIVisible } = this.state; @@ -72,13 +77,3 @@ export default class Navigation extends PureComponent { ); } } - -Navigation.propTypes = { - initialUiVisible: PropTypes.bool, - tabOpen: PropTypes.number.isRequired, - onChangeTab: PropTypes.func.isRequired, -}; - -Navigation.defaultProps = { - initialUiVisible: true, -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.js b/app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.tsx similarity index 76% rename from app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.js rename to app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.tsx index b9fbf8285d2c..340c360f80b7 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.js +++ b/app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.tsx @@ -1,9 +1,12 @@ import React, { PureComponent } from 'react'; import { Text, TouchableOpacity } from 'react-native'; -import PropTypes from 'prop-types'; import style from '../style'; -export default class VisibilityButton extends PureComponent { +interface Props { + onPress: () => void; +} + +export default class VisibilityButton extends PureComponent { render() { const { onPress } = this.props; return ( @@ -19,7 +22,3 @@ export default class VisibilityButton extends PureComponent { ); } } - -VisibilityButton.propTypes = { - onPress: PropTypes.func.isRequired, -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/panel.js b/app/react-native/src/preview/components/OnDeviceUI/panel.tsx similarity index 62% rename from app/react-native/src/preview/components/OnDeviceUI/panel.js rename to app/react-native/src/preview/components/OnDeviceUI/panel.tsx index 562950d4291f..d01b2a6d0b35 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/panel.js +++ b/app/react-native/src/preview/components/OnDeviceUI/panel.tsx @@ -1,6 +1,5 @@ import React, { PureComponent } from 'react'; -import { StyleSheet, Animated } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Animated, StyleProp, ViewStyle } from 'react-native'; const style = StyleSheet.create({ panel: { @@ -13,14 +12,13 @@ const style = StyleSheet.create({ }, }); -export default class Panel extends PureComponent { +interface Props { + style: any[]; +} + +export default class Panel extends PureComponent { render() { const { children, style: propsStyle } = this.props; return {children}; } } - -Panel.propTypes = { - style: PropTypes.arrayOf(PropTypes.object).isRequired, - children: PropTypes.node.isRequired, -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/style.js b/app/react-native/src/preview/components/OnDeviceUI/style.ts similarity index 89% rename from app/react-native/src/preview/components/OnDeviceUI/style.js rename to app/react-native/src/preview/components/OnDeviceUI/style.ts index c2a673fa7c74..47dde417c99b 100644 --- a/app/react-native/src/preview/components/OnDeviceUI/style.js +++ b/app/react-native/src/preview/components/OnDeviceUI/style.ts @@ -1,4 +1,6 @@ -export default { +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ main: { flex: 1, }, @@ -44,4 +46,4 @@ export default { flex: { flex: 1, }, -}; +}); diff --git a/app/react-native/src/preview/components/StoryListView/index.js b/app/react-native/src/preview/components/StoryListView/index.tsx similarity index 59% rename from app/react-native/src/preview/components/StoryListView/index.js rename to app/react-native/src/preview/components/StoryListView/index.tsx index 18844b8243a1..8bee6465a79f 100644 --- a/app/react-native/src/preview/components/StoryListView/index.js +++ b/app/react-native/src/preview/components/StoryListView/index.tsx @@ -1,21 +1,31 @@ -import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { SectionList, Text, TextInput, TouchableOpacity, View, SafeAreaView } from 'react-native'; import Events from '@storybook/core-events'; +import addons from '@storybook/addons'; import style from './style'; -const SectionHeader = ({ title, selected }) => ( +interface SectionProps { + title: string; + selected: boolean; +} + +const SectionHeader: React.FunctionComponent = ({ + title, + selected, +}: SectionProps) => ( {title} ); -SectionHeader.propTypes = { - title: PropTypes.string.isRequired, - selected: PropTypes.bool.isRequired, -}; +interface ListItemProps { + title: string; + kind: string; + selected: boolean; + onPress: () => void; +} -const ListItem = ({ kind, title, selected, onPress }) => ( +const ListItem: React.FunctionComponent = ({ kind, title, selected, onPress }) => ( ( ); -ListItem.propTypes = { - title: PropTypes.string.isRequired, - kind: PropTypes.string.isRequired, - onPress: PropTypes.func.isRequired, - selected: PropTypes.bool.isRequired, -}; +interface Props { + stories: any; + selectedKind?: string; + selectedStory?: string; +} + +interface State { + data: any[]; + originalData: any[]; +} -export default class StoryListView extends Component { - constructor(props, ...args) { - super(props, ...args); +export default class StoryListView extends Component { + constructor(props: Props) { + super(props); this.state = { data: [], originalData: [], }; - - this.storyAddedHandler = this.handleStoryAdded.bind(this); - - props.stories.on(Events.STORY_ADDED, this.storyAddedHandler); } componentDidMount() { + const channel = addons.getChannel(); + channel.on(Events.STORY_ADDED, this.handleStoryAdded); this.handleStoryAdded(); } componentWillUnmount() { - const { stories } = this.props; - stories.removeListener(Events.STORY_ADDED, this.storyAddedHandler); + const channel = addons.getChannel(); + channel.removeListener(Events.STORY_ADDED, this.handleStoryAdded); } handleStoryAdded = () => { @@ -62,9 +74,9 @@ export default class StoryListView extends Component { if (stories) { const data = stories.dumpStoryBook().map( - section => ({ + (section: any) => ({ title: section.kind, - data: section.stories.map(story => ({ + data: section.stories.map((story: any) => ({ key: story, name: story, kind: section.kind, @@ -77,7 +89,7 @@ export default class StoryListView extends Component { } }; - handleChangeSearchText = text => { + handleChangeSearchText = (text: string) => { const query = text.trim(); const { originalData: data } = this.state; @@ -86,16 +98,16 @@ export default class StoryListView extends Component { return; } - const checkValue = value => value.toLowerCase().includes(query.toLowerCase()); + const checkValue = (value: string) => value.toLowerCase().includes(query.toLowerCase()); const filteredData = data.reduce((acc, story) => { const hasTitle = checkValue(story.title); - const hasKind = story.data.some(kind => checkValue(kind.name)); + const hasKind = story.data.some((ref: any) => checkValue(ref.name)); if (hasTitle || hasKind) { acc.push({ ...story, // in case the query matches component's title, all of its stories will be shown - data: !hasTitle ? story.data.filter(kind => checkValue(kind.name)) : story.data, + data: !hasTitle ? story.data.filter((ref: any) => checkValue(ref.name)) : story.data, }); } @@ -105,10 +117,9 @@ export default class StoryListView extends Component { this.setState({ data: filteredData }); }; - changeStory(kind, story) { - const { events } = this.props; - - events.emit(Events.SET_CURRENT_STORY, { kind, story }); + changeStory(kind: string, story: string) { + const channel = addons.getChannel(); + channel.emit(Events.SET_CURRENT_STORY, { kind, story }); } render() { @@ -137,7 +148,7 @@ export default class StoryListView extends Component { /> )} renderSectionHeader={({ section: { title } }) => ( - + )} keyExtractor={(item, index) => item + index} sections={data} @@ -147,24 +158,3 @@ export default class StoryListView extends Component { ); } } - -StoryListView.propTypes = { - stories: PropTypes.shape({ - dumpStoryBook: PropTypes.func.isRequired, - on: PropTypes.func.isRequired, - emit: PropTypes.func.isRequired, - removeListener: PropTypes.func.isRequired, - }).isRequired, - events: PropTypes.shape({ - on: PropTypes.func.isRequired, - emit: PropTypes.func.isRequired, - removeListener: PropTypes.func.isRequired, - }).isRequired, - selectedKind: PropTypes.string, - selectedStory: PropTypes.string, -}; - -StoryListView.defaultProps = { - selectedKind: null, - selectedStory: null, -}; diff --git a/app/react-native/src/preview/components/StoryListView/style.js b/app/react-native/src/preview/components/StoryListView/style.ts similarity index 86% rename from app/react-native/src/preview/components/StoryListView/style.js rename to app/react-native/src/preview/components/StoryListView/style.ts index 619543f55714..8b155f32d347 100644 --- a/app/react-native/src/preview/components/StoryListView/style.js +++ b/app/react-native/src/preview/components/StoryListView/style.ts @@ -1,4 +1,6 @@ -export default { +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ searchBar: { backgroundColor: '#eee', borderRadius: 5, @@ -33,4 +35,4 @@ export default { itemTextSelected: { fontWeight: 'bold', }, -}; +}); diff --git a/app/react-native/src/preview/components/StoryView/index.js b/app/react-native/src/preview/components/StoryView/index.js deleted file mode 100644 index e218be4961b4..000000000000 --- a/app/react-native/src/preview/components/StoryView/index.js +++ /dev/null @@ -1,104 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { View, Text } from 'react-native'; -import Events from '@storybook/core-events'; -import style from './style'; - -export default class StoryView extends Component { - constructor(props, ...args) { - super(props, ...args); - this.state = { storyFn: null, selection: {} }; - - if (props.listenToEvents) { - this.storyHandler = this.selectStory.bind(this); - this.forceRender = this.forceUpdate.bind(this); - props.events.on(Events.SELECT_STORY, this.storyHandler); - props.events.on(Events.FORCE_RE_RENDER, this.forceRender); - } - } - - componentWillUnmount() { - const { listenToEvents, events } = this.props; - - if (listenToEvents) { - events.removeListener(Events.SELECT_STORY, this.storyHandler); - events.removeListener(Events.FORCE_RE_RENDER, this.forceRender); - } - } - - selectStory = selection => { - this.setState({ storyFn: selection.storyFn, selection }); - }; - - renderHelp = () => { - const { url } = this.props; - return ( - - {url && url.length ? ( - - Please open the Storybook UI ({url}) with a web browser and select a story for preview. - - ) : ( - - Please open the Storybook UI with a web browser and select a story for preview. - - )} - - ); - }; - - renderOnDeviceUIHelp = () => ( - - Please open navigator and select a story to preview. - - ); - - render() { - const { listenToEvents } = this.props; - - if (listenToEvents) { - const { storyFn, selection } = this.state; - const { kind, story } = selection; - - return storyFn ? ( - - {storyFn()} - - ) : ( - this.renderHelp() - ); - } - - const { storyFn, selection } = this.props; - const { kind, story } = selection; - - return storyFn ? ( - - {storyFn()} - - ) : ( - this.renderOnDeviceUIHelp() - ); - } -} - -StoryView.propTypes = { - listenToEvents: PropTypes.bool, - storyFn: PropTypes.func, - selection: PropTypes.shape({ - kind: PropTypes.string, - story: PropTypes.string, - }), - events: PropTypes.shape({ - on: PropTypes.func.isRequired, - removeListener: PropTypes.func.isRequired, - }).isRequired, - url: PropTypes.string, -}; - -StoryView.defaultProps = { - url: '', - listenToEvents: false, - selection: {}, - storyFn: null, -}; diff --git a/app/react-native/src/preview/components/StoryView/index.tsx b/app/react-native/src/preview/components/StoryView/index.tsx new file mode 100644 index 000000000000..a5448c28ec3b --- /dev/null +++ b/app/react-native/src/preview/components/StoryView/index.tsx @@ -0,0 +1,104 @@ +import React, { Component } from 'react'; +import { View, Text } from 'react-native'; +import addons from '@storybook/addons'; +import Events from '@storybook/core-events'; +import style from './style'; + +interface Props { + listenToEvents: boolean; + selection?: any; + storyFn?: any; + url: string; +} + +interface State { + storyFn?: any; + selection?: any; +} + +export default class StoryView extends Component { + componentDidMount() { + if (this.props.listenToEvents) { + const channel = addons.getChannel(); + channel.on(Events.SELECT_STORY, this.selectStory); + channel.on(Events.FORCE_RE_RENDER, this.forceReRender); + } + } + + componentWillUnmount() { + const { listenToEvents } = this.props; + + if (listenToEvents) { + const channel = addons.getChannel(); + channel.removeListener(Events.SELECT_STORY, this.selectStory); + channel.removeListener(Events.FORCE_RE_RENDER, this.forceReRender); + } + } + + forceReRender = () => { + this.forceUpdate(); + }; + + selectStory = (selection: any) => { + this.setState({ storyFn: selection.storyFn, selection }); + }; + + renderHelp = () => { + const { url } = this.props; + return ( + + {url && url.length ? ( + + Please open the Storybook UI ({url}) with a web browser and select a story for preview. + + ) : ( + + Please open the Storybook UI with a web browser and select a story for preview. + + )} + + ); + }; + + renderOnDeviceUIHelp = () => ( + + Please open navigator and select a story to preview. + + ); + + render() { + const { listenToEvents } = this.props; + + if (listenToEvents) { + return this.renderListening(); + } else { + return this.renderOnDevice(); + } + } + + renderListening = () => { + const { storyFn, selection } = this.state; + const { kind, story } = selection; + + return storyFn ? ( + + {storyFn()} + + ) : ( + this.renderHelp() + ); + }; + + renderOnDevice = () => { + const { storyFn, selection } = this.props; + const { kind, story } = selection; + + return storyFn ? ( + + {storyFn()} + + ) : ( + this.renderOnDeviceUIHelp() + ); + }; +} diff --git a/app/react-native/src/preview/components/StoryView/style.js b/app/react-native/src/preview/components/StoryView/style.ts similarity index 61% rename from app/react-native/src/preview/components/StoryView/style.js rename to app/react-native/src/preview/components/StoryView/style.ts index b5fe466b9043..71bf4b25971b 100644 --- a/app/react-native/src/preview/components/StoryView/style.js +++ b/app/react-native/src/preview/components/StoryView/style.ts @@ -1,4 +1,5 @@ -export default { +import { StyleSheet } from 'react-native'; +export default StyleSheet.create({ main: { flex: 1, }, @@ -8,4 +9,4 @@ export default { alignItems: 'center', justifyContent: 'center', }, -}; +}); diff --git a/app/react-native/src/preview/index.js b/app/react-native/src/preview/index.tsx similarity index 80% rename from app/react-native/src/preview/index.js rename to app/react-native/src/preview/index.tsx index 8c4f565e6c13..67020e90f354 100644 --- a/app/react-native/src/preview/index.js +++ b/app/react-native/src/preview/index.tsx @@ -1,50 +1,64 @@ -/* eslint-disable no-underscore-dangle */ - import React from 'react'; import { AsyncStorage } from 'react-native'; +// @ts-ignore import getHost from 'rn-host-detect'; import addons from '@storybook/addons'; import Events from '@storybook/core-events'; import Channel from '@storybook/channels'; import createChannel from '@storybook/channel-websocket'; +// @ts-ignore remove when client-api is migrated to TS import { StoryStore, ClientApi } from '@storybook/client-api'; import OnDeviceUI from './components/OnDeviceUI'; import StoryView from './components/StoryView'; const STORAGE_KEY = 'lastOpenedStory'; +interface Params { + onDeviceUI: boolean; + resetStorybook: boolean; + disableWebsockets: boolean; + query: string; + host: string; + port: number; + secured: boolean; + initialSelection: any; + shouldPersistSelection: boolean; + tabOpen: number; + isUIHidden: boolean; + shouldDisableKeyboardAvoidingView: boolean; + keyboardAvoidingViewVerticalOffset: number; +} + export default class Preview { + currentStory: any; + _clientApi: ClientApi; + _stories: StoryStore; + _addons: any; + _decorators: any[]; + constructor() { this._addons = {}; this._decorators = []; this._stories = new StoryStore({}); this._clientApi = new ClientApi({ storyStore: this._stories }); - - [ - 'storiesOf', - 'setAddon', - 'addDecorator', - 'addParameters', - 'clearDecorators', - 'getStorybook', - 'raw', - ].forEach(method => { - this[method] = this._clientApi[method].bind(this._clientApi); - }); } - configure(loadStories, module) { + api = () => { + return this._clientApi; + }; + + configure = (loadStories: () => void, module: any) => { loadStories(); if (module && module.hot) { module.hot.accept(() => this._sendSetStories()); // TODO remove all global decorators on dispose } - } + }; - getStorybookUI(params = {}) { - let webUrl = null; - let channel = null; + getStorybookUI = (params: Partial = {}) => { + let webUrl: string = null; + let channel: Channel = null; const onDeviceUI = params.onDeviceUI !== false; const { initialSelection, shouldPersistSelection } = params; @@ -64,7 +78,7 @@ export default class Preview { channel = new Channel({ async: true }); } else { const host = getHost(params.host || 'localhost'); - const port = params.port !== false ? `:${params.port || 7007}` : ''; + const port = params.port ? `:${params.port || 7007}` : ''; const query = params.query || ''; const { secured } = params; @@ -104,6 +118,7 @@ export default class Preview { setInitialStory = true; } + // tslint:disable-next-line:no-this-assignment const preview = this; addons.loadAddons(this._clientApi); @@ -116,7 +131,6 @@ export default class Preview { return ( ; + return ; } }; - } + }; _sendSetStories() { const channel = addons.getChannel(); @@ -151,7 +165,7 @@ export default class Preview { channel.emit(Events.GET_CURRENT_STORY); } - _setInitialStory = async (initialSelection, shouldPersistSelection = true) => { + _setInitialStory = async (initialSelection: any, shouldPersistSelection = true) => { const story = await this._getInitialStory(initialSelection, shouldPersistSelection)(); if (story) { @@ -159,7 +173,7 @@ export default class Preview { } }; - _getInitialStory = (initialSelection, shouldPersistSelection = true) => async () => { + _getInitialStory = (initialSelection: any, shouldPersistSelection = true) => async () => { let story = null; if (initialSelection && this._checkStory(initialSelection)) { story = initialSelection; @@ -178,7 +192,7 @@ export default class Preview { const dump = this._stories.dumpStoryBook(); - const nonEmptyKind = dump.find(kind => kind.stories.length > 0); + const nonEmptyKind = dump.find((kind: any) => kind.stories.length > 0); if (nonEmptyKind) { return this._getStory({ kind: nonEmptyKind.kind, story: nonEmptyKind.stories[0] }); } @@ -186,13 +200,13 @@ export default class Preview { return null; }; - _getStory(selection) { + _getStory(selection: { kind: string; story: string }) { const { kind, story } = selection; const storyFn = this._stories.getStoryWithContext(kind, story); return { ...selection, storyFn }; } - _selectStoryEvent(selection) { + _selectStoryEvent(selection: { kind: string; story: string }) { AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(selection)); if (selection) { @@ -201,13 +215,13 @@ export default class Preview { } } - _selectStory(story) { + _selectStory(story: any) { this.currentStory = story; const channel = addons.getChannel(); channel.emit(Events.SELECT_STORY, story); } - _checkStory(selection) { + _checkStory(selection: any) { if (!selection || typeof selection !== 'object' || !selection.kind || !selection.story) { return null; } diff --git a/app/react-native/tsconfig.json b/app/react-native/tsconfig.json new file mode 100644 index 000000000000..b2a74623bd83 --- /dev/null +++ b/app/react-native/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["src/__tests__/**/*"] +} diff --git a/yarn.lock b/yarn.lock index 9199af698a51..1317ea1dbad9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2977,6 +2977,14 @@ dependencies: "@types/react" "*" +"@types/react-native@^0.57.42": + version "0.57.42" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.57.42.tgz#06ad92cd1378146402b7667de13cb7b935d194d6" + integrity sha512-Ms4RI8Oyi8HOIwlteFhgRE7TA9chP/mliLeJCzjKBOywYpile5TrXQF8lRDYzcC1zyTyoopu/u8VMlF+FS7VnA== + dependencies: + "@types/prop-types" "*" + "@types/react" "*" + "@types/react-redux@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.0.6.tgz#992271450e0d3bf61130ad9e356ad018841c7f78"