@storybook/react-native 6.5.0-rc.3 → 6.5.0-rc.5
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 +39 -0
- package/dist/hooks.js +70 -2
- package/dist/preview/View.d.ts +1 -0
- package/dist/preview/View.js +7 -2
- package/dist/preview/components/OnDeviceUI/OnDeviceUI.d.ts +1 -2
- package/dist/preview/components/OnDeviceUI/OnDeviceUI.js +16 -9
- package/dist/preview/components/OnDeviceUI/Panel.d.ts +1 -1
- package/dist/preview/components/OnDeviceUI/Panel.js +5 -1
- package/dist/preview/components/OnDeviceUI/addons/Addons.js +3 -2
- 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/Wrapper.js +4 -1
- package/dist/preview/components/OnDeviceUI/navigation/Navigation.d.ts +3 -5
- package/dist/preview/components/OnDeviceUI/navigation/Navigation.js +22 -8
- 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/StoryListView/StoryListView.js +4 -4
- package/dist/preview/components/StoryView/StoryView.js +19 -13
- package/package.json +2 -2
- package/readme.md +2 -0
- package/dist/preview/components/OnDeviceUI/navigation/VisibilityButton.d.ts +0 -8
- package/dist/preview/components/OnDeviceUI/navigation/VisibilityButton.js +0 -45
package/dist/hooks.d.ts
CHANGED
|
@@ -21,7 +21,46 @@ export declare function useIsStorySelected(storyId: string): boolean;
|
|
|
21
21
|
* Hook that indicates if `title` is the currently selected story section.
|
|
22
22
|
*/
|
|
23
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;
|
|
24
28
|
/**
|
|
25
29
|
* Hook that gets the current theme values.
|
|
26
30
|
*/
|
|
27
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,5 @@
|
|
|
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
3
|
import { useTheme as useEmotionTheme } from 'emotion-theming';
|
|
4
4
|
const storyContextAtom = atom(null);
|
|
5
5
|
/**
|
|
@@ -34,9 +34,77 @@ export function useIsStorySelected(storyId) {
|
|
|
34
34
|
export function useIsStorySectionSelected(title) {
|
|
35
35
|
return useAtomValue(useMemo(() => atom((get) => { var _a; return ((_a = get(storyContextAtom)) === null || _a === void 0 ? void 0 : _a.title) === title; }), [title]));
|
|
36
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
|
+
}
|
|
37
43
|
/**
|
|
38
44
|
* Hook that gets the current theme values.
|
|
39
45
|
*/
|
|
40
46
|
export function useTheme() {
|
|
41
47
|
return useEmotionTheme();
|
|
42
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/preview/View.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type Params = {
|
|
|
30
30
|
shouldPersistSelection?: boolean;
|
|
31
31
|
tabOpen?: number;
|
|
32
32
|
isUIHidden?: boolean;
|
|
33
|
+
isSplitPanelVisible?: boolean;
|
|
33
34
|
shouldDisableKeyboardAvoidingView?: boolean;
|
|
34
35
|
keyboardAvoidingViewVerticalOffset?: number;
|
|
35
36
|
theme: DeepPartial<Theme>;
|
package/dist/preview/View.js
CHANGED
|
@@ -13,7 +13,7 @@ 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';
|
|
@@ -90,6 +90,11 @@ export class View {
|
|
|
90
90
|
// eslint-disable-next-line consistent-this
|
|
91
91
|
const self = this;
|
|
92
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
|
+
});
|
|
93
98
|
return () => {
|
|
94
99
|
const setContext = useSetStoryContext();
|
|
95
100
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
@@ -112,7 +117,7 @@ export class View {
|
|
|
112
117
|
if (onDeviceUI) {
|
|
113
118
|
return (React.createElement(SafeAreaProvider, null,
|
|
114
119
|
React.createElement(ThemeProvider, { theme: appliedTheme },
|
|
115
|
-
React.createElement(OnDeviceUI, { storyIndex: self._storyIndex,
|
|
120
|
+
React.createElement(OnDeviceUI, { storyIndex: self._storyIndex, tabOpen: params.tabOpen, shouldDisableKeyboardAvoidingView: params.shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset: params.keyboardAvoidingViewVerticalOffset }))));
|
|
116
121
|
}
|
|
117
122
|
else {
|
|
118
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,18 +1,19 @@
|
|
|
1
1
|
import styled from '@emotion/native';
|
|
2
2
|
import React, { useState, useRef } from 'react';
|
|
3
3
|
import { Animated, Dimensions, Easing, Keyboard, KeyboardAvoidingView, Platform, TouchableOpacity, StatusBar, StyleSheet, View, } from 'react-native';
|
|
4
|
-
import { useStoryContextParam, useTheme } from '../../../hooks';
|
|
4
|
+
import { useIsSplitPanelVisible, useIsUIVisible, useStoryContextParam, useTheme, } from '../../../hooks';
|
|
5
|
+
import { ANIMATION_DURATION_TRANSITION } from '../../../constants';
|
|
5
6
|
import StoryListView from '../StoryListView';
|
|
6
7
|
import StoryView from '../StoryView';
|
|
7
8
|
import AbsolutePositionedKeyboardAwareView from './absolute-positioned-keyboard-aware-view';
|
|
8
9
|
import Addons from './addons/Addons';
|
|
10
|
+
import { AddonsSkeleton } from './addons/AddonsSkeleton';
|
|
9
11
|
import { getAddonPanelPosition, getNavigatorPanelPosition, getPreviewShadowStyle, getPreviewStyle, } from './animation';
|
|
10
12
|
import Navigation from './navigation';
|
|
11
13
|
import { PREVIEW, ADDONS } from './navigation/constants';
|
|
12
14
|
import Panel from './Panel';
|
|
13
15
|
import { useWindowDimensions } from 'react-native';
|
|
14
16
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
15
|
-
const ANIMATION_DURATION = 400;
|
|
16
17
|
const IS_IOS = Platform.OS === 'ios';
|
|
17
18
|
// @ts-ignore: Property 'Expo' does not exist on type 'Global'
|
|
18
19
|
const getExpoRoot = () => global.Expo || global.__expo || global.__exponent;
|
|
@@ -33,7 +34,7 @@ const styles = StyleSheet.create({
|
|
|
33
34
|
expoAndroidContainer: { paddingTop: StatusBar.currentHeight },
|
|
34
35
|
});
|
|
35
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' } }))));
|
|
36
|
-
const OnDeviceUI = ({ storyIndex,
|
|
37
|
+
const OnDeviceUI = ({ storyIndex, shouldDisableKeyboardAvoidingView, keyboardAvoidingViewVerticalOffset, tabOpen: initialTabOpen, }) => {
|
|
37
38
|
const [tabOpen, setTabOpen] = useState(initialTabOpen || PREVIEW);
|
|
38
39
|
const lastTabOpen = React.useRef(tabOpen);
|
|
39
40
|
const [previewDimensions, setPreviewDimensions] = useState(() => ({
|
|
@@ -43,7 +44,6 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
43
44
|
const animatedValue = useRef(new Animated.Value(tabOpen));
|
|
44
45
|
const wide = useWindowDimensions().width >= BREAKPOINT;
|
|
45
46
|
const insets = useSafeAreaInsets();
|
|
46
|
-
const [isUIVisible, setIsUIVisible] = useState(isUIHidden !== undefined ? !isUIHidden : true);
|
|
47
47
|
const handleToggleTab = React.useCallback((newTabOpen) => {
|
|
48
48
|
if (newTabOpen === tabOpen) {
|
|
49
49
|
return;
|
|
@@ -51,7 +51,7 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
51
51
|
lastTabOpen.current = tabOpen;
|
|
52
52
|
Animated.timing(animatedValue.current, {
|
|
53
53
|
toValue: newTabOpen,
|
|
54
|
-
duration:
|
|
54
|
+
duration: ANIMATION_DURATION_TRANSITION,
|
|
55
55
|
easing: Easing.inOut(Easing.cubic),
|
|
56
56
|
useNativeDriver: true,
|
|
57
57
|
}).start();
|
|
@@ -73,6 +73,7 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
73
73
|
lastTabOpen: lastTabOpen.current,
|
|
74
74
|
}),
|
|
75
75
|
];
|
|
76
|
+
const [isUIVisible] = useIsUIVisible();
|
|
76
77
|
// The initial value is just a guess until the layout calculation has been done.
|
|
77
78
|
const [navBarHeight, setNavBarHeight] = React.useState(insets.bottom + 40);
|
|
78
79
|
const measureNavigation = React.useCallback(({ nativeEvent }) => {
|
|
@@ -93,6 +94,7 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
93
94
|
const safeAreaMargins = {
|
|
94
95
|
paddingBottom: isUIVisible ? insets.bottom + navBarHeight : noSafeArea ? 0 : insets.bottom,
|
|
95
96
|
paddingTop: !noSafeArea ? insets.top : 0,
|
|
97
|
+
overflow: 'hidden',
|
|
96
98
|
};
|
|
97
99
|
// The panels always apply the safe area, regardless of the story parameters.
|
|
98
100
|
const panelSafeAreaMargins = {
|
|
@@ -102,14 +104,19 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
102
104
|
// Adjust the keyboard offset (possibly in a negative direction) to account
|
|
103
105
|
// for the safe area and navigation bar.
|
|
104
106
|
const keyboardVerticalOffset = -panelSafeAreaMargins.paddingBottom + (keyboardAvoidingViewVerticalOffset !== null && keyboardAvoidingViewVerticalOffset !== void 0 ? keyboardAvoidingViewVerticalOffset : 0);
|
|
107
|
+
const [isSplitPanelVisible] = useIsSplitPanelVisible();
|
|
108
|
+
const isPreviewInactive = tabOpen !== PREVIEW;
|
|
105
109
|
return (React.createElement(React.Fragment, null,
|
|
106
110
|
React.createElement(Container, null,
|
|
107
|
-
React.createElement(KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView ||
|
|
111
|
+
React.createElement(KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView || isPreviewInactive, behavior: IS_IOS ? 'padding' : null, keyboardVerticalOffset: keyboardVerticalOffset, style: flex },
|
|
108
112
|
React.createElement(AbsolutePositionedKeyboardAwareView, { onLayout: setPreviewDimensions, previewDimensions: previewDimensions },
|
|
109
113
|
React.createElement(Animated.View, { style: previewWrapperStyles },
|
|
110
114
|
React.createElement(Preview, { style: safeAreaMargins, animatedValue: animatedValue.current },
|
|
111
|
-
React.createElement(StoryView, null)
|
|
112
|
-
|
|
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),
|
|
113
120
|
React.createElement(Panel, { edge: "right", style: [
|
|
114
121
|
getNavigatorPanelPosition(animatedValue.current, previewDimensions.width, wide),
|
|
115
122
|
panelSafeAreaMargins,
|
|
@@ -120,6 +127,6 @@ const OnDeviceUI = ({ storyIndex, isUIHidden, shouldDisableKeyboardAvoidingView,
|
|
|
120
127
|
panelSafeAreaMargins,
|
|
121
128
|
] },
|
|
122
129
|
React.createElement(Addons, { active: tabOpen === ADDONS })))),
|
|
123
|
-
React.createElement(Navigation, { onLayout: measureNavigation, tabOpen: tabOpen, onChangeTab: handleToggleTab
|
|
130
|
+
React.createElement(Navigation, { onLayout: measureNavigation, tabOpen: tabOpen, onChangeTab: handleToggleTab }))));
|
|
124
131
|
};
|
|
125
132
|
export default React.memo(OnDeviceUI);
|
|
@@ -3,12 +3,16 @@ import { StyleSheet, Animated } from 'react-native';
|
|
|
3
3
|
import styled from '@emotion/native';
|
|
4
4
|
const Container = styled(Animated.View)(({ theme, edge }) => ({
|
|
5
5
|
backgroundColor: theme.panel.backgroundColor,
|
|
6
|
+
borderTopWidth: edge === 'top' ? theme.panel.borderWidth : undefined,
|
|
6
7
|
borderStartWidth: edge === 'left' ? theme.panel.borderWidth : undefined,
|
|
7
8
|
borderEndWidth: edge === 'right' ? theme.panel.borderWidth : undefined,
|
|
8
9
|
borderColor: theme.panel.borderColor,
|
|
9
10
|
}));
|
|
10
11
|
const Panel = ({ edge, children, style }) => {
|
|
11
|
-
const containerStyle = StyleSheet.flatten([
|
|
12
|
+
const containerStyle = StyleSheet.flatten([
|
|
13
|
+
edge === 'top' ? undefined : StyleSheet.absoluteFillObject,
|
|
14
|
+
style,
|
|
15
|
+
]);
|
|
12
16
|
return (React.createElement(Container, { edge: edge, style: containerStyle }, children));
|
|
13
17
|
};
|
|
14
18
|
export default React.memo(Panel);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { Text } from 'react-native';
|
|
3
3
|
import { addons } from '@storybook/addons';
|
|
4
|
+
import { useSelectedAddon } from '../../../../hooks';
|
|
4
5
|
import AddonsList from './List';
|
|
5
6
|
import AddonWrapper from './Wrapper';
|
|
6
7
|
import { Box } from '../../Shared/layout';
|
|
7
8
|
const Addons = ({ active }) => {
|
|
8
9
|
const panels = addons.getElements('panel');
|
|
9
|
-
const [addonSelected, setAddonSelected] =
|
|
10
|
+
const [addonSelected, setAddonSelected] = useSelectedAddon(Object.keys(panels)[0]);
|
|
10
11
|
if (Object.keys(panels).length === 0) {
|
|
11
12
|
return (React.createElement(Box, { alignItems: "center", justifyContent: "center" },
|
|
12
13
|
React.createElement(Text, null, "No addons loaded.")));
|
|
@@ -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,12 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ScrollView } from 'react-native';
|
|
3
3
|
import styled from '@emotion/native';
|
|
4
|
-
import { useTheme } from '../../../../hooks';
|
|
4
|
+
import { useTheme, useUpdateOnStoryChanged } from '../../../../hooks';
|
|
5
5
|
const Container = styled.View(({ selected }) => ({
|
|
6
6
|
display: selected ? 'flex' : 'none',
|
|
7
7
|
flex: 1,
|
|
8
8
|
}));
|
|
9
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();
|
|
10
13
|
const theme = useTheme();
|
|
11
14
|
const addonKeys = Object.keys(panels);
|
|
12
15
|
const content = addonKeys.map((id) => {
|
|
@@ -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;
|
|
@@ -2,8 +2,9 @@ import React from 'react';
|
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
4
|
import GestureRecognizer from 'react-native-swipe-gestures';
|
|
5
|
+
import { useIsUIVisible } from '../../../../hooks';
|
|
5
6
|
import { NavigationBar } from './NavigationBar';
|
|
6
|
-
import VisibilityButton from './
|
|
7
|
+
import { VisibilityButton, AddonsSplitButton } from './NavigationButton';
|
|
7
8
|
const SWIPE_CONFIG = {
|
|
8
9
|
velocityThreshold: 0.2,
|
|
9
10
|
directionalOffsetThreshold: 80,
|
|
@@ -14,11 +15,8 @@ const navStyle = {
|
|
|
14
15
|
right: 0,
|
|
15
16
|
bottom: 0,
|
|
16
17
|
};
|
|
17
|
-
const Navigation = ({ tabOpen, onChangeTab,
|
|
18
|
+
const Navigation = ({ tabOpen, onChangeTab, onLayout }) => {
|
|
18
19
|
const insets = useSafeAreaInsets();
|
|
19
|
-
const handleToggleUI = () => {
|
|
20
|
-
setIsUIVisible((oldIsUIVisible) => !oldIsUIVisible);
|
|
21
|
-
};
|
|
22
20
|
const handleSwipeLeft = () => {
|
|
23
21
|
if (tabOpen < 1) {
|
|
24
22
|
onChangeTab(tabOpen + 1);
|
|
@@ -29,9 +27,25 @@ const Navigation = ({ tabOpen, onChangeTab, isUIVisible, setIsUIVisible, onLayou
|
|
|
29
27
|
onChangeTab(tabOpen - 1);
|
|
30
28
|
}
|
|
31
29
|
};
|
|
30
|
+
const [isUIVisible] = useIsUIVisible();
|
|
32
31
|
return (React.createElement(View, { style: navStyle, onLayout: onLayout },
|
|
33
|
-
isUIVisible && (React.createElement(GestureRecognizer, { onSwipeLeft: handleSwipeLeft, onSwipeRight: handleSwipeRight, config: SWIPE_CONFIG },
|
|
34
|
-
React.createElement(NavigationBar, { index: tabOpen, onPress: onChangeTab, style: { paddingBottom: insets.bottom } }))),
|
|
35
|
-
React.createElement(
|
|
32
|
+
React.createElement(View, null, isUIVisible && (React.createElement(GestureRecognizer, { onSwipeLeft: handleSwipeLeft, onSwipeRight: handleSwipeRight, config: SWIPE_CONFIG },
|
|
33
|
+
React.createElement(NavigationBar, { index: tabOpen, onPress: onChangeTab, style: { paddingBottom: insets.bottom } })))),
|
|
34
|
+
React.createElement(NavigationShortcuts, null,
|
|
35
|
+
React.createElement(VisibilityButton, null),
|
|
36
|
+
React.createElement(AddonsSplitButton, null))));
|
|
36
37
|
};
|
|
37
38
|
export default React.memo(Navigation);
|
|
39
|
+
function NavigationShortcuts({ children }) {
|
|
40
|
+
const insets = useSafeAreaInsets();
|
|
41
|
+
return (React.createElement(View, { style: {
|
|
42
|
+
zIndex: 100,
|
|
43
|
+
alignSelf: 'center',
|
|
44
|
+
justifyContent: 'center',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
flexDirection: 'row-reverse',
|
|
47
|
+
position: 'absolute',
|
|
48
|
+
bottom: insets.bottom + 14,
|
|
49
|
+
right: 8,
|
|
50
|
+
} }, children));
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TouchableWithoutFeedback } from 'react-native';
|
|
3
|
+
import { useIsSplitPanelVisible, useIsUIVisible } from '../../../../hooks';
|
|
4
|
+
import { Icon } from '../../Shared/icons';
|
|
5
|
+
import { Box } from '../../Shared/layout';
|
|
6
|
+
const hitSlop = { top: 5, left: 5, right: 5, bottom: 5 };
|
|
7
|
+
function NavigationButton({ iconName, inverseIconName, active, toggle }) {
|
|
8
|
+
return (React.createElement(TouchableWithoutFeedback, { onPress: toggle, hitSlop: hitSlop },
|
|
9
|
+
React.createElement(Box, { marginHorizontal: 8 },
|
|
10
|
+
React.createElement(Icon, { flex: 1, background: true, name: inverseIconName, opacity: 0.8, pointerEvents: "none" },
|
|
11
|
+
React.createElement(Icon, { name: iconName, opacity: active ? 0.6 : 0.25 })))));
|
|
12
|
+
}
|
|
13
|
+
export function VisibilityButton() {
|
|
14
|
+
const [active, toggle] = useIsUIVisible();
|
|
15
|
+
return (React.createElement(NavigationButton, { iconName: "layout-bottom", inverseIconName: "layout-bottom-inverse", active: active, toggle: () => toggle() }));
|
|
16
|
+
}
|
|
17
|
+
export function AddonsSplitButton() {
|
|
18
|
+
const [active, toggle] = useIsSplitPanelVisible();
|
|
19
|
+
return (React.createElement(NavigationButton, { iconName: "layout-split", inverseIconName: "layout-split-inverse", active: active, toggle: () => toggle() }));
|
|
20
|
+
}
|
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
declare const iconSources: {
|
|
3
|
+
grid: {
|
|
4
|
+
uri: string;
|
|
5
|
+
};
|
|
6
|
+
'story-white': {
|
|
7
|
+
uri: string;
|
|
8
|
+
};
|
|
9
|
+
'story-blue': {
|
|
10
|
+
uri: string;
|
|
11
|
+
};
|
|
12
|
+
search: {
|
|
13
|
+
uri: string;
|
|
14
|
+
};
|
|
15
|
+
'layout-bottom': {
|
|
16
|
+
uri: string;
|
|
17
|
+
};
|
|
18
|
+
'layout-bottom-inverse': {
|
|
19
|
+
uri: string;
|
|
20
|
+
};
|
|
21
|
+
'layout-split': {
|
|
22
|
+
uri: string;
|
|
23
|
+
};
|
|
24
|
+
'layout-split-inverse': {
|
|
25
|
+
uri: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export type IconName = keyof typeof iconSources;
|
|
29
|
+
declare const StyledImage: any;
|
|
30
|
+
interface IconProps extends React.ComponentProps<typeof StyledImage> {
|
|
31
|
+
name: IconName;
|
|
32
|
+
background?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function Icon({ name, background, ...props }: IconProps): JSX.Element;
|
|
35
|
+
export {};
|
|
@@ -1,34 +1,47 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import React from 'react';
|
|
2
|
-
import { Image,
|
|
13
|
+
import { Image, ImageBackground } from 'react-native';
|
|
14
|
+
import styled from '@emotion/native';
|
|
3
15
|
const iconSources = {
|
|
4
|
-
grid: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
grid: {
|
|
17
|
+
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAALdQTFRFAAAAIJ//Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqj9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Haf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqb9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqf9Hqj9Hqf9Hmz01QAAAD10Uk5TAAAEGjNefYqLXDUFAjSY0e79/9KZAghmyMkBQbz6vUIBBnD1cQcOo6Sp76qBePTHVOoC+RQTX4mCDZsBMmX5zDcAAAHcSURBVHic7ZrrcoIwEIVX8W5VpAiUaq33GyJqrfby/s9VacsCDojUJNNx9vzCTLLfZJdkHM4ChJTJSrl8oVj6s4qFckWqZiBed7V6Q24qV6op36uVVgxC042rAQh60LUIhPnY7rBCuOo8dZ9PGb3+gCXC1WA4CjPGE9YIV9NxiNHmwVCUWYAy4rIPV1PMmNnnxVCUoVf9LvOa+xp0f89HqCDzhbWMle3NsuPnWIt5MF7757zogfOxcqLOEGrtzVufm6U5Kz9iR3eHNgYObNWXc4svhQDs1C0GNTbHgZp/l6hnt5ECApqKQZu1471b93O1S1h6OQR2fsbqGcg28JeTtDIFBBwM28iCJON7lZSsVBAN3zFZCpRkkbgwDQQWWJQcvOK2LLYQCwPnoYDPS7aQJQYuwJ4/ZA8l/pASQQhCEIIQhCAEIQhBghB7nSj839xMnmtHQriJIAS5IYiQw3g7dxdBCEIQghCEIAQhyH+ECPmMLsQQKOMzY2vjgIHLYkwaIXaTEOMM+FuAIMbMhJYIW1aIwczaKj9EWuVCTH++7Quml8Qev0aMnl+qtxkfxnu4cYXLXiYhhpg2H4AP1g1L7a55ygDGrVdGZOuVKwFNZN/KSp/XtsPVpOpJ0C+2naE/XA0rpwAAAABJRU5ErkJggg==',
|
|
18
|
+
},
|
|
19
|
+
'story-white': {
|
|
20
|
+
uri: 'data:image/png;base64,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',
|
|
21
|
+
},
|
|
22
|
+
'story-blue': {
|
|
23
|
+
uri: 'data:image/png;base64,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',
|
|
24
|
+
},
|
|
25
|
+
search: {
|
|
26
|
+
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABgWlDQ1BzUkdCIElFQzYxOTY2LTIuMQAAKJF1kd8rg1EYxz/baGLy88KFsjSuTEyJG2USamnNlOFme+2H2ubtfbe03Cq3K0rc+HXBX8Ctcq0UkZJbrokb1ut5t9Uke07PeT7ne87zdM5zwBpMKim9ZgBS6YwWmPI6F0KLTvsLVlqw00VrWNHVcb/fR1X7vMdixlu3Wav6uX+tYSWqK2CpEx5TVC0jPC3sW8+oJu8ItyuJ8IrwmXCfJhcUvjP1SIlfTY6X+NtkLRiYAGuzsDP+iyO/WEloKWF5Oa5UMquU72O+xBFNz89J7BbvRCfAFF6czDDJBMMMMirzMG489MuKKvkDxfxZ1iRXkVklh8YqcRJk6BM1K9WjEmOiR2UkyZn9/9tXPTbkKVV3eKH22TDee8C+DYW8YXwdGUbhGGxPcJmu5K8dwsiH6PmK5jqApk04v6pokV242IKORzWshYuSTdwai8HbKTSGoO0G6pdKPSvvc/IAwQ35qmvY24deOd+0/AMfDGfFB6WkFAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAqNJREFUWIXF2DmIFFEQBuBvVh3FC0EFdT3QaDORTUTFI9hQPAIRQxE0ETXwQBADD7zAaBEDTQxlIxExE4VFjTzYxQNFERUTr/VCvII3Q78Ze2Z6drvZH5ppmqq//q73XnXVlGRHFzZgJeZiDibjLd7gMa7gOr62wdsWStiEAfzNeH3HhYroXNGNu02CPsN9vMOfBjZHUc5DzBb8qAtwG7uxMMW+jB704kOd3w3MGImYg3WEQ9jehn8nrtVxPMeC4YjZ4v+3S8tIFmzD54jrnnAIMqNbWPcqwRFhU48EC/A04uzLyllSu4Fv5CCmimX4HXGvzeK0Se2eGe4yNcLpiH8QY1s5xHVmR85iYEJFSDXG5mbGXZHhnQLEVLEqinO5meGByHBPgYJKeF2J80XIWirimrGoQEFwLoq1PM2gQyhkhMr8vGBBg9H9nFaC3hYshtAVVNGZZtAhqZ5DhcupjTE+zaBDkplZhcthdnQ/kGbQIUnjTDm1Ck0QL9ODRoKeVO5LQjdYJFZXfj/hVSOjjZKj2FugmGn4WYlzq5nhJMlX/oMGxzEHHJa8+MlWxhcj46sFiFkiyc57TG/lME9tL7Q1RzFlYQNXuXdmdTweOX3C/JwEHYt4BzAuq2MZNyPnx1g6AiFlYer4FXH2tEsyEy8igl84of36tFgYkepHo2F1EwvxsI7oIVZk8J2KQ5INnHbta0bQqG+egktYV/f8lTAuDwoVfkj4HHRijVD44mw+ErLShbPR8/041UxYI7HrK6RZx+jq9RG71G7gvdrIVDOMFea1PuFPhEYiPqMfZ4S9mIaWotoddSYK81unUNHHC0f5AV5WgrTCXrXLNazlyxu5LV+eSBU1ZhQF9eObpFj2CIdh1BFn6s1oZqiKfqHtWYLz/wAUO/neFu9thgAAAABJRU5ErkJggg==',
|
|
27
|
+
},
|
|
28
|
+
'layout-bottom': {
|
|
29
|
+
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEzSURBVHgB7ZjhDYIwEIWfTsAIjOAIHYGNZANwAnQi3KBu4AhYIiYNNHCF0ivxvuR+mHjNe70eyR0gCIKQOspEZUKb6CJFa6IxkWMD2SC8Y45q0OItvgXALd6uiJeJFG7eVQkSuSNZ49sL3qVcSQF3zylKcoOp+FjCbTJMTdSUxPHbL8CHwvQyFxmXjZMMC3pOjqSO8J+YzOo54+CIAW7EADdigBsxwI0Y4EYMcLPWwNXEG+HGxf4s8si4xNI8sOe8XK7Q450Q8uZdlfDS85c9cMN+PBAAypvrtwOhm7jcoGc2gWOlYuPdA6/R7wv4GK90niDQPw/bsUY6i62GkqgwLVt/UKwFVzZo0A4dOfWQGvt969cGaa1o38Kh1+s/EylUosbGHsxN3BG3InoQriAIgpA0H3BBw2jFymiHAAAAAElFTkSuQmCC',
|
|
30
|
+
},
|
|
31
|
+
'layout-bottom-inverse': {
|
|
32
|
+
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAFgSURBVHgB7ZjhcYMwDIXlTEA2YISOYDbIRmSD0AloJ+kIyQbuBskGqnz4R+OYw7jYchN9dzouwYIn2+LgAQiCIFQNImqKE4XBcpwpRooWUqHkxgnnxmpo5nSqOfF0+KJ4gzq4UHRKqZt/YjeT0EM94i1WSx81kma/DSyjwakXGigA3eeA4Z7TMcljQHwR4Z6OJlDEEJN49pIOwIRb9bvJ9MeoQBLeDSCACbfy19//+XqqLsCypGcH/xwpgBspgBspgBspgBspgBspgJukAugFsae44nbYa51gC/wrB87ndCqOa/Ws/h6ws0WHXJ+YN7rdHlboeckeeId8fMJfWdpzbsyA2zfxMUXPYg8Q+5AjVoqUHvj2frM5dPho6VxikgZv1QzWY2yNMYk6sEcNFjK4nHCNYWuxjb3IgPURtBWf0153AzvI+8yPxWrokp+EONntH/ho+ubE4LSNNQiCIFTND53ElQfvR3pDAAAAAElFTkSuQmCC',
|
|
33
|
+
},
|
|
34
|
+
'layout-split': {
|
|
35
|
+
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADxSURBVHgB7ZjhCcIwFITP4gAdISM4QkZwI0doNqgb1Q3iJjU/Imga5YVK3yveB/ejkJa7vgTCAYQQYh2fNCTFpHkjTUljksMK+mx8VtaQvTSbnwBom3+dSFMIC3++NgkRDlA3+0m+NNtVAlxgl7NkkaW9XyqWZg+VADNs8+a5w85hAG0YQBsG0IYBtNl9gKNgzQG6fL3acAtpwwDaMIA2DKDNXwRorvW2pBbgXjyfoEfZA90gIGDZxWhMoceyER8lL3rUCyVRK/YD+uwhVnw46UcC7LVyAQ3svl5/hrAwiYCVZ9AlXbHtRGI27kEIIaZ5ALildIGPrcQbAAAAAElFTkSuQmCC',
|
|
36
|
+
},
|
|
37
|
+
'layout-split-inverse': {
|
|
38
|
+
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEaSURBVHgB7ZntDYJADIaLcQBGYARHgAl0I0YQJ8BNdAPZ4BzBDWov8ENrkYuJtJA+SUO4D/K+1yaQAuA4jmMaRCwpjhQB5+NG0VIU8Cu0OR+EaxM15GM6szHxdLlQ7MAGHUWVZdmDT2xGNtRgR3wkaqmliY8MYF93AWwSs3B9HZAyUINdDnxAMmCpdDh7PiCVEIJhqITeNG9g4bgBbdyANm5AGzegzeINbKcW8Ff33Ex92ngJaeMGtHED2rgBbdZv4FtbzwKSgTu7V2uz0OHxPlCXsqlhzdWgkQXsm8uBaWlTNpZChzgIp/EXBuElyu38IvUhDdqjkbSus70+LKwoTqBP1CCKT4KyUVCcsf/tMxcB+zIuwXEcxzRPpfGo9y3IYogAAAAASUVORK5CYII=',
|
|
39
|
+
},
|
|
17
40
|
};
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
width: 12,
|
|
26
|
-
height: 12,
|
|
27
|
-
marginRight: 6,
|
|
28
|
-
},
|
|
29
|
-
searchIcon: {
|
|
30
|
-
opacity: 0.5,
|
|
31
|
-
width: 16,
|
|
32
|
-
height: 16,
|
|
33
|
-
},
|
|
34
|
-
});
|
|
41
|
+
const StyledImage = styled(Image)();
|
|
42
|
+
const StyledImageBackground = styled(ImageBackground)();
|
|
43
|
+
export function Icon(_a) {
|
|
44
|
+
var { name, background = false } = _a, props = __rest(_a, ["name", "background"]);
|
|
45
|
+
const IconComponent = background ? StyledImageBackground : StyledImage;
|
|
46
|
+
return React.createElement(IconComponent, Object.assign({ source: iconSources[name], width: 16, height: 16 }, props));
|
|
47
|
+
}
|
|
@@ -3,7 +3,7 @@ import { addons } from '@storybook/addons';
|
|
|
3
3
|
import Events from '@storybook/core-events';
|
|
4
4
|
import React, { useMemo, useState } from 'react';
|
|
5
5
|
import { SectionList, StyleSheet } from 'react-native';
|
|
6
|
-
import {
|
|
6
|
+
import { Icon } from '../Shared/icons';
|
|
7
7
|
import { Box } from '../Shared/layout';
|
|
8
8
|
import { useIsStorySelected, useIsStorySectionSelected, useTheme } from '../../../hooks';
|
|
9
9
|
const SectionHeaderText = styled.Text(({ theme }) => ({
|
|
@@ -36,7 +36,7 @@ const SearchContainer = styled.View(({ theme }) => ({
|
|
|
36
36
|
const SearchBar = (props) => {
|
|
37
37
|
const theme = useTheme();
|
|
38
38
|
return (React.createElement(SearchContainer, null,
|
|
39
|
-
React.createElement(
|
|
39
|
+
React.createElement(Icon, { name: "search", opacity: 0.5 }),
|
|
40
40
|
React.createElement(SearchInput, Object.assign({}, props, { autoCapitalize: "none", autoComplete: "off", autoCorrect: false, spellCheck: false, clearButtonMode: "while-editing", disableFullscreenUI: true, placeholderTextColor: theme.storyList.search.placeholderTextColor, returnKeyType: "search" }))));
|
|
41
41
|
};
|
|
42
42
|
const HeaderContainer = styled.TouchableOpacity({
|
|
@@ -53,7 +53,7 @@ const HeaderContainer = styled.TouchableOpacity({
|
|
|
53
53
|
const SectionHeader = React.memo(({ title, onPress }) => {
|
|
54
54
|
const selected = useIsStorySectionSelected(title);
|
|
55
55
|
return (React.createElement(HeaderContainer, { key: title, selected: selected, onPress: onPress, activeOpacity: 0.8 },
|
|
56
|
-
React.createElement(
|
|
56
|
+
React.createElement(Icon, { name: "grid", width: 10, height: 10, marginRight: 6 }),
|
|
57
57
|
React.createElement(SectionHeaderText, { selected: selected }, title)));
|
|
58
58
|
});
|
|
59
59
|
const ItemTouchable = styled.TouchableOpacity({
|
|
@@ -74,7 +74,7 @@ const ListItem = React.memo(({ storyId, kind, title, isLastItem, onPress }) => {
|
|
|
74
74
|
const selected = useIsStorySelected(storyId);
|
|
75
75
|
const sectionSelected = useIsStorySectionSelected(kind);
|
|
76
76
|
return (React.createElement(ItemTouchable, { key: title, onPress: onPress, activeOpacity: 0.8, testID: `Storybook.ListItem.${kind}.${title}`, accessibilityLabel: `Storybook.ListItem.${title}`, selected: selected, sectionSelected: sectionSelected, isLastItem: isLastItem },
|
|
77
|
-
React.createElement(
|
|
77
|
+
React.createElement(Icon, { name: selected ? 'story-white' : 'story-blue', marginRight: 6 }),
|
|
78
78
|
React.createElement(StoryNameText, { selected: selected }, title)));
|
|
79
79
|
}, (prevProps, nextProps) => prevProps.storyId === nextProps.storyId);
|
|
80
80
|
const getStories = (storyIndex) => {
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Text,
|
|
2
|
+
import { Text, Keyboard } from 'react-native';
|
|
3
3
|
import { useStoryContext } from '../../../hooks';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
import { Box } from '../Shared/layout';
|
|
5
|
+
/**
|
|
6
|
+
* This is a handler for `onStartShouldSetResponder`, which dismisses the
|
|
7
|
+
* keyboard as a side effect but then responds with `false` to the responder
|
|
8
|
+
* system, so as not to start actually handling the touch.
|
|
9
|
+
*
|
|
10
|
+
* The objective here is to dismiss the keyboard when the story view is tapped,
|
|
11
|
+
* but in a way that won't interfere with presses or swipes. Using a
|
|
12
|
+
* `Touchable...` component as a wrapper will start to handle the touch, which
|
|
13
|
+
* will swallow swipe gestures that should have gone on to a child view, such
|
|
14
|
+
* as `ScrollView`.
|
|
15
|
+
*/
|
|
16
|
+
function dismissOnStartResponder() {
|
|
17
|
+
Keyboard.dismiss();
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
14
20
|
const StoryView = () => {
|
|
15
21
|
const context = useStoryContext();
|
|
16
22
|
const id = context === null || context === void 0 ? void 0 : context.id;
|
|
17
23
|
if (context && context.unboundStoryFn) {
|
|
18
24
|
const { unboundStoryFn: StoryComponent } = context;
|
|
19
|
-
return (React.createElement(
|
|
25
|
+
return (React.createElement(Box, { flex: true, key: id, testID: id, onStartShouldSetResponder: dismissOnStartResponder }, StoryComponent && React.createElement(StoryComponent, Object.assign({}, context))));
|
|
20
26
|
}
|
|
21
|
-
return (React.createElement(
|
|
27
|
+
return (React.createElement(Box, { flex: true, padding: 16, alignItems: "center", justifyContent: "center" },
|
|
22
28
|
React.createElement(Text, null, "Please open navigator and select a story to preview.")));
|
|
23
29
|
};
|
|
24
30
|
export default React.memo(StoryView);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storybook/react-native",
|
|
3
|
-
"version": "6.5.0-rc.
|
|
3
|
+
"version": "6.5.0-rc.5",
|
|
4
4
|
"description": "A better way to develop React Native Components for your app",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"publishConfig": {
|
|
88
88
|
"access": "public"
|
|
89
89
|
},
|
|
90
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "5bc5a1f17cbca33fba28f8cce57d7d49ee80c689"
|
|
91
91
|
}
|
package/readme.md
CHANGED
|
@@ -227,6 +227,8 @@ You can pass these parameters to getStorybookUI call in your storybook entry poi
|
|
|
227
227
|
-- additional query string to pass to websockets
|
|
228
228
|
isUIHidden: Boolean (false)
|
|
229
229
|
-- should the ui be closed initially.
|
|
230
|
+
isSplitPanelVisible: Boolean (false)
|
|
231
|
+
-- should the split panel (addons on the bottom) be visible initially.
|
|
230
232
|
tabOpen: Number (0)
|
|
231
233
|
-- which tab should be open. -1 Navigator, 0 Preview, 1 Addons
|
|
232
234
|
initialSelection: Object (null)
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
-
interface Props {
|
|
4
|
-
onPress: () => void;
|
|
5
|
-
style?: StyleProp<ViewStyle>;
|
|
6
|
-
}
|
|
7
|
-
declare const _default: React.MemoExoticComponent<({ onPress, style }: Props) => JSX.Element>;
|
|
8
|
-
export default _default;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import styled from '@emotion/native';
|
|
3
|
-
import { View } from 'react-native';
|
|
4
|
-
const Touchable = styled.TouchableOpacity(({ theme }) => ({
|
|
5
|
-
backgroundColor: 'transparent',
|
|
6
|
-
position: 'absolute',
|
|
7
|
-
right: theme.tokens.spacing2,
|
|
8
|
-
bottom: theme.tokens.spacing4,
|
|
9
|
-
zIndex: 100,
|
|
10
|
-
}));
|
|
11
|
-
const HIDE_ICON_SIZE = 14;
|
|
12
|
-
const HIDE_ICON_BORDER_WIDTH = 1;
|
|
13
|
-
const Inner = styled.View(({ theme }) => ({
|
|
14
|
-
position: 'absolute',
|
|
15
|
-
top: 0,
|
|
16
|
-
left: 0,
|
|
17
|
-
width: HIDE_ICON_SIZE,
|
|
18
|
-
height: HIDE_ICON_SIZE,
|
|
19
|
-
borderRadius: theme.navigation.visibilityBorderRadius,
|
|
20
|
-
overflow: 'hidden',
|
|
21
|
-
borderColor: theme.navigation.visibilityInnerBorderColor,
|
|
22
|
-
borderWidth: HIDE_ICON_BORDER_WIDTH * 2,
|
|
23
|
-
}));
|
|
24
|
-
const Outer = styled.View(({ theme }) => ({
|
|
25
|
-
position: 'absolute',
|
|
26
|
-
top: 0,
|
|
27
|
-
left: 0,
|
|
28
|
-
width: HIDE_ICON_SIZE,
|
|
29
|
-
height: HIDE_ICON_SIZE,
|
|
30
|
-
borderRadius: theme.navigation.visibilityBorderRadius,
|
|
31
|
-
overflow: 'hidden',
|
|
32
|
-
borderColor: theme.navigation.visibilityOuterBorderColor,
|
|
33
|
-
borderWidth: HIDE_ICON_BORDER_WIDTH,
|
|
34
|
-
}));
|
|
35
|
-
const hideIconStyles = {
|
|
36
|
-
width: HIDE_ICON_SIZE,
|
|
37
|
-
height: HIDE_ICON_SIZE,
|
|
38
|
-
marginRight: 4,
|
|
39
|
-
};
|
|
40
|
-
const HideIcon = () => (React.createElement(View, { style: hideIconStyles },
|
|
41
|
-
React.createElement(Inner, null),
|
|
42
|
-
React.createElement(Outer, null)));
|
|
43
|
-
const VisibilityButton = ({ onPress, style }) => (React.createElement(Touchable, { onPress: onPress, style: style, testID: "Storybook.OnDeviceUI.toggleUI", accessibilityLabel: "Storybook.OnDeviceUI.toggleUI", hitSlop: { top: 5, left: 5, bottom: 5, right: 5 } },
|
|
44
|
-
React.createElement(HideIcon, null)));
|
|
45
|
-
export default React.memo(VisibilityButton);
|