@storybook/react-native 6.5.0-rc.2 → 6.5.0-rc.4
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.
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/hooks.d.ts +44 -0
- package/dist/hooks.js +80 -5
- package/dist/preview/View.d.ts +6 -3
- package/dist/preview/View.js +10 -3
- package/dist/preview/components/OnDeviceUI/OnDeviceUI.d.ts +1 -2
- package/dist/preview/components/OnDeviceUI/OnDeviceUI.js +30 -18
- package/dist/preview/components/OnDeviceUI/Panel.d.ts +7 -5
- package/dist/preview/components/OnDeviceUI/Panel.js +13 -4
- package/dist/preview/components/OnDeviceUI/addons/Addons.d.ts +3 -2
- package/dist/preview/components/OnDeviceUI/addons/Addons.js +10 -24
- package/dist/preview/components/OnDeviceUI/addons/AddonsSkeleton.d.ts +14 -0
- package/dist/preview/components/OnDeviceUI/addons/AddonsSkeleton.js +59 -0
- package/dist/preview/components/OnDeviceUI/addons/List.js +6 -15
- package/dist/preview/components/OnDeviceUI/addons/Wrapper.js +13 -14
- package/dist/preview/components/OnDeviceUI/navigation/Navigation.d.ts +3 -5
- package/dist/preview/components/OnDeviceUI/navigation/Navigation.js +23 -9
- package/dist/preview/components/OnDeviceUI/navigation/NavigationBar.d.ts +8 -0
- package/dist/preview/components/OnDeviceUI/navigation/NavigationBar.js +14 -0
- package/dist/preview/components/OnDeviceUI/navigation/NavigationButton.d.ts +3 -0
- package/dist/preview/components/OnDeviceUI/navigation/NavigationButton.js +20 -0
- package/dist/preview/components/Shared/icons.d.ts +35 -6
- package/dist/preview/components/Shared/icons.js +44 -31
- package/dist/preview/components/Shared/layout.d.ts +12 -0
- package/dist/preview/components/Shared/layout.js +27 -0
- package/dist/preview/components/Shared/tabs.d.ts +20 -0
- package/dist/preview/components/Shared/tabs.js +35 -0
- package/dist/preview/components/Shared/theme.d.ts +183 -13
- package/dist/preview/components/Shared/theme.js +177 -13
- package/dist/preview/components/StoryListView/StoryListView.js +59 -51
- package/dist/preview/components/StoryView/StoryView.js +7 -13
- package/package.json +3 -2
- package/readme.md +2 -0
- package/dist/preview/components/OnDeviceUI/navigation/Bar.d.ts +0 -9
- package/dist/preview/components/OnDeviceUI/navigation/Bar.js +0 -17
- package/dist/preview/components/OnDeviceUI/navigation/Button.d.ts +0 -10
- package/dist/preview/components/OnDeviceUI/navigation/Button.js +0 -17
- package/dist/preview/components/OnDeviceUI/navigation/VisibilityButton.d.ts +0 -8
- package/dist/preview/components/OnDeviceUI/navigation/VisibilityButton.js +0 -45
- package/dist/preview/components/Shared/text.d.ts +0 -3
- package/dist/preview/components/Shared/text.js +0 -18
package/dist/hooks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StoryContext } from '@storybook/csf';
|
|
2
|
+
import type { Theme } from './preview/components/Shared/theme';
|
|
2
3
|
import type { ReactNativeFramework } from './types/types-6.0';
|
|
3
4
|
/**
|
|
4
5
|
* Hook that returns a function to set the current story context.
|
|
@@ -20,3 +21,46 @@ export declare function useIsStorySelected(storyId: string): boolean;
|
|
|
20
21
|
* Hook that indicates if `title` is the currently selected story section.
|
|
21
22
|
*/
|
|
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
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { atom, useAtom, useAtomValue, useSetAtom, getDefaultStore } from 'jotai';
|
|
3
|
+
import { useTheme as useEmotionTheme } from 'emotion-theming';
|
|
3
4
|
const storyContextAtom = atom(null);
|
|
4
5
|
/**
|
|
5
6
|
* Hook that returns a function to set the current story context.
|
|
@@ -18,18 +19,92 @@ export function useStoryContext() {
|
|
|
18
19
|
*/
|
|
19
20
|
export function useStoryContextParam(name, defaultValue) {
|
|
20
21
|
var _a;
|
|
21
|
-
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]);
|
|
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]);
|
|
22
23
|
return (_a = useAtomValue(paramAtom)) !== null && _a !== void 0 ? _a : defaultValue;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Hook that indicates if `storyId` is the currently selected story.
|
|
26
27
|
*/
|
|
27
28
|
export function useIsStorySelected(storyId) {
|
|
28
|
-
return useAtomValue(useMemo(() => atom(get => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.id) === storyId; }), [storyId]));
|
|
29
|
+
return useAtomValue(useMemo(() => atom((get) => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.id) === storyId; }), [storyId]));
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* Hook that indicates if `title` is the currently selected story section.
|
|
32
33
|
*/
|
|
33
34
|
export function useIsStorySectionSelected(title) {
|
|
34
|
-
return useAtomValue(useMemo(() => atom(get => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.title) === title; }), [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;
|
|
35
110
|
}
|
package/dist/preview/View.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { StoryIndex, SelectionSpecifier } from '@storybook/store';
|
|
3
3
|
import { StoryContext } from '@storybook/csf';
|
|
4
|
-
import {
|
|
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
7
|
type StoryKind = string;
|
|
@@ -16,6 +16,9 @@ type InitialSelection = `${StoryKind}--${StoryName}` | {
|
|
|
16
16
|
*/
|
|
17
17
|
name: StoryName;
|
|
18
18
|
};
|
|
19
|
+
type DeepPartial<T> = T extends object ? {
|
|
20
|
+
[P in keyof T]?: DeepPartial<T[P]>;
|
|
21
|
+
} : T;
|
|
19
22
|
export type Params = {
|
|
20
23
|
onDeviceUI?: boolean;
|
|
21
24
|
enableWebsockets?: boolean;
|
|
@@ -27,10 +30,10 @@ export 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;
|
package/dist/preview/View.js
CHANGED
|
@@ -13,13 +13,14 @@ 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 } from '../hooks';
|
|
16
|
+
import { useSetStoryContext, syncExternalUI } from '../hooks';
|
|
17
17
|
import OnDeviceUI from './components/OnDeviceUI';
|
|
18
18
|
import { theme } from './components/Shared/theme';
|
|
19
19
|
import StoryView from './components/StoryView';
|
|
20
20
|
import createChannel from '@storybook/channel-websocket';
|
|
21
21
|
import getHost from './rn-host-detect';
|
|
22
22
|
import events from '@storybook/core-events';
|
|
23
|
+
import deepmerge from 'deepmerge';
|
|
23
24
|
const STORAGE_KEY = 'lastOpenedStory';
|
|
24
25
|
export class View {
|
|
25
26
|
constructor(preview) {
|
|
@@ -65,6 +66,7 @@ export class View {
|
|
|
65
66
|
});
|
|
66
67
|
};
|
|
67
68
|
this.getStorybookUI = (params = {}) => {
|
|
69
|
+
var _a;
|
|
68
70
|
const { shouldPersistSelection = true, onDeviceUI = true, enableWebsockets = false } = params;
|
|
69
71
|
const initialStory = this._getInitialStory(params);
|
|
70
72
|
if (enableWebsockets) {
|
|
@@ -87,7 +89,12 @@ export class View {
|
|
|
87
89
|
});
|
|
88
90
|
// eslint-disable-next-line consistent-this
|
|
89
91
|
const self = this;
|
|
90
|
-
const appliedTheme =
|
|
92
|
+
const appliedTheme = deepmerge(theme, (_a = params.theme) !== null && _a !== void 0 ? _a : {});
|
|
93
|
+
// Sync the Storybook parameters (external) with app UI state (internal), to initialise them.
|
|
94
|
+
syncExternalUI({
|
|
95
|
+
isUIVisible: params.isUIHidden !== undefined ? !params.isUIHidden : undefined,
|
|
96
|
+
isSplitPanelVisible: params.isSplitPanelVisible,
|
|
97
|
+
});
|
|
91
98
|
return () => {
|
|
92
99
|
const setContext = useSetStoryContext();
|
|
93
100
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
@@ -110,7 +117,7 @@ export class View {
|
|
|
110
117
|
if (onDeviceUI) {
|
|
111
118
|
return (React.createElement(SafeAreaProvider, null,
|
|
112
119
|
React.createElement(ThemeProvider, { theme: appliedTheme },
|
|
113
|
-
React.createElement(OnDeviceUI, { storyIndex: self._storyIndex,
|
|
120
|
+
React.createElement(OnDeviceUI, { storyIndex: self._storyIndex, tabOpen: params.tabOpen, shouldDisableKeyboardAvoidingView: params.shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset: params.keyboardAvoidingViewVerticalOffset }))));
|
|
114
121
|
}
|
|
115
122
|
else {
|
|
116
123
|
return React.createElement(StoryView, null);
|
|
@@ -5,9 +5,8 @@ interface OnDeviceUIProps {
|
|
|
5
5
|
storyIndex: StoryIndex;
|
|
6
6
|
url?: string;
|
|
7
7
|
tabOpen?: number;
|
|
8
|
-
isUIHidden?: boolean;
|
|
9
8
|
shouldDisableKeyboardAvoidingView?: boolean;
|
|
10
9
|
keyboardAvoidingViewVerticalOffset?: number;
|
|
11
10
|
}
|
|
12
|
-
declare const _default: React.MemoExoticComponent<({ storyIndex,
|
|
11
|
+
declare const _default: React.MemoExoticComponent<({ storyIndex, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }: OnDeviceUIProps) => JSX.Element>;
|
|
13
12
|
export default _default;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import styled from '@emotion/native';
|
|
2
|
-
import { useTheme } from 'emotion-theming';
|
|
3
2
|
import React, { useState, useRef } from 'react';
|
|
4
3
|
import { Animated, Dimensions, Easing, Keyboard, KeyboardAvoidingView, Platform, TouchableOpacity, StatusBar, StyleSheet, View, } from 'react-native';
|
|
5
|
-
import { useStoryContextParam } from '../../../hooks';
|
|
4
|
+
import { useIsSplitPanelVisible, useIsUIVisible, useStoryContextParam, useTheme, } from '../../../hooks';
|
|
5
|
+
import { ANIMATION_DURATION_TRANSITION } from '../../../constants';
|
|
6
6
|
import StoryListView from '../StoryListView';
|
|
7
7
|
import StoryView from '../StoryView';
|
|
8
8
|
import AbsolutePositionedKeyboardAwareView from './absolute-positioned-keyboard-aware-view';
|
|
9
9
|
import Addons from './addons/Addons';
|
|
10
|
+
import { AddonsSkeleton } from './addons/AddonsSkeleton';
|
|
10
11
|
import { getAddonPanelPosition, getNavigatorPanelPosition, getPreviewShadowStyle, getPreviewStyle, } from './animation';
|
|
11
12
|
import Navigation from './navigation';
|
|
12
13
|
import { PREVIEW, ADDONS } from './navigation/constants';
|
|
13
14
|
import Panel from './Panel';
|
|
14
15
|
import { useWindowDimensions } from 'react-native';
|
|
15
16
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
16
|
-
const ANIMATION_DURATION = 400;
|
|
17
17
|
const IS_IOS = Platform.OS === 'ios';
|
|
18
18
|
// @ts-ignore: Property 'Expo' does not exist on type 'Global'
|
|
19
19
|
const getExpoRoot = () => global.Expo || global.__expo || global.__exponent;
|
|
@@ -26,15 +26,15 @@ const flex = { flex: 1 };
|
|
|
26
26
|
*/
|
|
27
27
|
function Preview({ animatedValue, style, children }) {
|
|
28
28
|
const theme = useTheme();
|
|
29
|
-
const containerStyle = Object.assign({ backgroundColor: theme.backgroundColor }, getPreviewShadowStyle(animatedValue));
|
|
29
|
+
const containerStyle = Object.assign({ backgroundColor: theme.preview.backgroundColor }, getPreviewShadowStyle(animatedValue));
|
|
30
30
|
return (React.createElement(Animated.View, { style: [flex, containerStyle] },
|
|
31
31
|
React.createElement(View, { style: [flex, style] }, children)));
|
|
32
32
|
}
|
|
33
33
|
const styles = StyleSheet.create({
|
|
34
34
|
expoAndroidContainer: { paddingTop: StatusBar.currentHeight },
|
|
35
35
|
});
|
|
36
|
-
const Container = styled.View(({ theme }) => (Object.assign({ flex: 1, backgroundColor: theme.
|
|
37
|
-
const OnDeviceUI = ({ storyIndex,
|
|
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
38
|
const [tabOpen, setTabOpen] = useState(initialTabOpen || PREVIEW);
|
|
39
39
|
const lastTabOpen = React.useRef(tabOpen);
|
|
40
40
|
const [previewDimensions, setPreviewDimensions] = useState(() => ({
|
|
@@ -44,8 +44,6 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
44
44
|
const animatedValue = useRef(new Animated.Value(tabOpen));
|
|
45
45
|
const wide = useWindowDimensions().width >= BREAKPOINT;
|
|
46
46
|
const insets = useSafeAreaInsets();
|
|
47
|
-
const theme = useTheme();
|
|
48
|
-
const [isUIVisible, setIsUIVisible] = useState(isUIHidden !== undefined ? !isUIHidden : true);
|
|
49
47
|
const handleToggleTab = React.useCallback((newTabOpen) => {
|
|
50
48
|
if (newTabOpen === tabOpen) {
|
|
51
49
|
return;
|
|
@@ -53,7 +51,7 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
53
51
|
lastTabOpen.current = tabOpen;
|
|
54
52
|
Animated.timing(animatedValue.current, {
|
|
55
53
|
toValue: newTabOpen,
|
|
56
|
-
duration:
|
|
54
|
+
duration: ANIMATION_DURATION_TRANSITION,
|
|
57
55
|
easing: Easing.inOut(Easing.cubic),
|
|
58
56
|
useNativeDriver: true,
|
|
59
57
|
}).start();
|
|
@@ -75,6 +73,7 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
75
73
|
lastTabOpen: lastTabOpen.current,
|
|
76
74
|
}),
|
|
77
75
|
];
|
|
76
|
+
const [isUIVisible] = useIsUIVisible();
|
|
78
77
|
// The initial value is just a guess until the layout calculation has been done.
|
|
79
78
|
const [navBarHeight, setNavBarHeight] = React.useState(insets.bottom + 40);
|
|
80
79
|
const measureNavigation = React.useCallback(({ nativeEvent }) => {
|
|
@@ -95,26 +94,39 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
95
94
|
const safeAreaMargins = {
|
|
96
95
|
paddingBottom: isUIVisible ? insets.bottom + navBarHeight : noSafeArea ? 0 : insets.bottom,
|
|
97
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,
|
|
98
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 !== PREVIEW;
|
|
99
109
|
return (React.createElement(React.Fragment, null,
|
|
100
110
|
React.createElement(Container, null,
|
|
101
|
-
React.createElement(KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView ||
|
|
111
|
+
React.createElement(KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView || isPreviewInactive, behavior: IS_IOS ? 'padding' : null, keyboardVerticalOffset: keyboardVerticalOffset, style: flex },
|
|
102
112
|
React.createElement(AbsolutePositionedKeyboardAwareView, { onLayout: setPreviewDimensions, previewDimensions: previewDimensions },
|
|
103
113
|
React.createElement(Animated.View, { style: previewWrapperStyles },
|
|
104
114
|
React.createElement(Preview, { style: safeAreaMargins, animatedValue: animatedValue.current },
|
|
105
|
-
React.createElement(StoryView, null)
|
|
106
|
-
|
|
107
|
-
|
|
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(PREVIEW) })) : null),
|
|
120
|
+
React.createElement(Panel, { edge: "right", style: [
|
|
108
121
|
getNavigatorPanelPosition(animatedValue.current, previewDimensions.width, wide),
|
|
109
|
-
|
|
110
|
-
{ backgroundColor: theme.storyListBackgroundColor },
|
|
122
|
+
panelSafeAreaMargins,
|
|
111
123
|
] },
|
|
112
124
|
React.createElement(StoryListView, { storyIndex: storyIndex })),
|
|
113
|
-
React.createElement(Panel, { style: [
|
|
125
|
+
React.createElement(Panel, { edge: "left", style: [
|
|
114
126
|
getAddonPanelPosition(animatedValue.current, previewDimensions.width, wide),
|
|
115
|
-
|
|
127
|
+
panelSafeAreaMargins,
|
|
116
128
|
] },
|
|
117
129
|
React.createElement(Addons, { active: tabOpen === ADDONS })))),
|
|
118
|
-
React.createElement(Navigation, { onLayout: measureNavigation, tabOpen: tabOpen, onChangeTab: handleToggleTab
|
|
130
|
+
React.createElement(Navigation, { onLayout: measureNavigation, tabOpen: tabOpen, onChangeTab: handleToggleTab }))));
|
|
119
131
|
};
|
|
120
132
|
export default React.memo(OnDeviceUI);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import React
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 }:
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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 }) =>
|
|
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);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
2
|
+
interface AddonsProps {
|
|
3
3
|
active: boolean;
|
|
4
|
-
}
|
|
4
|
+
}
|
|
5
|
+
declare const _default: React.MemoExoticComponent<({ active }: AddonsProps) => JSX.Element>;
|
|
5
6
|
export default _default;
|
|
@@ -1,33 +1,19 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
import styled from '@emotion/native';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
4
3
|
import { addons } from '@storybook/addons';
|
|
4
|
+
import { useSelectedAddon } from '../../../../hooks';
|
|
5
5
|
import AddonsList from './List';
|
|
6
6
|
import AddonWrapper from './Wrapper';
|
|
7
|
-
import {
|
|
8
|
-
const NoAddonContainer = styled.View({
|
|
9
|
-
flex: 1,
|
|
10
|
-
alignItems: 'center',
|
|
11
|
-
justifyContent: 'center',
|
|
12
|
-
});
|
|
13
|
-
const Container = styled.View(({ theme }) => ({
|
|
14
|
-
flex: 1,
|
|
15
|
-
backgroundColor: theme.backgroundColor,
|
|
16
|
-
}));
|
|
7
|
+
import { Box } from '../../Shared/layout';
|
|
17
8
|
const Addons = ({ active }) => {
|
|
18
9
|
const panels = addons.getElements('panel');
|
|
19
|
-
const [addonSelected, setAddonSelected] =
|
|
10
|
+
const [addonSelected, setAddonSelected] = useSelectedAddon(Object.keys(panels)[0]);
|
|
20
11
|
if (Object.keys(panels).length === 0) {
|
|
21
|
-
return (React.createElement(
|
|
22
|
-
React.createElement(
|
|
23
|
-
React.createElement(Label, null, "No addons loaded."))));
|
|
12
|
+
return (React.createElement(Box, { alignItems: "center", justifyContent: "center" },
|
|
13
|
+
React.createElement(Text, null, "No addons loaded.")));
|
|
24
14
|
}
|
|
25
|
-
return (React.createElement(
|
|
26
|
-
React.createElement(
|
|
27
|
-
|
|
28
|
-
React.createElement(AddonWrapper, { addonSelected: active ? addonSelected : null, panels: panels }))));
|
|
15
|
+
return (React.createElement(Box, { flex: true },
|
|
16
|
+
React.createElement(AddonsList, { onPressAddon: setAddonSelected, panels: panels, addonSelected: active ? addonSelected : null }),
|
|
17
|
+
React.createElement(AddonWrapper, { addonSelected: active ? addonSelected : null, panels: panels })));
|
|
29
18
|
};
|
|
30
|
-
const styles = StyleSheet.create({
|
|
31
|
-
container: { flex: 1 },
|
|
32
|
-
});
|
|
33
19
|
export default React.memo(Addons);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Component that mimics the addons panel.
|
|
4
|
+
*
|
|
5
|
+
* The main reason this exists is that the scaled addons view feels more
|
|
6
|
+
* cluttered than a more abstract skeleton view, which allows users to focus
|
|
7
|
+
* on the story content rather than become distracted by the addons UI in an
|
|
8
|
+
* already small view.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AddonsSkeleton: React.NamedExoticComponent<AddonsSkeletonProps>;
|
|
11
|
+
interface AddonsSkeletonProps {
|
|
12
|
+
visible: boolean;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from '@emotion/native';
|
|
3
|
+
import { Animated, Easing, StyleSheet, View } from 'react-native';
|
|
4
|
+
import { ANIMATION_DURATION_TRANSITION } from '../../../../constants';
|
|
5
|
+
/**
|
|
6
|
+
* Component that mimics the addons panel.
|
|
7
|
+
*
|
|
8
|
+
* The main reason this exists is that the scaled addons view feels more
|
|
9
|
+
* cluttered than a more abstract skeleton view, which allows users to focus
|
|
10
|
+
* on the story content rather than become distracted by the addons UI in an
|
|
11
|
+
* already small view.
|
|
12
|
+
*/
|
|
13
|
+
export const AddonsSkeleton = React.memo(function AddonsSkeleton({ visible }) {
|
|
14
|
+
const progress = React.useRef(new Animated.Value(visible ? 1 : 0));
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
Animated.timing(progress.current, {
|
|
17
|
+
toValue: visible ? 1 : 0,
|
|
18
|
+
duration: ANIMATION_DURATION_TRANSITION,
|
|
19
|
+
useNativeDriver: true,
|
|
20
|
+
easing: Easing.inOut(Easing.cubic),
|
|
21
|
+
}).start();
|
|
22
|
+
}, [visible]);
|
|
23
|
+
return (React.createElement(AddonsSkeletonContainer, { pointerEvents: "none", opacity: progress.current },
|
|
24
|
+
React.createElement(TabsSkeleton, null),
|
|
25
|
+
React.createElement(AddonsContentSkeleton, null)));
|
|
26
|
+
});
|
|
27
|
+
const TabSkeleton = styled.View(({ theme, active }) => ({
|
|
28
|
+
opacity: active ? 1 : 0.5,
|
|
29
|
+
backgroundColor: active ? theme.tabs.activeBackgroundColor : theme.tokens.color.grey200,
|
|
30
|
+
borderRadius: theme.tokens.borderRadius.round,
|
|
31
|
+
width: active ? 100 : 70,
|
|
32
|
+
height: 30,
|
|
33
|
+
marginRight: 12,
|
|
34
|
+
}));
|
|
35
|
+
const BoxSkeleton = styled.View(({ theme, width, height }) => ({
|
|
36
|
+
backgroundColor: theme.tokens.color.blue200,
|
|
37
|
+
borderRadius: theme.tokens.borderRadius.large,
|
|
38
|
+
height,
|
|
39
|
+
width,
|
|
40
|
+
}));
|
|
41
|
+
function AddonsFieldSkeleton({ long = false }) {
|
|
42
|
+
return (React.createElement(View, { style: { marginBottom: 32 } },
|
|
43
|
+
React.createElement(BoxSkeleton, { width: 75, height: 10, marginBottom: 12 }),
|
|
44
|
+
React.createElement(BoxSkeleton, { width: long ? 200 : 120, height: 15 })));
|
|
45
|
+
}
|
|
46
|
+
function AddonsContentSkeleton() {
|
|
47
|
+
return (React.createElement(React.Fragment, null,
|
|
48
|
+
React.createElement(AddonsFieldSkeleton, { long: true }),
|
|
49
|
+
React.createElement(AddonsFieldSkeleton, { long: true }),
|
|
50
|
+
React.createElement(AddonsFieldSkeleton, null),
|
|
51
|
+
React.createElement(AddonsFieldSkeleton, null)));
|
|
52
|
+
}
|
|
53
|
+
function TabsSkeleton() {
|
|
54
|
+
return (React.createElement(View, { style: { flexDirection: 'row', marginBottom: 16 } },
|
|
55
|
+
React.createElement(TabSkeleton, null),
|
|
56
|
+
React.createElement(TabSkeleton, { active: true }),
|
|
57
|
+
React.createElement(TabSkeleton, null)));
|
|
58
|
+
}
|
|
59
|
+
const AddonsSkeletonContainer = styled(Animated.View)(({ theme }) => (Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { flex: 1, backgroundColor: theme.panel.backgroundColor, borderTopWidth: theme.panel.borderWidth, borderColor: theme.panel.borderColor, padding: theme.panel.paddingHorizontal, overflow: 'hidden' })));
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import styled from '@emotion/native';
|
|
4
|
-
import Button from '../navigation/Button';
|
|
5
|
-
const Container = styled.View(({ theme }) => ({
|
|
6
|
-
flexDirection: 'row',
|
|
7
|
-
borderBottomWidth: 1,
|
|
8
|
-
borderBottomColor: theme.borderColor || '#e6e6e6',
|
|
9
|
-
}));
|
|
2
|
+
import { TabBar, TabButton } from '../../Shared/tabs';
|
|
10
3
|
const AddonList = ({ panels, addonSelected, onPressAddon }) => {
|
|
11
4
|
const addonKeys = Object.keys(panels);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return renderTab(id, resolvedTitle);
|
|
18
|
-
}))));
|
|
5
|
+
return (React.createElement(TabBar, { scrollable: true }, addonKeys.map((id) => {
|
|
6
|
+
const { title } = panels[id];
|
|
7
|
+
const resolvedTitle = typeof title === 'function' ? title() : title;
|
|
8
|
+
return (React.createElement(TabButton, { active: id === addonSelected, key: id, id: id, onPress: () => onPressAddon(id) }, resolvedTitle.toUpperCase()));
|
|
9
|
+
})));
|
|
19
10
|
};
|
|
20
11
|
export default React.memo(AddonList);
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
flex: {
|
|
11
|
-
flex: 1,
|
|
12
|
-
},
|
|
13
|
-
});
|
|
2
|
+
import { ScrollView } from 'react-native';
|
|
3
|
+
import styled from '@emotion/native';
|
|
4
|
+
import { useTheme, useUpdateOnStoryChanged } from '../../../../hooks';
|
|
5
|
+
const Container = styled.View(({ selected }) => ({
|
|
6
|
+
display: selected ? 'flex' : 'none',
|
|
7
|
+
flex: 1,
|
|
8
|
+
}));
|
|
14
9
|
const Wrapper = ({ panels, addonSelected }) => {
|
|
10
|
+
// Force a re-render when the current story changes, to ensure that the addon
|
|
11
|
+
// panels state is not desynced.
|
|
12
|
+
useUpdateOnStoryChanged();
|
|
13
|
+
const theme = useTheme();
|
|
15
14
|
const addonKeys = Object.keys(panels);
|
|
16
15
|
const content = addonKeys.map((id) => {
|
|
17
16
|
const selected = addonSelected === id;
|
|
18
|
-
return (React.createElement(
|
|
19
|
-
React.createElement(ScrollView,
|
|
17
|
+
return (React.createElement(Container, { key: id, selected: selected },
|
|
18
|
+
React.createElement(ScrollView, { contentContainerStyle: { padding: theme.panel.paddingHorizontal } }, panels[id].render({ active: selected, key: id }))));
|
|
20
19
|
});
|
|
21
20
|
return React.createElement(React.Fragment, null, content);
|
|
22
21
|
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { ViewProps } from 'react-native';
|
|
3
|
-
interface
|
|
3
|
+
interface NavigationProps {
|
|
4
4
|
tabOpen: number;
|
|
5
5
|
onChangeTab: (index: number) => void;
|
|
6
|
-
isUIVisible: boolean;
|
|
7
|
-
setIsUIVisible: Dispatch<SetStateAction<boolean>>;
|
|
8
6
|
onLayout: ViewProps['onLayout'];
|
|
9
7
|
}
|
|
10
|
-
declare const _default: React.MemoExoticComponent<({ tabOpen, onChangeTab,
|
|
8
|
+
declare const _default: React.MemoExoticComponent<({ tabOpen, onChangeTab, onLayout }: NavigationProps) => JSX.Element>;
|
|
11
9
|
export default _default;
|