@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.
Files changed (67) hide show
  1. package/.cache/tsconfig.tsbuildinfo +1 -0
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +21 -0
  4. package/config/metro.cjs +125 -0
  5. package/config/react-native-safe-area-context.cjs +22 -0
  6. package/lib/DesktopDriverBridge.d.ts +7 -0
  7. package/lib/DesktopDriverBridge.d.ts.map +1 -0
  8. package/lib/DesktopDriverBridge.js +52 -0
  9. package/lib/DesktopDriverBridge.js.map +1 -0
  10. package/lib/DesktopStoryRoot.d.ts +8 -0
  11. package/lib/DesktopStoryRoot.d.ts.map +1 -0
  12. package/lib/DesktopStoryRoot.js +57 -0
  13. package/lib/DesktopStoryRoot.js.map +1 -0
  14. package/lib/DesktopStorybookConfig.d.ts +28 -0
  15. package/lib/DesktopStorybookConfig.d.ts.map +1 -0
  16. package/lib/DesktopStorybookConfig.js +23 -0
  17. package/lib/DesktopStorybookConfig.js.map +1 -0
  18. package/lib/StorybookTheme.d.ts +4 -0
  19. package/lib/StorybookTheme.d.ts.map +1 -0
  20. package/lib/StorybookTheme.js +79 -0
  21. package/lib/StorybookTheme.js.map +1 -0
  22. package/lib/StorybookUI.d.ts +2 -0
  23. package/lib/StorybookUI.d.ts.map +1 -0
  24. package/lib/StorybookUI.js +2 -0
  25. package/lib/StorybookUI.js.map +1 -0
  26. package/lib/StorybookUI.win32.d.ts +3 -0
  27. package/lib/StorybookUI.win32.d.ts.map +1 -0
  28. package/lib/StorybookUI.win32.js +171 -0
  29. package/lib/StorybookUI.win32.js.map +1 -0
  30. package/lib/Win32AddonsPanel.d.ts +9 -0
  31. package/lib/Win32AddonsPanel.d.ts.map +1 -0
  32. package/lib/Win32AddonsPanel.js +72 -0
  33. package/lib/Win32AddonsPanel.js.map +1 -0
  34. package/lib/Win32CalloutPortal.d.ts +16 -0
  35. package/lib/Win32CalloutPortal.d.ts.map +1 -0
  36. package/lib/Win32CalloutPortal.js +17 -0
  37. package/lib/Win32CalloutPortal.js.map +1 -0
  38. package/lib/Win32ResizeHandle.d.ts +8 -0
  39. package/lib/Win32ResizeHandle.d.ts.map +1 -0
  40. package/lib/Win32ResizeHandle.js +46 -0
  41. package/lib/Win32ResizeHandle.js.map +1 -0
  42. package/lib/createDesktopStorybookApp.d.ts +9 -0
  43. package/lib/createDesktopStorybookApp.d.ts.map +1 -0
  44. package/lib/createDesktopStorybookApp.js +38 -0
  45. package/lib/createDesktopStorybookApp.js.map +1 -0
  46. package/lib/createDesktopStorybookPreview.d.ts +3 -0
  47. package/lib/createDesktopStorybookPreview.d.ts.map +1 -0
  48. package/lib/createDesktopStorybookPreview.js +21 -0
  49. package/lib/createDesktopStorybookPreview.js.map +1 -0
  50. package/lib/index.d.ts +7 -0
  51. package/lib/index.d.ts.map +1 -0
  52. package/lib/index.js +5 -0
  53. package/lib/index.js.map +1 -0
  54. package/package.json +106 -0
  55. package/src/DesktopDriverBridge.tsx +66 -0
  56. package/src/DesktopStoryRoot.tsx +88 -0
  57. package/src/DesktopStorybookConfig.tsx +58 -0
  58. package/src/StorybookTheme.tsx +114 -0
  59. package/src/StorybookUI.ts +1 -0
  60. package/src/StorybookUI.win32.tsx +330 -0
  61. package/src/Win32AddonsPanel.tsx +135 -0
  62. package/src/Win32CalloutPortal.tsx +73 -0
  63. package/src/Win32ResizeHandle.tsx +67 -0
  64. package/src/createDesktopStorybookApp.tsx +72 -0
  65. package/src/createDesktopStorybookPreview.tsx +31 -0
  66. package/src/index.ts +12 -0
  67. 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
+ });