@fluentui-react-native/storybook-desktop-runtime 0.2.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/.cache/tsconfig.tsbuildinfo +1 -0
- package/CHANGELOG.md +25 -0
- package/README.md +21 -0
- package/config/metro.cjs +125 -0
- package/config/react-native-safe-area-context.cjs +22 -0
- package/lib/DesktopDriverBridge.d.ts +7 -0
- package/lib/DesktopDriverBridge.d.ts.map +1 -0
- package/lib/DesktopDriverBridge.js +52 -0
- package/lib/DesktopDriverBridge.js.map +1 -0
- package/lib/DesktopStoryRoot.d.ts +8 -0
- package/lib/DesktopStoryRoot.d.ts.map +1 -0
- package/lib/DesktopStoryRoot.js +57 -0
- package/lib/DesktopStoryRoot.js.map +1 -0
- package/lib/DesktopStorybookConfig.d.ts +28 -0
- package/lib/DesktopStorybookConfig.d.ts.map +1 -0
- package/lib/DesktopStorybookConfig.js +23 -0
- package/lib/DesktopStorybookConfig.js.map +1 -0
- package/lib/StorybookTheme.d.ts +4 -0
- package/lib/StorybookTheme.d.ts.map +1 -0
- package/lib/StorybookTheme.js +79 -0
- package/lib/StorybookTheme.js.map +1 -0
- package/lib/StorybookUI.d.ts +2 -0
- package/lib/StorybookUI.d.ts.map +1 -0
- package/lib/StorybookUI.js +2 -0
- package/lib/StorybookUI.js.map +1 -0
- package/lib/StorybookUI.win32.d.ts +3 -0
- package/lib/StorybookUI.win32.d.ts.map +1 -0
- package/lib/StorybookUI.win32.js +171 -0
- package/lib/StorybookUI.win32.js.map +1 -0
- package/lib/Win32AddonsPanel.d.ts +9 -0
- package/lib/Win32AddonsPanel.d.ts.map +1 -0
- package/lib/Win32AddonsPanel.js +72 -0
- package/lib/Win32AddonsPanel.js.map +1 -0
- package/lib/Win32CalloutPortal.d.ts +16 -0
- package/lib/Win32CalloutPortal.d.ts.map +1 -0
- package/lib/Win32CalloutPortal.js +17 -0
- package/lib/Win32CalloutPortal.js.map +1 -0
- package/lib/Win32ResizeHandle.d.ts +8 -0
- package/lib/Win32ResizeHandle.d.ts.map +1 -0
- package/lib/Win32ResizeHandle.js +46 -0
- package/lib/Win32ResizeHandle.js.map +1 -0
- package/lib/createDesktopStorybookApp.d.ts +9 -0
- package/lib/createDesktopStorybookApp.d.ts.map +1 -0
- package/lib/createDesktopStorybookApp.js +38 -0
- package/lib/createDesktopStorybookApp.js.map +1 -0
- package/lib/createDesktopStorybookPreview.d.ts +3 -0
- package/lib/createDesktopStorybookPreview.d.ts.map +1 -0
- package/lib/createDesktopStorybookPreview.js +21 -0
- package/lib/createDesktopStorybookPreview.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -0
- package/package.json +106 -0
- package/src/DesktopDriverBridge.tsx +66 -0
- package/src/DesktopStoryRoot.tsx +88 -0
- package/src/DesktopStorybookConfig.tsx +58 -0
- package/src/StorybookTheme.tsx +114 -0
- package/src/StorybookUI.ts +1 -0
- package/src/StorybookUI.win32.tsx +330 -0
- package/src/Win32AddonsPanel.tsx +135 -0
- package/src/Win32CalloutPortal.tsx +73 -0
- package/src/Win32ResizeHandle.tsx +67 -0
- package/src/createDesktopStorybookApp.tsx +72 -0
- package/src/createDesktopStorybookPreview.tsx +31 -0
- package/src/index.ts +12 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { ThemeProvider } from '@storybook/react-native-theming';
|
|
5
|
+
import { StorageProvider } from '@storybook/react-native-ui-common';
|
|
6
|
+
import type { SBUI, Selection } from '@storybook/react-native-ui-common';
|
|
7
|
+
import { Sidebar } from '@storybook/react-native-ui-lite';
|
|
8
|
+
|
|
9
|
+
import { useDesktopStorybookTestID } from './DesktopStorybookConfig';
|
|
10
|
+
import { Win32AddonsPanel } from './Win32AddonsPanel';
|
|
11
|
+
import { Win32CalloutPortal } from './Win32CalloutPortal';
|
|
12
|
+
import { Win32ResizeHandle } from './Win32ResizeHandle';
|
|
13
|
+
|
|
14
|
+
const emptyRefs = {};
|
|
15
|
+
const emptyStatus = {};
|
|
16
|
+
|
|
17
|
+
export const StorybookUIComponent: SBUI = ({ children, setStory, storage, story, storyHash, theme }) => {
|
|
18
|
+
const storiesAnchorRef = React.useRef<View>(null);
|
|
19
|
+
const addonsAnchorRef = React.useRef<View>(null);
|
|
20
|
+
const [sidebarVisible, setSidebarVisible] = React.useState(true);
|
|
21
|
+
const [sidebarWidth, setSidebarWidth] = React.useState(280);
|
|
22
|
+
const [addonsVisible, setAddonsVisible] = React.useState(true);
|
|
23
|
+
const [addonsHeight, setAddonsHeight] = React.useState(280);
|
|
24
|
+
const [storyDrawerOpen, setStoryDrawerOpen] = React.useState(false);
|
|
25
|
+
const [addonsPanelOpen, setAddonsPanelOpen] = React.useState(false);
|
|
26
|
+
const chromeTestID = useDesktopStorybookTestID('win32-chrome');
|
|
27
|
+
const sidebarTestID = useDesktopStorybookTestID('win32-sidebar');
|
|
28
|
+
const sidebarResizeTestID = useDesktopStorybookTestID('win32-sidebar-resize');
|
|
29
|
+
const toolbarTestID = useDesktopStorybookTestID('win32-desktop-toolbar');
|
|
30
|
+
const showSidebarTestID = useDesktopStorybookTestID('win32-show-sidebar');
|
|
31
|
+
const popoutStoriesTestID = useDesktopStorybookTestID('win32-popout-stories');
|
|
32
|
+
const showAddonsTestID = useDesktopStorybookTestID('win32-show-addons');
|
|
33
|
+
const popoutAddonsTestID = useDesktopStorybookTestID('win32-popout-addons');
|
|
34
|
+
const previewTestID = useDesktopStorybookTestID('win32-preview');
|
|
35
|
+
const addonsResizeTestID = useDesktopStorybookTestID('win32-addons-resize');
|
|
36
|
+
const inlineAddonsTestID = useDesktopStorybookTestID('win32-inline-addons');
|
|
37
|
+
const storyDrawerTestID = useDesktopStorybookTestID('win32-story-drawer');
|
|
38
|
+
const closeStoriesTestID = useDesktopStorybookTestID('win32-close-stories');
|
|
39
|
+
const addonsDrawerTestID = useDesktopStorybookTestID('win32-addons-drawer');
|
|
40
|
+
const hideSidebarTestID = useDesktopStorybookTestID('win32-hide-sidebar');
|
|
41
|
+
|
|
42
|
+
const setSelection = React.useCallback(
|
|
43
|
+
(selection: Selection) => {
|
|
44
|
+
if (selection?.storyId) {
|
|
45
|
+
setStory(selection.storyId);
|
|
46
|
+
setStoryDrawerOpen(false);
|
|
47
|
+
setSidebarVisible(true);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
[setStory],
|
|
51
|
+
);
|
|
52
|
+
const closeStoryDrawer = React.useCallback(() => {
|
|
53
|
+
setStoryDrawerOpen(false);
|
|
54
|
+
setSidebarVisible(true);
|
|
55
|
+
}, []);
|
|
56
|
+
const closeAddonsDrawer = React.useCallback(() => {
|
|
57
|
+
setAddonsPanelOpen(false);
|
|
58
|
+
setAddonsVisible(true);
|
|
59
|
+
}, []);
|
|
60
|
+
const resizeSidebar = React.useCallback((delta: number) => {
|
|
61
|
+
setSidebarWidth((current) => Math.min(Math.max(current + delta, 200), 480));
|
|
62
|
+
}, []);
|
|
63
|
+
const resizeAddons = React.useCallback((delta: number) => {
|
|
64
|
+
setAddonsHeight((current) => Math.min(Math.max(current - delta, 160), 520));
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<ThemeProvider theme={theme}>
|
|
69
|
+
<StorageProvider storage={storage}>
|
|
70
|
+
<View style={styles.root} testID={chromeTestID}>
|
|
71
|
+
{sidebarVisible ? (
|
|
72
|
+
<>
|
|
73
|
+
<View
|
|
74
|
+
style={[
|
|
75
|
+
styles.sidebar,
|
|
76
|
+
{
|
|
77
|
+
backgroundColor: theme.background.content,
|
|
78
|
+
borderColor: theme.appBorderColor,
|
|
79
|
+
width: sidebarWidth,
|
|
80
|
+
},
|
|
81
|
+
]}
|
|
82
|
+
testID={sidebarTestID}
|
|
83
|
+
>
|
|
84
|
+
<View accessibilityLabel="Storybook sidebar" accessible style={styles.sidebarHeader} testID={`${sidebarTestID}-header`}>
|
|
85
|
+
<Text style={[styles.sidebarTitle, { color: theme.barTextColor }]}>Stories</Text>
|
|
86
|
+
<Pressable
|
|
87
|
+
accessibilityLabel="Hide story sidebar"
|
|
88
|
+
accessibilityRole="button"
|
|
89
|
+
onPress={() => setSidebarVisible(false)}
|
|
90
|
+
style={styles.headerButton}
|
|
91
|
+
testID={hideSidebarTestID}
|
|
92
|
+
>
|
|
93
|
+
<Text style={{ color: theme.color.mediumdark }}>Hide</Text>
|
|
94
|
+
</Pressable>
|
|
95
|
+
</View>
|
|
96
|
+
<View style={styles.sidebarContent}>
|
|
97
|
+
<Sidebar
|
|
98
|
+
index={storyHash}
|
|
99
|
+
indexError={undefined}
|
|
100
|
+
previewInitialized
|
|
101
|
+
refs={emptyRefs}
|
|
102
|
+
setSelection={setSelection}
|
|
103
|
+
status={emptyStatus}
|
|
104
|
+
storyId={story?.id}
|
|
105
|
+
/>
|
|
106
|
+
</View>
|
|
107
|
+
</View>
|
|
108
|
+
<Win32ResizeHandle direction="horizontal" onResize={resizeSidebar} testID={sidebarResizeTestID} />
|
|
109
|
+
</>
|
|
110
|
+
) : null}
|
|
111
|
+
<View style={styles.main}>
|
|
112
|
+
{!sidebarVisible ? (
|
|
113
|
+
<View
|
|
114
|
+
accessibilityLabel="Storybook desktop toolbar"
|
|
115
|
+
accessible
|
|
116
|
+
style={[styles.toolbar, { backgroundColor: theme.background.content, borderBottomColor: theme.appBorderColor }]}
|
|
117
|
+
testID={toolbarTestID}
|
|
118
|
+
>
|
|
119
|
+
<Pressable
|
|
120
|
+
accessibilityRole="button"
|
|
121
|
+
onPress={() => setSidebarVisible(true)}
|
|
122
|
+
style={[styles.toolbarButton, { backgroundColor: theme.button.background, borderColor: theme.appBorderColor }]}
|
|
123
|
+
testID={showSidebarTestID}
|
|
124
|
+
>
|
|
125
|
+
<Text style={{ color: theme.color.mediumdark }}>Show stories</Text>
|
|
126
|
+
</Pressable>
|
|
127
|
+
<Pressable
|
|
128
|
+
accessibilityRole="button"
|
|
129
|
+
onPress={() => setStoryDrawerOpen(true)}
|
|
130
|
+
ref={storiesAnchorRef}
|
|
131
|
+
style={({ pressed }) => [
|
|
132
|
+
styles.toolbarButton,
|
|
133
|
+
{
|
|
134
|
+
backgroundColor: storyDrawerOpen ? theme.barSelectedColor : theme.button.background,
|
|
135
|
+
borderColor: theme.appBorderColor,
|
|
136
|
+
opacity: pressed ? 0.75 : 1,
|
|
137
|
+
},
|
|
138
|
+
]}
|
|
139
|
+
testID={popoutStoriesTestID}
|
|
140
|
+
>
|
|
141
|
+
<Text style={{ color: storyDrawerOpen ? theme.color.lightest : theme.color.mediumdark }}>Pop out stories</Text>
|
|
142
|
+
</Pressable>
|
|
143
|
+
{!addonsVisible ? (
|
|
144
|
+
<Pressable
|
|
145
|
+
accessibilityRole="button"
|
|
146
|
+
onPress={() => setAddonsVisible(true)}
|
|
147
|
+
style={[styles.toolbarButton, { backgroundColor: theme.button.background, borderColor: theme.appBorderColor }]}
|
|
148
|
+
testID={showAddonsTestID}
|
|
149
|
+
>
|
|
150
|
+
<Text style={{ color: theme.color.mediumdark }}>Show addons</Text>
|
|
151
|
+
</Pressable>
|
|
152
|
+
) : null}
|
|
153
|
+
<Pressable
|
|
154
|
+
accessibilityRole="button"
|
|
155
|
+
onPress={() => {
|
|
156
|
+
setAddonsVisible(false);
|
|
157
|
+
setAddonsPanelOpen(true);
|
|
158
|
+
}}
|
|
159
|
+
ref={addonsAnchorRef}
|
|
160
|
+
style={({ pressed }) => [
|
|
161
|
+
styles.toolbarButton,
|
|
162
|
+
{
|
|
163
|
+
backgroundColor: addonsPanelOpen ? theme.barSelectedColor : theme.button.background,
|
|
164
|
+
borderColor: theme.appBorderColor,
|
|
165
|
+
opacity: pressed ? 0.75 : 1,
|
|
166
|
+
},
|
|
167
|
+
]}
|
|
168
|
+
testID={popoutAddonsTestID}
|
|
169
|
+
>
|
|
170
|
+
<Text style={{ color: addonsPanelOpen ? theme.color.lightest : theme.color.mediumdark }}>Pop out addons</Text>
|
|
171
|
+
</Pressable>
|
|
172
|
+
<Text numberOfLines={1} style={[styles.currentStory, { color: theme.textMutedColor }]}>
|
|
173
|
+
{story ? `${story.title}/${story.name}` : 'No story selected'}
|
|
174
|
+
</Text>
|
|
175
|
+
</View>
|
|
176
|
+
) : null}
|
|
177
|
+
<View style={styles.preview} testID={previewTestID}>
|
|
178
|
+
{children}
|
|
179
|
+
</View>
|
|
180
|
+
{addonsVisible ? (
|
|
181
|
+
<>
|
|
182
|
+
<Win32ResizeHandle direction="vertical" onResize={resizeAddons} testID={addonsResizeTestID} />
|
|
183
|
+
<View
|
|
184
|
+
style={[
|
|
185
|
+
styles.addons,
|
|
186
|
+
{
|
|
187
|
+
backgroundColor: theme.background.content,
|
|
188
|
+
height: addonsHeight,
|
|
189
|
+
},
|
|
190
|
+
]}
|
|
191
|
+
testID={inlineAddonsTestID}
|
|
192
|
+
>
|
|
193
|
+
<Win32AddonsPanel onClose={() => setAddonsVisible(false)} parameters={story?.parameters} storyId={story?.id} />
|
|
194
|
+
</View>
|
|
195
|
+
</>
|
|
196
|
+
) : null}
|
|
197
|
+
</View>
|
|
198
|
+
<Win32CalloutPortal
|
|
199
|
+
accessibilityLabel="Storybook stories"
|
|
200
|
+
directionalHint="bottomLeftEdge"
|
|
201
|
+
height={600}
|
|
202
|
+
onDismiss={closeStoryDrawer}
|
|
203
|
+
target={storiesAnchorRef}
|
|
204
|
+
testID={storyDrawerTestID}
|
|
205
|
+
visible={storyDrawerOpen}
|
|
206
|
+
width={360}
|
|
207
|
+
>
|
|
208
|
+
<View style={styles.drawerHeader}>
|
|
209
|
+
<Text style={[styles.drawerTitle, { color: theme.barTextColor }]}>Stories</Text>
|
|
210
|
+
<Pressable
|
|
211
|
+
accessibilityLabel="Close story drawer"
|
|
212
|
+
accessibilityRole="button"
|
|
213
|
+
onPress={closeStoryDrawer}
|
|
214
|
+
style={styles.closeButton}
|
|
215
|
+
testID={closeStoriesTestID}
|
|
216
|
+
>
|
|
217
|
+
<Text style={{ color: theme.color.mediumdark }}>Close</Text>
|
|
218
|
+
</Pressable>
|
|
219
|
+
</View>
|
|
220
|
+
<View style={styles.drawerContent}>
|
|
221
|
+
<Sidebar
|
|
222
|
+
index={storyHash}
|
|
223
|
+
indexError={undefined}
|
|
224
|
+
previewInitialized
|
|
225
|
+
refs={emptyRefs}
|
|
226
|
+
setSelection={setSelection}
|
|
227
|
+
status={emptyStatus}
|
|
228
|
+
storyId={story?.id}
|
|
229
|
+
/>
|
|
230
|
+
</View>
|
|
231
|
+
</Win32CalloutPortal>
|
|
232
|
+
<Win32CalloutPortal
|
|
233
|
+
accessibilityLabel="Storybook addons"
|
|
234
|
+
directionalHint="bottomLeftEdge"
|
|
235
|
+
height={480}
|
|
236
|
+
onDismiss={closeAddonsDrawer}
|
|
237
|
+
target={addonsAnchorRef}
|
|
238
|
+
testID={addonsDrawerTestID}
|
|
239
|
+
visible={addonsPanelOpen}
|
|
240
|
+
width={520}
|
|
241
|
+
>
|
|
242
|
+
<Win32AddonsPanel onClose={closeAddonsDrawer} parameters={story?.parameters} storyId={story?.id} />
|
|
243
|
+
</Win32CalloutPortal>
|
|
244
|
+
</View>
|
|
245
|
+
</StorageProvider>
|
|
246
|
+
</ThemeProvider>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const styles = StyleSheet.create({
|
|
251
|
+
root: {
|
|
252
|
+
flex: 1,
|
|
253
|
+
flexDirection: 'row',
|
|
254
|
+
},
|
|
255
|
+
sidebar: {
|
|
256
|
+
borderRightWidth: 1,
|
|
257
|
+
},
|
|
258
|
+
sidebarHeader: {
|
|
259
|
+
alignItems: 'center',
|
|
260
|
+
flexDirection: 'row',
|
|
261
|
+
justifyContent: 'space-between',
|
|
262
|
+
minHeight: 44,
|
|
263
|
+
paddingHorizontal: 12,
|
|
264
|
+
},
|
|
265
|
+
sidebarTitle: {
|
|
266
|
+
fontSize: 16,
|
|
267
|
+
fontWeight: '600',
|
|
268
|
+
},
|
|
269
|
+
sidebarContent: {
|
|
270
|
+
flex: 1,
|
|
271
|
+
},
|
|
272
|
+
headerButton: {
|
|
273
|
+
alignItems: 'center',
|
|
274
|
+
justifyContent: 'center',
|
|
275
|
+
minHeight: 32,
|
|
276
|
+
paddingHorizontal: 8,
|
|
277
|
+
},
|
|
278
|
+
main: {
|
|
279
|
+
flex: 1,
|
|
280
|
+
},
|
|
281
|
+
toolbar: {
|
|
282
|
+
alignItems: 'center',
|
|
283
|
+
borderBottomWidth: 1,
|
|
284
|
+
flexDirection: 'row',
|
|
285
|
+
gap: 8,
|
|
286
|
+
minHeight: 44,
|
|
287
|
+
paddingHorizontal: 10,
|
|
288
|
+
paddingVertical: 6,
|
|
289
|
+
},
|
|
290
|
+
toolbarButton: {
|
|
291
|
+
alignItems: 'center',
|
|
292
|
+
borderRadius: 4,
|
|
293
|
+
borderWidth: 1,
|
|
294
|
+
justifyContent: 'center',
|
|
295
|
+
minHeight: 30,
|
|
296
|
+
paddingHorizontal: 12,
|
|
297
|
+
},
|
|
298
|
+
currentStory: {
|
|
299
|
+
flex: 1,
|
|
300
|
+
fontSize: 11,
|
|
301
|
+
marginStart: 4,
|
|
302
|
+
},
|
|
303
|
+
addons: {
|
|
304
|
+
minHeight: 160,
|
|
305
|
+
},
|
|
306
|
+
drawerHeader: {
|
|
307
|
+
alignItems: 'center',
|
|
308
|
+
flexDirection: 'row',
|
|
309
|
+
justifyContent: 'space-between',
|
|
310
|
+
minHeight: 44,
|
|
311
|
+
paddingHorizontal: 12,
|
|
312
|
+
},
|
|
313
|
+
drawerTitle: {
|
|
314
|
+
fontSize: 16,
|
|
315
|
+
fontWeight: '600',
|
|
316
|
+
},
|
|
317
|
+
closeButton: {
|
|
318
|
+
alignItems: 'center',
|
|
319
|
+
justifyContent: 'center',
|
|
320
|
+
minHeight: 32,
|
|
321
|
+
paddingHorizontal: 8,
|
|
322
|
+
},
|
|
323
|
+
drawerContent: {
|
|
324
|
+
flex: 1,
|
|
325
|
+
minHeight: 420,
|
|
326
|
+
},
|
|
327
|
+
preview: {
|
|
328
|
+
flex: 1,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '@storybook/react-native-theming';
|
|
5
|
+
import type { Parameters } from 'storybook/internal/csf';
|
|
6
|
+
import { Addon_TypesEnum } from 'storybook/internal/types';
|
|
7
|
+
import type { Addon_BaseType, Addon_Collection } from 'storybook/internal/types';
|
|
8
|
+
import { addons } from 'storybook/manager-api';
|
|
9
|
+
|
|
10
|
+
import { useDesktopStorybookTestID } from './DesktopStorybookConfig';
|
|
11
|
+
|
|
12
|
+
type Win32AddonsPanelProps = {
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
parameters?: Parameters;
|
|
15
|
+
storyId?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function preferredPanelId(panels: [string, Addon_BaseType][]) {
|
|
19
|
+
return panels.find(([id]) => id.includes('actions'))?.[0] ?? panels[0]?.[0];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Win32AddonsPanel({ onClose, parameters, storyId }: Win32AddonsPanelProps) {
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
const panelTestID = useDesktopStorybookTestID('win32-addons-panel');
|
|
25
|
+
const addonTestID = useDesktopStorybookTestID('win32-addon');
|
|
26
|
+
const closeTestID = useDesktopStorybookTestID('win32-close-addons');
|
|
27
|
+
const panels = React.useMemo(() => {
|
|
28
|
+
const allPanels: Addon_Collection<Addon_BaseType> = addons.getElements(Addon_TypesEnum.PANEL);
|
|
29
|
+
return Object.entries(allPanels).filter(([, panel]) => !panel.paramKey || !parameters?.[panel.paramKey]?.disable);
|
|
30
|
+
}, [parameters]);
|
|
31
|
+
const [selectedPanelId, setSelectedPanelId] = React.useState(() => preferredPanelId(panels));
|
|
32
|
+
const activePanel = panels.find(([id]) => id === selectedPanelId) ?? panels[0];
|
|
33
|
+
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (!panels.some(([id]) => id === selectedPanelId)) {
|
|
36
|
+
setSelectedPanelId(preferredPanelId(panels));
|
|
37
|
+
}
|
|
38
|
+
}, [panels, selectedPanelId]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View style={styles.root} testID={panelTestID}>
|
|
42
|
+
<View
|
|
43
|
+
accessibilityLabel="Storybook addons panel"
|
|
44
|
+
accessible
|
|
45
|
+
style={[styles.header, { borderBottomColor: theme.appBorderColor }]}
|
|
46
|
+
testID={`${panelTestID}-header`}
|
|
47
|
+
>
|
|
48
|
+
<ScrollView
|
|
49
|
+
contentContainerStyle={styles.tabs}
|
|
50
|
+
horizontal
|
|
51
|
+
keyboardShouldPersistTaps="handled"
|
|
52
|
+
showsHorizontalScrollIndicator={false}
|
|
53
|
+
>
|
|
54
|
+
{panels.map(([id, panel]) => {
|
|
55
|
+
const title = typeof panel.title === 'function' ? panel.title({}) : panel.title;
|
|
56
|
+
const selected = id === activePanel?.[0];
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Pressable
|
|
60
|
+
accessibilityLabel={String(title)}
|
|
61
|
+
accessibilityRole="tab"
|
|
62
|
+
accessibilityState={{ selected }}
|
|
63
|
+
key={id}
|
|
64
|
+
onPress={() => setSelectedPanelId(id)}
|
|
65
|
+
style={[
|
|
66
|
+
styles.tab,
|
|
67
|
+
selected && {
|
|
68
|
+
borderBottomColor: theme.barSelectedColor,
|
|
69
|
+
borderBottomWidth: 2,
|
|
70
|
+
},
|
|
71
|
+
]}
|
|
72
|
+
testID={`${addonTestID}-${id.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}`}
|
|
73
|
+
>
|
|
74
|
+
<Text style={{ color: selected ? theme.barSelectedColor : theme.color.mediumdark }}>{String(title)}</Text>
|
|
75
|
+
</Pressable>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</ScrollView>
|
|
79
|
+
<Pressable
|
|
80
|
+
accessibilityLabel="Close addons panel"
|
|
81
|
+
accessibilityRole="button"
|
|
82
|
+
onPress={onClose}
|
|
83
|
+
style={styles.closeButton}
|
|
84
|
+
testID={closeTestID}
|
|
85
|
+
>
|
|
86
|
+
<Text style={{ color: theme.color.mediumdark }}>Close</Text>
|
|
87
|
+
</Pressable>
|
|
88
|
+
</View>
|
|
89
|
+
<ScrollView contentContainerStyle={styles.panelContent} key={storyId} keyboardShouldPersistTaps="handled">
|
|
90
|
+
{!storyId ? (
|
|
91
|
+
<Text style={{ color: theme.color.mediumdark }}>No story selected.</Text>
|
|
92
|
+
) : !activePanel ? (
|
|
93
|
+
<Text style={{ color: theme.color.mediumdark }}>No addons loaded.</Text>
|
|
94
|
+
) : (
|
|
95
|
+
<PanelRenderer panel={activePanel[1]} />
|
|
96
|
+
)}
|
|
97
|
+
</ScrollView>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function PanelRenderer({ panel }: { panel: Addon_BaseType }) {
|
|
103
|
+
return panel.render({ active: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const styles = StyleSheet.create({
|
|
107
|
+
root: {
|
|
108
|
+
flex: 1,
|
|
109
|
+
minHeight: 240,
|
|
110
|
+
},
|
|
111
|
+
header: {
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
borderBottomWidth: 1,
|
|
114
|
+
flexDirection: 'row',
|
|
115
|
+
},
|
|
116
|
+
tabs: {
|
|
117
|
+
alignItems: 'stretch',
|
|
118
|
+
},
|
|
119
|
+
tab: {
|
|
120
|
+
alignItems: 'center',
|
|
121
|
+
justifyContent: 'center',
|
|
122
|
+
minHeight: 40,
|
|
123
|
+
paddingHorizontal: 14,
|
|
124
|
+
},
|
|
125
|
+
closeButton: {
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
justifyContent: 'center',
|
|
128
|
+
minHeight: 40,
|
|
129
|
+
paddingHorizontal: 12,
|
|
130
|
+
},
|
|
131
|
+
panelContent: {
|
|
132
|
+
flexGrow: 1,
|
|
133
|
+
padding: 12,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Callout } from '@fluentui-react-native/callout';
|
|
5
|
+
import type { CalloutProps, DirectionalHint } from '@fluentui-react-native/callout';
|
|
6
|
+
import { useTheme } from '@storybook/react-native-theming';
|
|
7
|
+
|
|
8
|
+
type Win32CalloutPortalProps = React.PropsWithChildren<{
|
|
9
|
+
accessibilityLabel: string;
|
|
10
|
+
directionalHint: DirectionalHint;
|
|
11
|
+
height: number;
|
|
12
|
+
onDismiss: () => void;
|
|
13
|
+
onShow?: () => void;
|
|
14
|
+
target: CalloutProps['target'];
|
|
15
|
+
testID: string;
|
|
16
|
+
visible: boolean;
|
|
17
|
+
width: number;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
export function Win32CalloutPortal({
|
|
21
|
+
accessibilityLabel,
|
|
22
|
+
children,
|
|
23
|
+
directionalHint,
|
|
24
|
+
height,
|
|
25
|
+
onDismiss,
|
|
26
|
+
onShow,
|
|
27
|
+
target,
|
|
28
|
+
testID,
|
|
29
|
+
visible,
|
|
30
|
+
width,
|
|
31
|
+
}: Win32CalloutPortalProps) {
|
|
32
|
+
const theme = useTheme();
|
|
33
|
+
|
|
34
|
+
if (!visible) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Callout
|
|
40
|
+
accessibilityLabel={accessibilityLabel}
|
|
41
|
+
accessibilityRole="dialog"
|
|
42
|
+
backgroundColor={theme.background.content}
|
|
43
|
+
borderColor={theme.appBorderColor}
|
|
44
|
+
borderRadius={8}
|
|
45
|
+
borderWidth={1}
|
|
46
|
+
directionalHint={directionalHint}
|
|
47
|
+
gapSpace={4}
|
|
48
|
+
maxHeight={height}
|
|
49
|
+
maxWidth={width}
|
|
50
|
+
minPadding={12}
|
|
51
|
+
minWidth={width}
|
|
52
|
+
onDismiss={onDismiss}
|
|
53
|
+
onShow={onShow}
|
|
54
|
+
setInitialFocus
|
|
55
|
+
target={target}
|
|
56
|
+
testID={testID}
|
|
57
|
+
>
|
|
58
|
+
<View
|
|
59
|
+
collapsable={false}
|
|
60
|
+
style={[styles.content, { backgroundColor: theme.background.content, height, width }]}
|
|
61
|
+
testID={`${testID}-content`}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</View>
|
|
65
|
+
</Callout>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const styles = StyleSheet.create({
|
|
70
|
+
content: {
|
|
71
|
+
overflow: 'hidden',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { PanResponder, StyleSheet, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '@storybook/react-native-theming';
|
|
5
|
+
|
|
6
|
+
type Win32ResizeHandleProps = {
|
|
7
|
+
direction: 'horizontal' | 'vertical';
|
|
8
|
+
onResize: (delta: number) => void;
|
|
9
|
+
testID: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function Win32ResizeHandle({ direction, onResize, testID }: Win32ResizeHandleProps) {
|
|
13
|
+
const theme = useTheme();
|
|
14
|
+
const previousDeltaRef = React.useRef(0);
|
|
15
|
+
const [active, setActive] = React.useState(false);
|
|
16
|
+
const horizontal = direction === 'horizontal';
|
|
17
|
+
const panResponder = React.useMemo(
|
|
18
|
+
() =>
|
|
19
|
+
PanResponder.create({
|
|
20
|
+
onMoveShouldSetPanResponder: () => true,
|
|
21
|
+
onPanResponderGrant: () => {
|
|
22
|
+
previousDeltaRef.current = 0;
|
|
23
|
+
setActive(true);
|
|
24
|
+
},
|
|
25
|
+
onPanResponderMove: (_, gestureState) => {
|
|
26
|
+
const nextDelta = horizontal ? gestureState.dx : gestureState.dy;
|
|
27
|
+
onResize(nextDelta - previousDeltaRef.current);
|
|
28
|
+
previousDeltaRef.current = nextDelta;
|
|
29
|
+
},
|
|
30
|
+
onPanResponderRelease: () => setActive(false),
|
|
31
|
+
onPanResponderTerminate: () => setActive(false),
|
|
32
|
+
onPanResponderTerminationRequest: () => false,
|
|
33
|
+
onShouldBlockNativeResponder: () => true,
|
|
34
|
+
}),
|
|
35
|
+
[horizontal, onResize],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View
|
|
40
|
+
{...panResponder.panHandlers}
|
|
41
|
+
accessibilityLabel={horizontal ? 'Resize story sidebar' : 'Resize addons panel'}
|
|
42
|
+
accessibilityRole="adjustable"
|
|
43
|
+
accessible
|
|
44
|
+
style={[
|
|
45
|
+
horizontal ? styles.horizontal : styles.vertical,
|
|
46
|
+
{
|
|
47
|
+
backgroundColor: active ? theme.barSelectedColor : 'transparent',
|
|
48
|
+
borderColor: active ? theme.barSelectedColor : theme.appBorderColor,
|
|
49
|
+
},
|
|
50
|
+
]}
|
|
51
|
+
testID={testID}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
horizontal: {
|
|
58
|
+
alignSelf: 'stretch',
|
|
59
|
+
borderLeftWidth: 1,
|
|
60
|
+
width: 6,
|
|
61
|
+
},
|
|
62
|
+
vertical: {
|
|
63
|
+
borderTopWidth: 1,
|
|
64
|
+
height: 6,
|
|
65
|
+
width: '100%',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
import { StyleSheet, View as NativeView } from 'react-native';
|
|
3
|
+
import type { View } from '@storybook/react-native';
|
|
4
|
+
|
|
5
|
+
import { DesktopDriverBridge } from './DesktopDriverBridge';
|
|
6
|
+
import { DesktopStorybookConfigProvider, useDesktopStorybookTestID } from './DesktopStorybookConfig';
|
|
7
|
+
import type { DesktopStorybookRuntimeInstance } from './DesktopStorybookConfig';
|
|
8
|
+
import { StorybookThemeHost } from './StorybookTheme';
|
|
9
|
+
import { StorybookUIComponent } from './StorybookUI';
|
|
10
|
+
|
|
11
|
+
export type DesktopStorybookOptions = {
|
|
12
|
+
enableWebsockets?: boolean;
|
|
13
|
+
host?: string;
|
|
14
|
+
port?: number;
|
|
15
|
+
testIDPrefix?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function createDesktopStorybookApp(
|
|
19
|
+
view: Pick<View, 'getStorybookUI'>,
|
|
20
|
+
{ enableWebsockets = true, host = '127.0.0.1', port, testIDPrefix = 'storybook-desktop' }: DesktopStorybookOptions = {},
|
|
21
|
+
) {
|
|
22
|
+
const runtimeInstance = (
|
|
23
|
+
globalThis as typeof globalThis & {
|
|
24
|
+
__FURN_DESKTOP_STORYBOOK_INSTANCE__?: DesktopStorybookRuntimeInstance;
|
|
25
|
+
}
|
|
26
|
+
).__FURN_DESKTOP_STORYBOOK_INSTANCE__;
|
|
27
|
+
const memoryStore: Record<string, string> = {};
|
|
28
|
+
const storage = {
|
|
29
|
+
getItem: async (key: string) => (key in memoryStore ? memoryStore[key] : null),
|
|
30
|
+
setItem: async (key: string, value: string) => {
|
|
31
|
+
memoryStore[key] = value;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const StorybookUI = view.getStorybookUI({
|
|
35
|
+
enableWebsockets,
|
|
36
|
+
host,
|
|
37
|
+
port: port ?? runtimeInstance?.storybookPort ?? 7007,
|
|
38
|
+
CustomUIComponent: StorybookUIComponent,
|
|
39
|
+
storage,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function DesktopStorybookApp() {
|
|
43
|
+
const resolvedTestIDPrefix = runtimeInstance?.testIDPrefix ?? testIDPrefix;
|
|
44
|
+
return (
|
|
45
|
+
<DesktopStorybookConfigProvider runtimeInstance={runtimeInstance} testIDPrefix={resolvedTestIDPrefix}>
|
|
46
|
+
<DesktopStorybookAppRoot>
|
|
47
|
+
<DesktopDriverBridge />
|
|
48
|
+
<StorybookThemeHost>
|
|
49
|
+
<StorybookUI />
|
|
50
|
+
</StorybookThemeHost>
|
|
51
|
+
</DesktopStorybookAppRoot>
|
|
52
|
+
</DesktopStorybookConfigProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return DesktopStorybookApp;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function DesktopStorybookAppRoot({ children }: React.PropsWithChildren) {
|
|
60
|
+
const testID = useDesktopStorybookTestID('app-root');
|
|
61
|
+
return (
|
|
62
|
+
<NativeView style={styles.root} testID={testID}>
|
|
63
|
+
{children}
|
|
64
|
+
</NativeView>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const styles = StyleSheet.create({
|
|
69
|
+
root: {
|
|
70
|
+
flex: 1,
|
|
71
|
+
},
|
|
72
|
+
});
|