@storybook/react-native 6.5.0-rc.0 → 6.5.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/constants.d.ts +5 -0
  2. package/dist/constants.js +5 -0
  3. package/dist/hooks.d.ts +66 -0
  4. package/dist/hooks.js +110 -0
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +6 -0
  7. package/dist/preview/View.d.ts +10 -7
  8. package/dist/preview/View.js +16 -6
  9. package/dist/preview/components/OnDeviceUI/OnDeviceUI.d.ts +1 -5
  10. package/dist/preview/components/OnDeviceUI/OnDeviceUI.js +80 -50
  11. package/dist/preview/components/OnDeviceUI/Panel.d.ts +7 -5
  12. package/dist/preview/components/OnDeviceUI/Panel.js +13 -4
  13. package/dist/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.d.ts +1 -1
  14. package/dist/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.js +7 -4
  15. package/dist/preview/components/OnDeviceUI/addons/Addons.d.ts +3 -2
  16. package/dist/preview/components/OnDeviceUI/addons/Addons.js +18 -23
  17. package/dist/preview/components/OnDeviceUI/addons/AddonsSkeleton.d.ts +14 -0
  18. package/dist/preview/components/OnDeviceUI/addons/AddonsSkeleton.js +59 -0
  19. package/dist/preview/components/OnDeviceUI/addons/List.js +6 -15
  20. package/dist/preview/components/OnDeviceUI/addons/Wrapper.js +13 -14
  21. package/dist/preview/components/OnDeviceUI/animation.d.ts +35 -12
  22. package/dist/preview/components/OnDeviceUI/animation.js +56 -25
  23. package/dist/preview/components/OnDeviceUI/navigation/Navigation.d.ts +5 -5
  24. package/dist/preview/components/OnDeviceUI/navigation/Navigation.js +24 -10
  25. package/dist/preview/components/OnDeviceUI/navigation/NavigationBar.d.ts +8 -0
  26. package/dist/preview/components/OnDeviceUI/navigation/NavigationBar.js +14 -0
  27. package/dist/preview/components/OnDeviceUI/navigation/NavigationButton.d.ts +3 -0
  28. package/dist/preview/components/OnDeviceUI/navigation/NavigationButton.js +20 -0
  29. package/dist/preview/components/OnDeviceUI/navigation/constants.d.ts +2 -2
  30. package/dist/preview/components/OnDeviceUI/navigation/constants.js +2 -2
  31. package/dist/preview/components/Shared/icons.d.ts +35 -5
  32. package/dist/preview/components/Shared/icons.js +45 -19
  33. package/dist/preview/components/Shared/layout.d.ts +12 -0
  34. package/dist/preview/components/Shared/layout.js +27 -0
  35. package/dist/preview/components/Shared/tabs.d.ts +20 -0
  36. package/dist/preview/components/Shared/tabs.js +35 -0
  37. package/dist/preview/components/Shared/theme.d.ts +187 -11
  38. package/dist/preview/components/Shared/theme.js +316 -11
  39. package/dist/preview/components/StoryListView/StoryListView.d.ts +3 -6
  40. package/dist/preview/components/StoryListView/StoryListView.js +88 -51
  41. package/dist/preview/components/StoryView/StoryView.d.ts +3 -8
  42. package/dist/preview/components/StoryView/StoryView.js +25 -16
  43. package/dist/preview/start.d.ts +2 -2
  44. package/dist/preview/start.js +4 -4
  45. package/dist/types/emotion-native.d.js +4 -0
  46. package/dist/types/types-6.0.d.ts +10 -10
  47. package/dist/types/types.d.ts +3 -3
  48. package/package.json +7 -6
  49. package/readme.md +146 -168
  50. package/scripts/__snapshots__/loader.test.js.snap +77 -28
  51. package/scripts/loader.js +34 -16
  52. package/scripts/loader.test.js +10 -0
  53. package/scripts/mocks/configuration-objects/components/FakeComponent.tsx +1 -0
  54. package/scripts/mocks/configuration-objects/components/FakeStory.stories.tsx +9 -0
  55. package/scripts/mocks/configuration-objects/main.js +15 -0
  56. package/scripts/mocks/configuration-objects/preview.js +24 -0
  57. package/dist/preview/components/OnDeviceUI/navigation/Bar.d.ts +0 -9
  58. package/dist/preview/components/OnDeviceUI/navigation/Bar.js +0 -17
  59. package/dist/preview/components/OnDeviceUI/navigation/Button.d.ts +0 -10
  60. package/dist/preview/components/OnDeviceUI/navigation/Button.js +0 -17
  61. package/dist/preview/components/OnDeviceUI/navigation/VisibilityButton.d.ts +0 -8
  62. package/dist/preview/components/OnDeviceUI/navigation/VisibilityButton.js +0 -45
  63. package/dist/preview/components/Shared/text.d.ts +0 -3
  64. package/dist/preview/components/Shared/text.js +0 -12
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Duration for a typical transition animation, such as rescaling the canvas
3
+ * UI.
4
+ */
5
+ export declare const ANIMATION_DURATION_TRANSITION = 400;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Duration for a typical transition animation, such as rescaling the canvas
3
+ * UI.
4
+ */
5
+ export const ANIMATION_DURATION_TRANSITION = 400;
@@ -0,0 +1,66 @@
1
+ import type { StoryContext } from '@storybook/csf';
2
+ import type { Theme } from './preview/components/Shared/theme';
3
+ import type { ReactNativeFramework } from './types/types-6.0';
4
+ /**
5
+ * Hook that returns a function to set the current story context.
6
+ */
7
+ export declare function useSetStoryContext(): (args_0: StoryContext<ReactNativeFramework, import("@storybook/csf").Args> | ((prev: StoryContext<ReactNativeFramework, import("@storybook/csf").Args>) => StoryContext<ReactNativeFramework, import("@storybook/csf").Args>)) => void;
8
+ /**
9
+ * Hook to read the current story context.
10
+ */
11
+ export declare function useStoryContext(): StoryContext<ReactNativeFramework, import("@storybook/csf").Args>;
12
+ /**
13
+ * Hook that reads the value of a specific story context parameter.
14
+ */
15
+ export declare function useStoryContextParam<T = any>(name: string, defaultValue?: T): T;
16
+ /**
17
+ * Hook that indicates if `storyId` is the currently selected story.
18
+ */
19
+ export declare function useIsStorySelected(storyId: string): boolean;
20
+ /**
21
+ * Hook that indicates if `title` is the currently selected story section.
22
+ */
23
+ export declare function useIsStorySectionSelected(title: string): boolean;
24
+ /**
25
+ * Hook that causes a re-render when the currently selected story is changed.
26
+ */
27
+ export declare function useUpdateOnStoryChanged(): void;
28
+ /**
29
+ * Hook that gets the current theme values.
30
+ */
31
+ export declare function useTheme(): Theme;
32
+ /**
33
+ * A boolean atom creator for an atom that can only be toggled between
34
+ * true/false.
35
+ *
36
+ * @see {@link https://jotai.org/docs/recipes/atom-creators#atomwithtoggle}
37
+ */
38
+ export declare function atomWithToggle(initialValue?: boolean): import("jotai").WritableAtom<boolean, [nextValue?: boolean], void> & {
39
+ init: boolean;
40
+ };
41
+ /**
42
+ * Hook that retrieves the current state, and a setter, for the `isUIVisible`
43
+ * atom.
44
+ */
45
+ export declare function useIsUIVisible(): [boolean, (nextValue?: boolean) => void];
46
+ /**
47
+ * Hook that retrieves the current state, and a setter, for the
48
+ * `isSplitPanelVisibleAtom` atom.
49
+ */
50
+ export declare function useIsSplitPanelVisible(): [boolean, (nextValue?: boolean) => void];
51
+ interface SyncExternalUIParams {
52
+ isUIVisible?: boolean;
53
+ isSplitPanelVisible?: boolean;
54
+ }
55
+ /**
56
+ * Sync the UI atom states with external values, such as from Story parameters.
57
+ */
58
+ export declare function syncExternalUI({ isUIVisible, isSplitPanelVisible }: SyncExternalUIParams): void;
59
+ /**
60
+ * Hook that manages the state for the currently selected addon.
61
+ *
62
+ * This value persists across stories, so that the same addon will be selected
63
+ * when switching stories.
64
+ */
65
+ export declare function useSelectedAddon(initialValue?: string): [string, (args_0: string | ((prev: string) => string)) => void];
66
+ export {};
package/dist/hooks.js ADDED
@@ -0,0 +1,110 @@
1
+ import React, { useMemo } from 'react';
2
+ import { atom, useAtom, useAtomValue, useSetAtom, getDefaultStore } from 'jotai';
3
+ import { useTheme as useEmotionTheme } from 'emotion-theming';
4
+ const storyContextAtom = atom(null);
5
+ /**
6
+ * Hook that returns a function to set the current story context.
7
+ */
8
+ export function useSetStoryContext() {
9
+ return useSetAtom(storyContextAtom);
10
+ }
11
+ /**
12
+ * Hook to read the current story context.
13
+ */
14
+ export function useStoryContext() {
15
+ return useAtomValue(storyContextAtom);
16
+ }
17
+ /**
18
+ * Hook that reads the value of a specific story context parameter.
19
+ */
20
+ export function useStoryContextParam(name, defaultValue) {
21
+ var _a;
22
+ const paramAtom = useMemo(() => atom((get) => { var _a, _b; return (_b = (_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.parameters) === null || _b === void 0 ? void 0 : _b[name]; }), [name]);
23
+ return (_a = useAtomValue(paramAtom)) !== null && _a !== void 0 ? _a : defaultValue;
24
+ }
25
+ /**
26
+ * Hook that indicates if `storyId` is the currently selected story.
27
+ */
28
+ export function useIsStorySelected(storyId) {
29
+ return useAtomValue(useMemo(() => atom((get) => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.id) === storyId; }), [storyId]));
30
+ }
31
+ /**
32
+ * Hook that indicates if `title` is the currently selected story section.
33
+ */
34
+ export function useIsStorySectionSelected(title) {
35
+ return useAtomValue(useMemo(() => atom((get) => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.title) === title; }), [title]));
36
+ }
37
+ /**
38
+ * Hook that causes a re-render when the currently selected story is changed.
39
+ */
40
+ export function useUpdateOnStoryChanged() {
41
+ useAtomValue(useMemo(() => atom((get) => { var _a; return (_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.id; }), []));
42
+ }
43
+ /**
44
+ * Hook that gets the current theme values.
45
+ */
46
+ export function useTheme() {
47
+ return useEmotionTheme();
48
+ }
49
+ /**
50
+ * A boolean atom creator for an atom that can only be toggled between
51
+ * true/false.
52
+ *
53
+ * @see {@link https://jotai.org/docs/recipes/atom-creators#atomwithtoggle}
54
+ */
55
+ export function atomWithToggle(initialValue) {
56
+ const anAtom = atom(initialValue, (get, set, nextValue) => {
57
+ const update = nextValue !== null && nextValue !== void 0 ? nextValue : !get(anAtom);
58
+ set(anAtom, update);
59
+ });
60
+ return anAtom;
61
+ }
62
+ const isUIVisibleAtom = atomWithToggle(true);
63
+ /**
64
+ * Hook that retrieves the current state, and a setter, for the `isUIVisible`
65
+ * atom.
66
+ */
67
+ export function useIsUIVisible() {
68
+ return useAtom(isUIVisibleAtom);
69
+ }
70
+ const isSplitPanelVisibleAtom = atomWithToggle(false);
71
+ /**
72
+ * Hook that retrieves the current state, and a setter, for the
73
+ * `isSplitPanelVisibleAtom` atom.
74
+ */
75
+ export function useIsSplitPanelVisible() {
76
+ return useAtom(isSplitPanelVisibleAtom);
77
+ }
78
+ /**
79
+ * Sync the UI atom states with external values, such as from Story parameters.
80
+ */
81
+ export function syncExternalUI({ isUIVisible, isSplitPanelVisible }) {
82
+ const jotaiStore = getDefaultStore();
83
+ if (isUIVisible !== undefined) {
84
+ jotaiStore.set(isUIVisibleAtom, isUIVisible);
85
+ }
86
+ if (isSplitPanelVisible !== undefined) {
87
+ jotaiStore.set(isSplitPanelVisibleAtom, isSplitPanelVisible);
88
+ }
89
+ }
90
+ const selectedAddonAtom = atom(undefined);
91
+ /**
92
+ * Hook that manages the state for the currently selected addon.
93
+ *
94
+ * This value persists across stories, so that the same addon will be selected
95
+ * when switching stories.
96
+ */
97
+ export function useSelectedAddon(initialValue) {
98
+ const result = useAtom(selectedAddonAtom);
99
+ const set = result[1];
100
+ React.useEffect(() => {
101
+ const jotaiStore = getDefaultStore();
102
+ // Only apply the initial value once, and only if the atom doesn't have a
103
+ // value yet.
104
+ if (jotaiStore.get(selectedAddonAtom) === undefined) {
105
+ set(initialValue);
106
+ }
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, []);
109
+ return result;
110
+ }
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import { ReactNode } from 'react';
6
6
  import type { ReactNativeFramework } from './types/types-6.0';
7
7
  declare const configure: (loadable: import("@storybook/core-client").Loadable, m: NodeModule) => void;
8
8
  export { configure };
9
- declare type C = ClientApi<ReactNativeFramework>;
9
+ type C = ClientApi<ReactNativeFramework>;
10
10
  export declare const setAddon: C['setAddon'];
11
11
  export declare const addDecorator: C['addDecorator'];
12
12
  export declare const addParameters: C['addParameters'];
@@ -18,3 +18,4 @@ export declare const raw: C['raw'];
18
18
  export declare const storiesOf: (kind: string, _module: NodeModule) => StoryApi<ReactNode>;
19
19
  export declare const getStorybookUI: (params?: Partial<import("./preview/View").Params>) => () => JSX.Element;
20
20
  export * from './types/types-6.0';
21
+ export { theme, darkTheme } from './preview/components/Shared/theme';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ var _a;
1
2
  import { start } from './preview/start';
2
3
  const { clientApi, configure, view } = start();
3
4
  export { configure };
@@ -15,3 +16,8 @@ export const storiesOf = (kind, _module) => rawStoriesOf(kind, { hot: () => { }
15
16
  });
16
17
  export const getStorybookUI = view.getStorybookUI;
17
18
  export * from './types/types-6.0';
19
+ // @storybook/addon-storyshots v6 needs global.__STORYBOOK_STORY_STORE__.initializationPromise
20
+ global.__STORYBOOK_STORY_STORE__ = {
21
+ initializationPromise: (_a = clientApi.storyStore) === null || _a === void 0 ? void 0 : _a.initializationPromise,
22
+ };
23
+ export { theme, darkTheme } from './preview/components/Shared/theme';
@@ -1,12 +1,12 @@
1
1
  /// <reference types="react" />
2
2
  import { StoryIndex, SelectionSpecifier } from '@storybook/store';
3
3
  import { StoryContext } from '@storybook/csf';
4
- import { theme } from './components/Shared/theme';
4
+ import { Theme } from './components/Shared/theme';
5
5
  import type { ReactNativeFramework } from '../types/types-6.0';
6
6
  import { PreviewWeb } from '@storybook/preview-web';
7
- declare type StoryKind = string;
8
- declare type StoryName = string;
9
- declare type InitialSelection = `${StoryKind}--${StoryName}` | {
7
+ type StoryKind = string;
8
+ type StoryName = string;
9
+ type InitialSelection = `${StoryKind}--${StoryName}` | {
10
10
  /**
11
11
  * Kind is the default export name or the storiesOf("name") name
12
12
  */
@@ -16,7 +16,10 @@ declare type InitialSelection = `${StoryKind}--${StoryName}` | {
16
16
  */
17
17
  name: StoryName;
18
18
  };
19
- export declare type Params = {
19
+ type DeepPartial<T> = T extends object ? {
20
+ [P in keyof T]?: DeepPartial<T[P]>;
21
+ } : T;
22
+ export type Params = {
20
23
  onDeviceUI?: boolean;
21
24
  enableWebsockets?: boolean;
22
25
  query?: string;
@@ -27,10 +30,10 @@ export declare type Params = {
27
30
  shouldPersistSelection?: boolean;
28
31
  tabOpen?: number;
29
32
  isUIHidden?: boolean;
33
+ isSplitPanelVisible?: boolean;
30
34
  shouldDisableKeyboardAvoidingView?: boolean;
31
35
  keyboardAvoidingViewVerticalOffset?: number;
32
- } & {
33
- theme?: typeof theme;
36
+ theme: DeepPartial<Theme>;
34
37
  };
35
38
  export declare class View {
36
39
  _storyIndex: StoryIndex;
@@ -7,18 +7,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import React, { useEffect, useState, useReducer } from 'react';
10
+ import React, { useEffect, useReducer } from 'react';
11
11
  import AsyncStorage from '@react-native-async-storage/async-storage';
12
12
  import { toId } from '@storybook/csf';
13
13
  import { addons } from '@storybook/addons';
14
14
  import { ThemeProvider } from 'emotion-theming';
15
15
  import { SafeAreaProvider } from 'react-native-safe-area-context';
16
+ import { useSetStoryContext, syncExternalUI } from '../hooks';
16
17
  import OnDeviceUI from './components/OnDeviceUI';
17
- import { theme } from './components/Shared/theme';
18
+ import { darkTheme, theme } from './components/Shared/theme';
18
19
  import StoryView from './components/StoryView';
19
20
  import createChannel from '@storybook/channel-websocket';
20
21
  import getHost from './rn-host-detect';
21
22
  import events from '@storybook/core-events';
23
+ import { Appearance } from 'react-native';
24
+ import deepmerge from 'deepmerge';
22
25
  const STORAGE_KEY = 'lastOpenedStory';
23
26
  export class View {
24
27
  constructor(preview) {
@@ -64,6 +67,7 @@ export class View {
64
67
  });
65
68
  };
66
69
  this.getStorybookUI = (params = {}) => {
70
+ var _a;
67
71
  const { shouldPersistSelection = true, onDeviceUI = true, enableWebsockets = false } = params;
68
72
  const initialStory = this._getInitialStory(params);
69
73
  if (enableWebsockets) {
@@ -86,9 +90,14 @@ export class View {
86
90
  });
87
91
  // eslint-disable-next-line consistent-this
88
92
  const self = this;
89
- const appliedTheme = Object.assign(Object.assign({}, theme), params.theme);
93
+ const appliedTheme = deepmerge(Appearance.getColorScheme() === 'dark' ? darkTheme : theme, (_a = params.theme) !== null && _a !== void 0 ? _a : {});
94
+ // Sync the Storybook parameters (external) with app UI state (internal), to initialise them.
95
+ syncExternalUI({
96
+ isUIVisible: params.isUIHidden !== undefined ? !params.isUIHidden : undefined,
97
+ isSplitPanelVisible: params.isSplitPanelVisible,
98
+ });
90
99
  return () => {
91
- const [context, setContext] = useState();
100
+ const setContext = useSetStoryContext();
92
101
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
93
102
  useEffect(() => {
94
103
  self._setStory = (newStory) => {
@@ -104,14 +113,15 @@ export class View {
104
113
  self._preview.urlStore.selectionSpecifier = story;
105
114
  self._preview.selectSpecifiedStory();
106
115
  });
116
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
117
  }, []);
108
118
  if (onDeviceUI) {
109
119
  return (React.createElement(SafeAreaProvider, null,
110
120
  React.createElement(ThemeProvider, { theme: appliedTheme },
111
- React.createElement(OnDeviceUI, { context: context, storyIndex: self._storyIndex, isUIHidden: params.isUIHidden, tabOpen: params.tabOpen, shouldDisableKeyboardAvoidingView: params.shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset: params.keyboardAvoidingViewVerticalOffset }))));
121
+ React.createElement(OnDeviceUI, { storyIndex: self._storyIndex, tabOpen: params.tabOpen, shouldDisableKeyboardAvoidingView: params.shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset: params.keyboardAvoidingViewVerticalOffset }))));
112
122
  }
113
123
  else {
114
- return React.createElement(StoryView, { context: context });
124
+ return React.createElement(StoryView, null);
115
125
  }
116
126
  };
117
127
  };
@@ -1,16 +1,12 @@
1
1
  import { StoryIndex } from '@storybook/client-api';
2
2
  import React from 'react';
3
- import { StoryContext } from '@storybook/csf';
4
- import { ReactNativeFramework } from 'src/types/types-6.0';
5
3
  export declare const IS_EXPO: boolean;
6
4
  interface OnDeviceUIProps {
7
- context: StoryContext<ReactNativeFramework>;
8
5
  storyIndex: StoryIndex;
9
6
  url?: string;
10
7
  tabOpen?: number;
11
- isUIHidden?: boolean;
12
8
  shouldDisableKeyboardAvoidingView?: boolean;
13
9
  keyboardAvoidingViewVerticalOffset?: number;
14
10
  }
15
- declare const _default: React.MemoExoticComponent<({ context, storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }: OnDeviceUIProps) => JSX.Element>;
11
+ declare const _default: React.MemoExoticComponent<({ storyIndex, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }: OnDeviceUIProps) => JSX.Element>;
16
12
  export default _default;
@@ -1,17 +1,19 @@
1
1
  import styled from '@emotion/native';
2
2
  import React, { useState, useRef } from 'react';
3
- import { Animated, Dimensions, Keyboard, KeyboardAvoidingView, Platform, SafeAreaView, TouchableOpacity, StatusBar, StyleSheet, View, } from 'react-native';
3
+ import { Animated, Dimensions, Easing, Keyboard, KeyboardAvoidingView, Platform, TouchableOpacity, StatusBar, StyleSheet, View, } from 'react-native';
4
+ import { useIsSplitPanelVisible, useIsUIVisible, useStoryContextParam, useTheme, } from '../../../hooks';
5
+ import { ANIMATION_DURATION_TRANSITION } from '../../../constants';
4
6
  import StoryListView from '../StoryListView';
5
7
  import StoryView from '../StoryView';
6
8
  import AbsolutePositionedKeyboardAwareView from './absolute-positioned-keyboard-aware-view';
7
9
  import Addons from './addons/Addons';
8
- import { getAddonPanelPosition, getNavigatorPanelPosition, getPreviewPosition, getPreviewScale, } from './animation';
10
+ import { AddonsSkeleton } from './addons/AddonsSkeleton';
11
+ import { getAddonPanelPosition, getSidebarPanelPosition, getPreviewShadowStyle, getPreviewStyle, } from './animation';
9
12
  import Navigation from './navigation';
10
- import { PREVIEW, ADDONS } from './navigation/constants';
13
+ import { CANVAS, ADDONS } from './navigation/constants';
11
14
  import Panel from './Panel';
12
15
  import { useWindowDimensions } from 'react-native';
13
16
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
14
- const ANIMATION_DURATION = 300;
15
17
  const IS_IOS = Platform.OS === 'ios';
16
18
  // @ts-ignore: Property 'Expo' does not exist on type 'Global'
17
19
  const getExpoRoot = () => global.Expo || global.__expo || global.__exponent;
@@ -19,84 +21,112 @@ export const IS_EXPO = getExpoRoot() !== undefined;
19
21
  const IS_ANDROID = Platform.OS === 'android';
20
22
  const BREAKPOINT = 1024;
21
23
  const flex = { flex: 1 };
22
- const Preview = styled.View(flex, ({ disabled, theme }) => ({
23
- borderLeftWidth: disabled ? 0 : 1,
24
- borderTopWidth: disabled ? 0 : 1,
25
- borderRightWidth: disabled ? 0 : 1,
26
- borderBottomWidth: disabled ? 0 : 1,
27
- borderColor: disabled ? 'transparent' : theme.previewBorderColor,
28
- }));
29
- const absolutePosition = {
30
- position: 'absolute',
31
- top: 0,
32
- bottom: 0,
33
- left: 0,
34
- right: 0,
35
- };
24
+ /**
25
+ * Story preview container.
26
+ */
27
+ function Preview({ animatedValue, style, children }) {
28
+ const theme = useTheme();
29
+ const containerStyle = Object.assign({ backgroundColor: theme.preview.backgroundColor }, getPreviewShadowStyle(animatedValue));
30
+ return (React.createElement(Animated.View, { style: [flex, containerStyle] },
31
+ React.createElement(View, { style: [flex, style] }, children)));
32
+ }
36
33
  const styles = StyleSheet.create({
37
34
  expoAndroidContainer: { paddingTop: StatusBar.currentHeight },
38
35
  });
39
- const OnDeviceUI = ({ context, storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }) => {
40
- var _a, _b;
41
- const [tabOpen, setTabOpen] = useState(initialTabOpen || PREVIEW);
42
- const [slideBetweenAnimation, setSlideBetweenAnimation] = useState(false);
43
- const [previewDimensions, setPreviewDimensions] = useState({
36
+ const Container = styled.View(({ theme }) => (Object.assign(Object.assign({ flex: 1, backgroundColor: theme.preview.containerBackgroundColor }, (IS_ANDROID && IS_EXPO ? styles.expoAndroidContainer : undefined)), Platform.select({ web: { overflow: 'hidden' } }))));
37
+ const OnDeviceUI = ({ storyIndex, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }) => {
38
+ const [tabOpen, setTabOpen] = useState(initialTabOpen || CANVAS);
39
+ const lastTabOpen = React.useRef(tabOpen);
40
+ const [previewDimensions, setPreviewDimensions] = useState(() => ({
44
41
  width: Dimensions.get('window').width,
45
42
  height: Dimensions.get('window').height,
46
- });
43
+ }));
47
44
  const animatedValue = useRef(new Animated.Value(tabOpen));
48
45
  const wide = useWindowDimensions().width >= BREAKPOINT;
49
46
  const insets = useSafeAreaInsets();
50
- const [isUIVisible, setIsUIVisible] = useState(isUIHidden !== undefined ? !isUIHidden : true);
51
- const handleToggleTab = (newTabOpen) => {
47
+ const handleToggleTab = React.useCallback((newTabOpen) => {
52
48
  if (newTabOpen === tabOpen) {
53
49
  return;
54
50
  }
51
+ lastTabOpen.current = tabOpen;
55
52
  Animated.timing(animatedValue.current, {
56
53
  toValue: newTabOpen,
57
- duration: ANIMATION_DURATION,
54
+ duration: ANIMATION_DURATION_TRANSITION,
55
+ easing: Easing.inOut(Easing.cubic),
58
56
  useNativeDriver: true,
59
57
  }).start();
60
58
  setTabOpen(newTabOpen);
61
- const isSwipingBetweenNavigatorAndAddons = tabOpen + newTabOpen === PREVIEW;
62
- setSlideBetweenAnimation(isSwipingBetweenNavigatorAndAddons);
63
59
  // close the keyboard opened from a TextInput from story list or knobs
64
- if (newTabOpen === PREVIEW) {
60
+ if (newTabOpen === CANVAS) {
65
61
  Keyboard.dismiss();
66
62
  }
67
- };
68
- const noSafeArea = (_b = (_a = context === null || context === void 0 ? void 0 : context.parameters) === null || _a === void 0 ? void 0 : _a.noSafeArea) !== null && _b !== void 0 ? _b : false;
63
+ }, [tabOpen]);
64
+ const noSafeArea = useStoryContextParam('noSafeArea', false);
69
65
  const previewWrapperStyles = [
70
66
  flex,
71
- getPreviewPosition({
67
+ getPreviewStyle({
72
68
  animatedValue: animatedValue.current,
73
69
  previewDimensions,
74
- slideBetweenAnimation,
75
70
  wide,
76
- noSafeArea,
77
71
  insets,
72
+ tabOpen,
73
+ lastTabOpen: lastTabOpen.current,
78
74
  }),
79
75
  ];
80
- const previewStyles = [flex, getPreviewScale(animatedValue.current, slideBetweenAnimation, wide)];
81
- const WrapperView = noSafeArea ? View : SafeAreaView;
82
- const wrapperMargin = { marginBottom: isUIVisible ? insets.bottom + 40 : 0 };
76
+ const [isUIVisible] = useIsUIVisible();
77
+ // The initial value is just a guess until the layout calculation has been done.
78
+ const [navBarHeight, setNavBarHeight] = React.useState(insets.bottom + 40);
79
+ const measureNavigation = React.useCallback(({ nativeEvent }) => {
80
+ const inset = insets.bottom;
81
+ setNavBarHeight(isUIVisible ? nativeEvent.layout.height - inset : 0);
82
+ }, [isUIVisible, insets]);
83
+ // There are 4 cases for the additional UI margin:
84
+ // 1. Storybook UI is visible, and `noSafeArea` is false: Include top and
85
+ // bottom safe area insets, and also include the navigation bar height.
86
+ //
87
+ // 2. Storybook UI is not visible, and `noSafeArea` is false: Include top
88
+ // and bottom safe area insets.
89
+ //
90
+ // 3. Storybook UI is visible, and `noSafeArea` is true: Include only the
91
+ // bottom safe area inset and the navigation bar height.
92
+ //
93
+ // 4. Storybook UI is not visible, and `noSafeArea` is true: No margin.
94
+ const safeAreaMargins = {
95
+ paddingBottom: isUIVisible ? insets.bottom + navBarHeight : noSafeArea ? 0 : insets.bottom,
96
+ paddingTop: !noSafeArea ? insets.top : 0,
97
+ overflow: 'hidden',
98
+ };
99
+ // The panels always apply the safe area, regardless of the story parameters.
100
+ const panelSafeAreaMargins = {
101
+ paddingBottom: insets.bottom + navBarHeight,
102
+ paddingTop: insets.top,
103
+ };
104
+ // Adjust the keyboard offset (possibly in a negative direction) to account
105
+ // for the safe area and navigation bar.
106
+ const keyboardVerticalOffset = -panelSafeAreaMargins.paddingBottom + (keyboardAvoidingViewVerticalOffset !== null && keyboardAvoidingViewVerticalOffset !== void 0 ? keyboardAvoidingViewVerticalOffset : 0);
107
+ const [isSplitPanelVisible] = useIsSplitPanelVisible();
108
+ const isPreviewInactive = tabOpen !== CANVAS;
83
109
  return (React.createElement(React.Fragment, null,
84
- React.createElement(View, { style: [flex, IS_ANDROID && IS_EXPO && styles.expoAndroidContainer] },
85
- React.createElement(KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView || tabOpen !== PREVIEW, behavior: IS_IOS ? 'padding' : null, keyboardVerticalOffset: keyboardAvoidingViewVerticalOffset, style: flex },
110
+ React.createElement(Container, null,
111
+ React.createElement(KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView || isPreviewInactive, behavior: IS_IOS ? 'padding' : null, keyboardVerticalOffset: keyboardVerticalOffset, style: flex },
86
112
  React.createElement(AbsolutePositionedKeyboardAwareView, { onLayout: setPreviewDimensions, previewDimensions: previewDimensions },
87
113
  React.createElement(Animated.View, { style: previewWrapperStyles },
88
- React.createElement(Animated.View, { style: previewStyles },
89
- React.createElement(Preview, { disabled: tabOpen === PREVIEW },
90
- React.createElement(WrapperView, { style: [flex, wrapperMargin] },
91
- React.createElement(StoryView, { context: context }))),
92
- tabOpen !== PREVIEW ? (React.createElement(TouchableOpacity, { style: absolutePosition, onPress: () => handleToggleTab(PREVIEW) })) : null)),
93
- React.createElement(Panel, { style: getNavigatorPanelPosition(animatedValue.current, previewDimensions.width, wide) },
94
- React.createElement(StoryListView, { storyIndex: storyIndex, selectedStoryContext: context })),
95
- React.createElement(Panel, { style: [
114
+ React.createElement(Preview, { style: safeAreaMargins, animatedValue: animatedValue.current },
115
+ React.createElement(StoryView, null),
116
+ isSplitPanelVisible ? (React.createElement(Panel, { edge: "top", style: { flex: 1 } },
117
+ React.createElement(Addons, { active: true }),
118
+ React.createElement(AddonsSkeleton, { visible: isPreviewInactive }))) : null),
119
+ isPreviewInactive ? (React.createElement(TouchableOpacity, { style: StyleSheet.absoluteFillObject, onPress: () => handleToggleTab(CANVAS) })) : null),
120
+ React.createElement(Panel, { edge: "right", style: [
121
+ getSidebarPanelPosition(animatedValue.current, previewDimensions.width, wide),
122
+ panelSafeAreaMargins,
123
+ ] },
124
+ React.createElement(StoryListView, { storyIndex: storyIndex })),
125
+ React.createElement(Panel, { edge: "left", style: [
96
126
  getAddonPanelPosition(animatedValue.current, previewDimensions.width, wide),
97
- wrapperMargin,
127
+ panelSafeAreaMargins,
98
128
  ] },
99
129
  React.createElement(Addons, { active: tabOpen === ADDONS })))),
100
- React.createElement(Navigation, { tabOpen: tabOpen, onChangeTab: handleToggleTab, isUIVisible: isUIVisible, setIsUIVisible: setIsUIVisible }))));
130
+ React.createElement(Navigation, { onLayout: measureNavigation, tabOpen: tabOpen, onChangeTab: handleToggleTab }))));
101
131
  };
102
132
  export default React.memo(OnDeviceUI);
@@ -1,7 +1,9 @@
1
- import React, { ReactNode } from 'react';
2
- interface Props {
3
- style: any[];
4
- children: ReactNode;
1
+ import React from 'react';
2
+ import { Animated, StyleProp, ViewStyle } from 'react-native';
3
+ interface PanelProps {
4
+ edge: 'left' | 'right' | 'top';
5
+ style: StyleProp<Animated.AnimatedProps<ViewStyle>>;
6
+ children: React.ReactNode;
5
7
  }
6
- declare const _default: React.MemoExoticComponent<({ children, style }: Props) => JSX.Element>;
8
+ declare const _default: React.MemoExoticComponent<({ edge, children, style }: PanelProps) => JSX.Element>;
7
9
  export default _default;
@@ -1,9 +1,18 @@
1
1
  import React from 'react';
2
2
  import { StyleSheet, Animated } from 'react-native';
3
3
  import styled from '@emotion/native';
4
- // @ts-ignore styled is being weird ;(
5
- const Container = styled(Animated.View)(({ theme }) => ({
6
- backgroundColor: theme.backgroundColor || 'white',
4
+ const Container = styled(Animated.View)(({ theme, edge }) => ({
5
+ backgroundColor: theme.panel.backgroundColor,
6
+ borderTopWidth: edge === 'top' ? theme.panel.borderWidth : undefined,
7
+ borderStartWidth: edge === 'left' ? theme.panel.borderWidth : undefined,
8
+ borderEndWidth: edge === 'right' ? theme.panel.borderWidth : undefined,
9
+ borderColor: theme.panel.borderColor,
7
10
  }));
8
- const Panel = ({ children, style }) => (React.createElement(Container, { style: [StyleSheet.absoluteFillObject, ...style] }, children));
11
+ const Panel = ({ edge, children, style }) => {
12
+ const containerStyle = StyleSheet.flatten([
13
+ edge === 'top' ? undefined : StyleSheet.absoluteFillObject,
14
+ style,
15
+ ]);
16
+ return (React.createElement(Container, { edge: edge, style: containerStyle }, children));
17
+ };
9
18
  export default React.memo(Panel);
@@ -3,7 +3,7 @@ export interface PreviewDimens {
3
3
  width: number;
4
4
  height: number;
5
5
  }
6
- declare type Props = {
6
+ type Props = {
7
7
  onLayout: (dimens: PreviewDimens) => void;
8
8
  previewDimensions: PreviewDimens;
9
9
  children: ReactNode;
@@ -4,10 +4,13 @@ import { View, StyleSheet } from 'react-native';
4
4
  // To avoid issues we use absolute positioned element with predefined screen size
5
5
  const AbsolutePositionedKeyboardAwareView = ({ onLayout, previewDimensions: { width, height }, children, }) => {
6
6
  const onLayoutHandler = ({ nativeEvent }) => {
7
- onLayout({
8
- height: nativeEvent.layout.height,
9
- width: nativeEvent.layout.width,
10
- });
7
+ const { height: layoutHeight, width: layoutWidth } = nativeEvent.layout;
8
+ if (layoutHeight !== height || layoutWidth !== width) {
9
+ onLayout({
10
+ height: layoutHeight,
11
+ width: layoutWidth,
12
+ });
13
+ }
11
14
  };
12
15
  return (React.createElement(View, { style: styles.container, onLayout: onLayoutHandler },
13
16
  React.createElement(View, { style: width === 0
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- declare const _default: React.MemoExoticComponent<({ active }: {
2
+ interface AddonsProps {
3
3
  active: boolean;
4
- }) => JSX.Element>;
4
+ }
5
+ declare const _default: React.MemoExoticComponent<({ active }: AddonsProps) => JSX.Element>;
5
6
  export default _default;