@storybook/react-native-ui 8.0.0-alpha.4 → 8.2.0-alpha.0
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/index.d.ts +15 -283
- package/dist/index.js +3138 -823
- package/package.json +10 -11
- package/src/Button.tsx +2 -74
- package/src/Explorer.stories.tsx +0 -6
- package/src/Explorer.tsx +0 -16
- package/src/Layout.stories.tsx +1 -6
- package/src/Layout.tsx +32 -25
- package/src/MobileAddonsPanel.tsx +141 -126
- package/src/MobileMenuDrawer.tsx +15 -10
- package/src/Refs.tsx +3 -63
- package/src/Search.tsx +4 -97
- package/src/SearchResults.tsx +3 -73
- package/src/Sidebar.stories.tsx +4 -42
- package/src/Sidebar.tsx +7 -70
- package/src/Tree.stories.tsx +2 -10
- package/src/Tree.tsx +7 -61
- package/src/TreeNode.stories.tsx +0 -5
- package/src/TreeNode.tsx +0 -1
- package/src/icon/CloseIcon.tsx +21 -20
- package/src/icon/MenuIcon.tsx +0 -1
- package/src/icon/SearchIcon.tsx +17 -14
- package/src/index.tsx +1 -0
- package/src/mockdata.ts +1 -1
- package/src/types.ts +3 -15
- package/src/useExpanded.ts +4 -62
- package/src/util/StoryHash.ts +244 -0
- package/src/util/status.tsx +1 -1
- package/src/util/tree.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storybook/react-native-ui",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0-alpha.0",
|
|
4
4
|
"description": "ui components for react native storybook",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -48,23 +48,22 @@
|
|
|
48
48
|
"preset": "react-native"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@storybook/react": "^8",
|
|
52
|
-
"@storybook/types": "^8",
|
|
53
51
|
"@types/jest": "^29.4.3",
|
|
54
|
-
"@types/react": "~18.2.
|
|
52
|
+
"@types/react": "~18.2.79",
|
|
55
53
|
"babel-jest": "^29.4.3",
|
|
56
|
-
"jest": "
|
|
54
|
+
"jest": "29.7.0",
|
|
57
55
|
"react-test-renderer": "18.2.0",
|
|
58
56
|
"tsup": "^7.2.0",
|
|
59
|
-
"typescript": "
|
|
57
|
+
"typescript": "~5.3.3"
|
|
60
58
|
},
|
|
61
59
|
"dependencies": {
|
|
62
|
-
"@storybook/core
|
|
63
|
-
"@storybook/
|
|
64
|
-
"@storybook/react-native-theming": "^8.
|
|
60
|
+
"@storybook/core": "8.2.0-beta.0",
|
|
61
|
+
"@storybook/react": "8.2.0-beta.0",
|
|
62
|
+
"@storybook/react-native-theming": "^8.2.0-alpha.0",
|
|
65
63
|
"fuse.js": "^7.0.0",
|
|
66
64
|
"memoizerific": "^1.11.3",
|
|
67
|
-
"polished": "^4.3.1"
|
|
65
|
+
"polished": "^4.3.1",
|
|
66
|
+
"store2": "^2.14.3"
|
|
68
67
|
},
|
|
69
68
|
"peerDependencies": {
|
|
70
69
|
"@gorhom/bottom-sheet": ">=4",
|
|
@@ -81,5 +80,5 @@
|
|
|
81
80
|
"publishConfig": {
|
|
82
81
|
"access": "public"
|
|
83
82
|
},
|
|
84
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "56cad3ccee28ceae70cc7e2fc9b63ec28384eecf"
|
|
85
84
|
}
|
package/src/Button.tsx
CHANGED
|
@@ -18,7 +18,6 @@ export interface ButtonProps extends TouchableOpacityProps {
|
|
|
18
18
|
export const Button = forwardRef<TouchableOpacity, ButtonProps>(
|
|
19
19
|
(
|
|
20
20
|
{
|
|
21
|
-
// asChild = false,
|
|
22
21
|
Icon,
|
|
23
22
|
animation = 'none',
|
|
24
23
|
size = 'small',
|
|
@@ -54,23 +53,8 @@ export const Button = forwardRef<TouchableOpacity, ButtonProps>(
|
|
|
54
53
|
return () => clearTimeout(timer);
|
|
55
54
|
}, [isAnimating]);
|
|
56
55
|
|
|
57
|
-
// Match the old API with the new API.
|
|
58
|
-
// TODO: Remove this after 9.0.
|
|
59
|
-
// if (props.primary) {
|
|
60
|
-
// localVariant = 'solid';
|
|
61
|
-
// localSize = 'medium';
|
|
62
|
-
// }
|
|
63
|
-
|
|
64
|
-
// Match the old API with the new API.
|
|
65
|
-
// TODO: Remove this after 9.0.
|
|
66
|
-
// if (props.secondary || props.tertiary || props.gray || props.outline || props.inForm) {
|
|
67
|
-
// localVariant = 'outline';
|
|
68
|
-
// localSize = 'medium';
|
|
69
|
-
// }
|
|
70
|
-
|
|
71
56
|
return (
|
|
72
57
|
<StyledButton
|
|
73
|
-
// as={Comp}
|
|
74
58
|
ref={ref}
|
|
75
59
|
variant={variant}
|
|
76
60
|
size={size}
|
|
@@ -96,19 +80,11 @@ export const Button = forwardRef<TouchableOpacity, ButtonProps>(
|
|
|
96
80
|
|
|
97
81
|
Button.displayName = 'Button';
|
|
98
82
|
|
|
99
|
-
// const StyledButton = styled('TouchableOpacity', {
|
|
100
|
-
// shouldForwardProp: (prop) => isPropValid(prop),
|
|
101
|
-
// })<
|
|
102
|
-
// ButtonProps & {
|
|
103
|
-
// animating: boolean;
|
|
104
|
-
// animation: ButtonProps['animation'];
|
|
105
|
-
// }
|
|
106
|
-
// >(({ theme, variant, size, disabled, active, animating, animation, padding }) => ({
|
|
107
83
|
const StyledButton = styled.TouchableOpacity<
|
|
108
84
|
ButtonProps & { animating: boolean; animation: ButtonProps['animation'] }
|
|
109
|
-
>(({ theme, variant, size, disabled, active,
|
|
85
|
+
>(({ theme, variant, size, disabled, active, padding }) => ({
|
|
110
86
|
border: 0,
|
|
111
|
-
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
87
|
+
// cursor: disabled ? 'not-allowed' : 'pointer',
|
|
112
88
|
display: 'flex',
|
|
113
89
|
flexDirection: 'row',
|
|
114
90
|
gap: 6,
|
|
@@ -125,12 +101,9 @@ const StyledButton = styled.TouchableOpacity<
|
|
|
125
101
|
paddingVertical: 0,
|
|
126
102
|
height: size === 'small' ? 28 : 32,
|
|
127
103
|
position: 'relative',
|
|
128
|
-
|
|
129
|
-
// textDecoration: 'none',
|
|
130
104
|
transitionProperty: 'background, box-shadow',
|
|
131
105
|
transitionDuration: '150ms',
|
|
132
106
|
transitionTimingFunction: 'ease-out',
|
|
133
|
-
// verticalAlign: 'top',
|
|
134
107
|
whiteSpace: 'nowrap',
|
|
135
108
|
userSelect: 'none',
|
|
136
109
|
opacity: disabled ? 0.5 : 1,
|
|
@@ -148,40 +121,6 @@ const StyledButton = styled.TouchableOpacity<
|
|
|
148
121
|
borderRadius: theme.input.borderRadius,
|
|
149
122
|
// Making sure that the button never shrinks below its minimum size
|
|
150
123
|
flexShrink: 0,
|
|
151
|
-
|
|
152
|
-
// '&:hover': {
|
|
153
|
-
// color: variant === 'ghost' ? theme.color.secondary : null,
|
|
154
|
-
// background: (() => {
|
|
155
|
-
// let bgColor = theme.color.secondary;
|
|
156
|
-
// if (variant === 'solid') bgColor = theme.color.secondary;
|
|
157
|
-
// if (variant === 'outline') bgColor = theme.button.background;
|
|
158
|
-
|
|
159
|
-
// if (variant === 'ghost') return transparentize(0.86, theme.color.secondary);
|
|
160
|
-
// return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
|
|
161
|
-
// })(),
|
|
162
|
-
// },
|
|
163
|
-
|
|
164
|
-
// '&:active': {
|
|
165
|
-
// color: variant === 'ghost' ? theme.color.secondary : null,
|
|
166
|
-
// background: (() => {
|
|
167
|
-
// let bgColor = theme.color.secondary;
|
|
168
|
-
// if (variant === 'solid') bgColor = theme.color.secondary;
|
|
169
|
-
// if (variant === 'outline') bgColor = theme.button.background;
|
|
170
|
-
|
|
171
|
-
// if (variant === 'ghost') return theme.background.hoverable;
|
|
172
|
-
// return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
|
|
173
|
-
// })(),
|
|
174
|
-
// },
|
|
175
|
-
|
|
176
|
-
// '&:focus': {
|
|
177
|
-
// boxShadow: `${rgba(theme.color.secondary, 1)} 0 0 0 1px inset`,
|
|
178
|
-
// outline: 'none',
|
|
179
|
-
// },
|
|
180
|
-
|
|
181
|
-
// '> svg': {
|
|
182
|
-
// animation:
|
|
183
|
-
// animating && animation !== 'none' ? `${theme.animation[animation]} 1000ms ease-out` : '',
|
|
184
|
-
// },
|
|
185
124
|
}));
|
|
186
125
|
|
|
187
126
|
export const ButtonText = styled.Text<{
|
|
@@ -198,7 +137,6 @@ export const ButtonText = styled.Text<{
|
|
|
198
137
|
flexDirection: 'row',
|
|
199
138
|
gap: 6,
|
|
200
139
|
textAlign: 'center',
|
|
201
|
-
// lineHeight: theme.typography.size.s1,
|
|
202
140
|
fontSize: theme.typography.size.s1,
|
|
203
141
|
fontWeight: theme.typography.weight.bold,
|
|
204
142
|
}));
|
|
@@ -231,13 +169,3 @@ export const ButtonIcon = ({
|
|
|
231
169
|
|
|
232
170
|
return <Icon color={color} />;
|
|
233
171
|
};
|
|
234
|
-
|
|
235
|
-
// color: variant === 'ghost' ? theme.color.secondary : null,
|
|
236
|
-
// background: (() => {
|
|
237
|
-
// let bgColor = theme.color.secondary;
|
|
238
|
-
// if (variant === 'solid') bgColor = theme.color.secondary;
|
|
239
|
-
// if (variant === 'outline') bgColor = theme.button.background;
|
|
240
|
-
|
|
241
|
-
// if (variant === 'ghost') return theme.background.hoverable;
|
|
242
|
-
// return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
|
|
243
|
-
// })(),
|
package/src/Explorer.stories.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ManagerContext } from '@storybook/manager-api';
|
|
2
1
|
import { Explorer } from './Explorer';
|
|
3
2
|
import { mockDataset } from './mockdata';
|
|
4
3
|
import type { RefType } from './types';
|
|
@@ -9,11 +8,6 @@ export default {
|
|
|
9
8
|
title: 'UI/Sidebar/Explorer',
|
|
10
9
|
parameters: { layout: 'fullscreen', theme: 'side-by-side' },
|
|
11
10
|
decorators: [
|
|
12
|
-
(storyFn: any) => (
|
|
13
|
-
<ManagerContext.Provider value={{ state: { docsOptions: {} } } as any}>
|
|
14
|
-
{storyFn()}
|
|
15
|
-
</ManagerContext.Provider>
|
|
16
|
-
),
|
|
17
11
|
(storyFn: any) => <View style={{ paddingHorizontal: 20 }}>{storyFn()}</View>,
|
|
18
12
|
(storyFn: any) => <View style={{ paddingHorizontal: 20 }}>{storyFn()}</View>,
|
|
19
13
|
],
|
package/src/Explorer.tsx
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { FC } from 'react';
|
|
2
2
|
import React, { useRef } from 'react';
|
|
3
|
-
|
|
4
3
|
import { Ref } from './Refs';
|
|
5
4
|
import type { CombinedDataset, Selection } from './types';
|
|
6
|
-
|
|
7
|
-
// import { useHighlighted } from './useHighlighted';
|
|
8
|
-
// import { HighlightStyles } from './HighlightStyles';
|
|
9
5
|
import { View } from 'react-native';
|
|
10
6
|
|
|
11
7
|
export interface ExplorerProps {
|
|
@@ -25,18 +21,8 @@ export const Explorer: FC<ExplorerProps> = React.memo(function Explorer({
|
|
|
25
21
|
}) {
|
|
26
22
|
const containerRef = useRef<View>(null);
|
|
27
23
|
|
|
28
|
-
// Track highlighted nodes, keep it in sync with props and enable keyboard navigation
|
|
29
|
-
// const [highlighted, setHighlighted, highlightedRef] = useHighlighted({
|
|
30
|
-
// containerRef,
|
|
31
|
-
// isLoading,
|
|
32
|
-
// isBrowsing,
|
|
33
|
-
// dataset,
|
|
34
|
-
// selected,
|
|
35
|
-
// });
|
|
36
|
-
|
|
37
24
|
return (
|
|
38
25
|
<View ref={containerRef} id="storybook-explorer-tree">
|
|
39
|
-
{/* {highlighted && <HighlightStyles {...highlighted} />} */}
|
|
40
26
|
{dataset.entries.map(([refId, ref]) => (
|
|
41
27
|
<Ref
|
|
42
28
|
{...ref}
|
|
@@ -45,8 +31,6 @@ export const Explorer: FC<ExplorerProps> = React.memo(function Explorer({
|
|
|
45
31
|
isBrowsing={isBrowsing}
|
|
46
32
|
selectedStoryId={selected?.refId === ref.id ? selected.storyId : null}
|
|
47
33
|
setSelection={setSelection}
|
|
48
|
-
// highlightedRef={highlightedRef}
|
|
49
|
-
// setHighlighted={setHighlighted}
|
|
50
34
|
/>
|
|
51
35
|
))}
|
|
52
36
|
</View>
|
package/src/Layout.stories.tsx
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
2
|
import { Layout } from './Layout';
|
|
4
|
-
// import { mockDataset } from './mockdata';
|
|
5
|
-
// import { index } from './mockdata.large';
|
|
6
3
|
import { mockDataset } from './mockdata';
|
|
7
|
-
// import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
8
|
-
// import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
|
9
4
|
import { Text } from 'react-native';
|
|
10
5
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
11
6
|
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
package/src/Layout.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { SET_CURRENT_STORY } from '@storybook/core-events';
|
|
2
|
-
import { addons } from '@storybook/manager-api';
|
|
1
|
+
import { SET_CURRENT_STORY } from '@storybook/core/core-events';
|
|
2
|
+
import { addons } from '@storybook/core/manager-api';
|
|
3
3
|
import { styled, useTheme } from '@storybook/react-native-theming';
|
|
4
|
-
import { type API_IndexHash, type Args, type StoryContext } from '@storybook/types';
|
|
5
|
-
import { ReactNode, useRef } from 'react';
|
|
6
|
-
import { Text, View } from 'react-native';
|
|
4
|
+
import { type API_IndexHash, type Args, type StoryContext } from '@storybook/core/types';
|
|
5
|
+
import { ReactNode, useRef, useState } from 'react';
|
|
6
|
+
import { Platform, Text, View } from 'react-native';
|
|
7
7
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
8
8
|
import { IconButton } from './IconButton';
|
|
9
9
|
import { Sidebar } from './Sidebar';
|
|
@@ -11,7 +11,7 @@ import { DEFAULT_REF_ID } from './constants';
|
|
|
11
11
|
import { BottomBarToggleIcon } from './icon/BottomBarToggleIcon';
|
|
12
12
|
import { MenuIcon } from './icon/MenuIcon';
|
|
13
13
|
|
|
14
|
-
import { ReactRenderer } from '@storybook/react';
|
|
14
|
+
import type { ReactRenderer } from '@storybook/react';
|
|
15
15
|
import { MobileMenuDrawer, MobileMenuDrawerRef } from './MobileMenuDrawer';
|
|
16
16
|
import { MobileAddonsPanel, MobileAddonsPanelRef } from './MobileAddonsPanel';
|
|
17
17
|
|
|
@@ -26,13 +26,15 @@ export const Layout = ({
|
|
|
26
26
|
}) => {
|
|
27
27
|
const theme = useTheme();
|
|
28
28
|
const mobileMenuDrawerRef = useRef<MobileMenuDrawerRef>(null);
|
|
29
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
30
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
29
31
|
const addonPanelRef = useRef<MobileAddonsPanelRef>(null);
|
|
30
32
|
const insets = useSafeAreaInsets();
|
|
31
33
|
|
|
32
34
|
return (
|
|
33
35
|
<View style={{ flex: 1, paddingTop: insets.top }}>
|
|
34
36
|
<View style={{ flex: 1 }}>{children}</View>
|
|
35
|
-
<MobileMenuDrawer ref={mobileMenuDrawerRef}>
|
|
37
|
+
<MobileMenuDrawer ref={mobileMenuDrawerRef} onStateChange={setDrawerOpen}>
|
|
36
38
|
<Sidebar
|
|
37
39
|
extra={[]}
|
|
38
40
|
previewInitialized
|
|
@@ -49,25 +51,30 @@ export const Layout = ({
|
|
|
49
51
|
refId={DEFAULT_REF_ID}
|
|
50
52
|
/>
|
|
51
53
|
</MobileMenuDrawer>
|
|
52
|
-
<MobileAddonsPanel ref={addonPanelRef} storyId={story?.id} />
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
<MobileAddonsPanel ref={addonPanelRef} storyId={story?.id} onStateChange={setMenuOpen} />
|
|
55
|
+
{(Platform.OS !== 'android' || (!menuOpen && !drawerOpen)) && (
|
|
56
|
+
<Container style={{ marginBottom: insets.bottom }}>
|
|
57
|
+
<Nav>
|
|
58
|
+
<Button
|
|
59
|
+
style={{ flexShrink: 1 }}
|
|
60
|
+
hitSlop={{ bottom: 10, left: 10, right: 10, top: 10 }}
|
|
61
|
+
onPress={() => {
|
|
62
|
+
mobileMenuDrawerRef.current.setMobileMenuOpen(true);
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<MenuIcon color={theme.color.mediumdark} />
|
|
66
|
+
<Text style={{ flexShrink: 1 }} numberOfLines={1}>
|
|
67
|
+
{story?.title}/{story?.name}
|
|
68
|
+
</Text>
|
|
69
|
+
</Button>
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
<IconButton
|
|
72
|
+
onPress={() => addonPanelRef.current.setAddonsPanelOpen(true)}
|
|
73
|
+
Icon={BottomBarToggleIcon}
|
|
74
|
+
/>
|
|
75
|
+
</Nav>
|
|
76
|
+
</Container>
|
|
77
|
+
)}
|
|
71
78
|
</View>
|
|
72
79
|
);
|
|
73
80
|
};
|
|
@@ -1,143 +1,159 @@
|
|
|
1
|
-
import { BottomSheetModal
|
|
2
|
-
import { addons } from '@storybook/manager-api';
|
|
1
|
+
import { BottomSheetModal } from '@gorhom/bottom-sheet';
|
|
2
|
+
import { addons } from '@storybook/core/manager-api';
|
|
3
3
|
import { styled } from '@storybook/react-native-theming';
|
|
4
|
-
import { Addon_TypesEnum } from '@storybook/types';
|
|
4
|
+
import { Addon_TypesEnum } from '@storybook/core/types';
|
|
5
5
|
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
|
6
|
-
import { Text, View } from 'react-native';
|
|
6
|
+
import { Text, View, useWindowDimensions } from 'react-native';
|
|
7
7
|
import { ScrollView } from 'react-native-gesture-handler';
|
|
8
|
-
import {
|
|
8
|
+
import Animated, {
|
|
9
|
+
useAnimatedKeyboard,
|
|
10
|
+
useAnimatedStyle,
|
|
11
|
+
useReducedMotion,
|
|
12
|
+
useSharedValue,
|
|
13
|
+
} from 'react-native-reanimated';
|
|
9
14
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
10
15
|
|
|
11
16
|
import { IconButton } from './IconButton';
|
|
12
17
|
import { CloseIcon } from './icon/CloseIcon';
|
|
13
18
|
|
|
14
19
|
export interface MobileAddonsPanelRef {
|
|
15
|
-
isAddonsPanelOpen: boolean;
|
|
16
20
|
setAddonsPanelOpen: (isOpen: boolean) => void;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
export const MobileAddonsPanel = forwardRef<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
onPress={() => setAddonSelected(id)}
|
|
88
|
-
text={resolvedTitle}
|
|
89
|
-
/>
|
|
90
|
-
);
|
|
91
|
-
})}
|
|
92
|
-
</ScrollView>
|
|
93
|
-
<IconButton
|
|
94
|
-
style={{
|
|
95
|
-
marginRight: 4,
|
|
96
|
-
marginBottom: 4,
|
|
97
|
-
alignItems: 'center',
|
|
98
|
-
justifyContent: 'center',
|
|
99
|
-
}}
|
|
100
|
-
hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
|
|
101
|
-
Icon={CloseIcon}
|
|
102
|
-
onPress={() => {
|
|
103
|
-
setAddonsPanelOpen(false);
|
|
104
|
-
addonsPanelBottomSheetRef.current?.dismiss();
|
|
105
|
-
}}
|
|
106
|
-
/>
|
|
107
|
-
</View>
|
|
108
|
-
<BottomSheetScrollView
|
|
109
|
-
keyboardShouldPersistTaps="handled"
|
|
23
|
+
export const MobileAddonsPanel = forwardRef<
|
|
24
|
+
MobileAddonsPanelRef,
|
|
25
|
+
{ storyId?: string; onStateChange: (open: boolean) => void }
|
|
26
|
+
>(({ storyId, onStateChange }, ref) => {
|
|
27
|
+
const reducedMotion = useReducedMotion();
|
|
28
|
+
|
|
29
|
+
const addonsPanelBottomSheetRef = useRef<BottomSheetModal>(null);
|
|
30
|
+
const insets = useSafeAreaInsets();
|
|
31
|
+
|
|
32
|
+
const panels = addons.getElements(Addon_TypesEnum.PANEL);
|
|
33
|
+
const [addonSelected, setAddonSelected] = useState(Object.keys(panels)[0]);
|
|
34
|
+
const animatedPosition = useSharedValue(0);
|
|
35
|
+
|
|
36
|
+
// bringing in animated keyboard disables android resizing
|
|
37
|
+
// TODO replicate functionality without this
|
|
38
|
+
useAnimatedKeyboard();
|
|
39
|
+
|
|
40
|
+
useImperativeHandle(ref, () => ({
|
|
41
|
+
setAddonsPanelOpen: (open: boolean) => {
|
|
42
|
+
if (open) {
|
|
43
|
+
onStateChange(true);
|
|
44
|
+
addonsPanelBottomSheetRef.current?.present();
|
|
45
|
+
} else {
|
|
46
|
+
onStateChange(false);
|
|
47
|
+
addonsPanelBottomSheetRef.current?.dismiss();
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
const { height } = useWindowDimensions();
|
|
53
|
+
|
|
54
|
+
const adjustedBottomSheetSize = useAnimatedStyle(() => {
|
|
55
|
+
return {
|
|
56
|
+
maxHeight: height - animatedPosition.value - insets.bottom,
|
|
57
|
+
};
|
|
58
|
+
}, [animatedPosition.value, height, insets.bottom]);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<BottomSheetModal
|
|
62
|
+
ref={addonsPanelBottomSheetRef}
|
|
63
|
+
index={1}
|
|
64
|
+
animateOnMount={!reducedMotion}
|
|
65
|
+
onDismiss={() => {
|
|
66
|
+
onStateChange(false);
|
|
67
|
+
}}
|
|
68
|
+
snapPoints={['25%', '50%', '75%']}
|
|
69
|
+
style={{
|
|
70
|
+
paddingTop: 8,
|
|
71
|
+
}}
|
|
72
|
+
animatedPosition={animatedPosition}
|
|
73
|
+
containerStyle={{}}
|
|
74
|
+
backgroundStyle={{
|
|
75
|
+
borderRadius: 0,
|
|
76
|
+
borderTopColor: 'lightgrey',
|
|
77
|
+
borderTopWidth: 1,
|
|
78
|
+
}}
|
|
79
|
+
keyboardBehavior="extend"
|
|
80
|
+
// keyboardBlurBehavior="restore"
|
|
81
|
+
enableDismissOnClose
|
|
82
|
+
enableHandlePanningGesture={true}
|
|
83
|
+
// enableContentPanningGesture={true}
|
|
84
|
+
stackBehavior="replace"
|
|
85
|
+
>
|
|
86
|
+
<Animated.View style={[{ flex: 1 }, adjustedBottomSheetSize]}>
|
|
87
|
+
<View style={{ flexDirection: 'row' }}>
|
|
88
|
+
<ScrollView
|
|
89
|
+
horizontal
|
|
90
|
+
showsHorizontalScrollIndicator={false}
|
|
110
91
|
contentContainerStyle={{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
paddingHorizontal: 16,
|
|
92
|
+
paddingHorizontal: 8,
|
|
93
|
+
justifyContent: 'center',
|
|
114
94
|
}}
|
|
115
95
|
>
|
|
116
|
-
{(() => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
96
|
+
{Object.values(panels).map(({ id, title }) => {
|
|
97
|
+
const resolvedTitle = typeof title === 'function' ? title({}) : title;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Tab
|
|
101
|
+
key={id}
|
|
102
|
+
active={id === addonSelected}
|
|
103
|
+
onPress={() => setAddonSelected(id)}
|
|
104
|
+
text={resolvedTitle}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</ScrollView>
|
|
109
|
+
<IconButton
|
|
110
|
+
style={{
|
|
111
|
+
marginRight: 4,
|
|
112
|
+
marginBottom: 4,
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
justifyContent: 'center',
|
|
115
|
+
}}
|
|
116
|
+
hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
|
|
117
|
+
Icon={CloseIcon}
|
|
118
|
+
onPress={() => {
|
|
119
|
+
onStateChange(false);
|
|
120
|
+
addonsPanelBottomSheetRef.current?.dismiss();
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
136
123
|
</View>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
124
|
+
<ScrollView
|
|
125
|
+
style={{ flex: 1 }}
|
|
126
|
+
keyboardShouldPersistTaps="handled"
|
|
127
|
+
contentContainerStyle={{
|
|
128
|
+
paddingBottom: insets.bottom + 16,
|
|
129
|
+
marginTop: 10,
|
|
130
|
+
paddingHorizontal: 16,
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
{(() => {
|
|
134
|
+
if (!storyId) {
|
|
135
|
+
return (
|
|
136
|
+
<View style={{ alignItems: 'center', justifyContent: 'center' }}>
|
|
137
|
+
<Text>No Story Selected</Text>
|
|
138
|
+
</View>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (Object.keys(panels).length === 0) {
|
|
143
|
+
return (
|
|
144
|
+
<View style={{ alignItems: 'center', justifyContent: 'center' }}>
|
|
145
|
+
<Text>No addons loaded.</Text>
|
|
146
|
+
</View>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return panels[addonSelected].render({ active: true });
|
|
151
|
+
})()}
|
|
152
|
+
</ScrollView>
|
|
153
|
+
</Animated.View>
|
|
154
|
+
</BottomSheetModal>
|
|
155
|
+
);
|
|
156
|
+
});
|
|
141
157
|
|
|
142
158
|
const Tab = ({ active, onPress, text }: { active: boolean; onPress: () => void; text: string }) => {
|
|
143
159
|
return (
|
|
@@ -150,7 +166,6 @@ const Tab = ({ active, onPress, text }: { active: boolean; onPress: () => void;
|
|
|
150
166
|
const TabButton = styled.TouchableOpacity<{ active: boolean }>(({ theme, active }) => ({
|
|
151
167
|
borderBottomWidth: active ? 2 : 0,
|
|
152
168
|
borderBottomColor: active ? theme.barSelectedColor : undefined,
|
|
153
|
-
|
|
154
169
|
overflow: 'hidden',
|
|
155
170
|
paddingHorizontal: 15,
|
|
156
171
|
justifyContent: 'center',
|
|
@@ -161,6 +176,6 @@ const TabText = styled.Text<{ active: boolean }>(({ theme, active }) => ({
|
|
|
161
176
|
color: active ? theme.barSelectedColor : theme.color.mediumdark,
|
|
162
177
|
textAlign: 'center',
|
|
163
178
|
fontWeight: 'bold',
|
|
164
|
-
fontSize:
|
|
179
|
+
fontSize: 12,
|
|
165
180
|
lineHeight: 12,
|
|
166
181
|
}));
|