@storybook/react-native-ui-lite 9.0.0-beta.11

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +94 -0
  3. package/dist/index.js +5437 -0
  4. package/package.json +80 -0
  5. package/src/Button.stories.tsx +134 -0
  6. package/src/Button.tsx +172 -0
  7. package/src/Explorer.stories.tsx +40 -0
  8. package/src/Explorer.tsx +38 -0
  9. package/src/IconButton.tsx +10 -0
  10. package/src/Layout.stories.tsx +70 -0
  11. package/src/Layout.tsx +310 -0
  12. package/src/LayoutProvider.tsx +32 -0
  13. package/src/MobileAddonsPanel.tsx +195 -0
  14. package/src/MobileMenuDrawer.tsx +90 -0
  15. package/src/Refs.tsx +82 -0
  16. package/src/Search.tsx +234 -0
  17. package/src/SearchResults.stories.tsx +102 -0
  18. package/src/SearchResults.tsx +254 -0
  19. package/src/SelectedNodeProvider.tsx +58 -0
  20. package/src/Sidebar.stories.tsx +188 -0
  21. package/src/Sidebar.tsx +131 -0
  22. package/src/StorageProvider.tsx +21 -0
  23. package/src/StorybookLogo.stories.tsx +76 -0
  24. package/src/StorybookLogo.tsx +108 -0
  25. package/src/Tree.stories.tsx +177 -0
  26. package/src/Tree.tsx +390 -0
  27. package/src/TreeNode.stories.tsx +117 -0
  28. package/src/TreeNode.tsx +154 -0
  29. package/src/assets/react-native-logo.png +0 -0
  30. package/src/constants.ts +4 -0
  31. package/src/hooks/useExpanded.ts +64 -0
  32. package/src/hooks/useLastViewed.ts +48 -0
  33. package/src/hooks/useStoreState.ts +27 -0
  34. package/src/icon/iconDataUris.tsx +365 -0
  35. package/src/index.tsx +11 -0
  36. package/src/mockdata.large.ts +25217 -0
  37. package/src/mockdata.ts +287 -0
  38. package/src/types.ts +66 -0
  39. package/src/util/StoryHash.ts +249 -0
  40. package/src/util/status.tsx +87 -0
  41. package/src/util/tree.ts +93 -0
  42. package/src/util/useStyle.ts +28 -0
package/src/Tree.tsx ADDED
@@ -0,0 +1,390 @@
1
+ import type {
2
+ ComponentEntry,
3
+ GroupEntry,
4
+ State,
5
+ StoriesHash,
6
+ StoryEntry,
7
+ } from 'storybook/internal/manager-api';
8
+ import { styled } from '@storybook/react-native-theming';
9
+ import React, { useCallback, useMemo, useRef } from 'react';
10
+ import { View } from 'react-native';
11
+ import { IconButton } from './IconButton';
12
+ import { ComponentNode, GroupNode, StoryNode } from './TreeNode';
13
+ import { Item } from './types';
14
+ import type { ExpandAction, ExpandedState } from './hooks/useExpanded';
15
+ import { useExpanded } from './hooks/useExpanded';
16
+ import { createId, getAncestorIds, getDescendantIds, isStoryHoistable } from './util/tree';
17
+ import { useSelectedNode } from './SelectedNodeProvider';
18
+ import { CollapseAllIcon, CollapseIcon, ExpandAllIcon } from './icon/iconDataUris';
19
+
20
+ interface NodeProps {
21
+ item: Item;
22
+ refId: string;
23
+ docsMode: boolean;
24
+ isOrphan: boolean;
25
+ isDisplayed: boolean;
26
+ color: string | undefined;
27
+ isSelected: boolean;
28
+ isFullyExpanded?: boolean;
29
+ isExpanded: boolean;
30
+ setExpanded: (action: ExpandAction) => void;
31
+ setFullyExpanded?: () => void;
32
+ onSelectStoryId: (itemId: string) => void;
33
+ status: State['status'][keyof State['status']];
34
+ }
35
+
36
+ const TextItem = styled.Text(({ theme }) => ({
37
+ color: theme.color.defaultText,
38
+ }));
39
+
40
+ export const Node = React.memo<NodeProps>(function Node({
41
+ item,
42
+ refId,
43
+ isOrphan,
44
+ isDisplayed,
45
+ isSelected,
46
+ isFullyExpanded,
47
+ color: _2,
48
+ setFullyExpanded,
49
+ isExpanded,
50
+ setExpanded,
51
+ onSelectStoryId,
52
+ }) {
53
+ const { setNodeRef } = useSelectedNode();
54
+
55
+ const setRef = useCallback(
56
+ (node: View | null) => {
57
+ if (isSelected && node) {
58
+ setNodeRef(node);
59
+ }
60
+ },
61
+ [isSelected, setNodeRef]
62
+ );
63
+
64
+ if (!isDisplayed) {
65
+ return null;
66
+ }
67
+
68
+ const id = createId(item.id, refId);
69
+
70
+ if (item.type === 'story') {
71
+ return (
72
+ <LeafNodeStyleWrapper>
73
+ <StoryNode
74
+ ref={setRef}
75
+ selected={isSelected}
76
+ key={id}
77
+ id={id}
78
+ depth={isOrphan ? item.depth : item.depth - 1}
79
+ onPress={() => {
80
+ onSelectStoryId(item.id);
81
+ }}
82
+ >
83
+ {(item.renderLabel as (i: typeof item) => React.ReactNode)?.(item) || item.name}
84
+ </StoryNode>
85
+ </LeafNodeStyleWrapper>
86
+ );
87
+ }
88
+
89
+ if (item.type === 'root') {
90
+ return (
91
+ <RootNode key={id} id={id}>
92
+ <CollapseButton
93
+ data-action="collapse-root"
94
+ onPress={(event) => {
95
+ event.preventDefault();
96
+ setExpanded({ ids: [item.id], value: !isExpanded });
97
+ }}
98
+ aria-expanded={isExpanded}
99
+ >
100
+ <CollapseIcon isExpanded={isExpanded} />
101
+ <TextItem>{item.renderLabel?.(item, {}) || item.name}</TextItem>
102
+ </CollapseButton>
103
+ {isExpanded && (
104
+ <IconButton
105
+ aria-label={isFullyExpanded ? 'Expand' : 'Collapse'}
106
+ data-action="expand-all"
107
+ data-expanded={isFullyExpanded}
108
+ onPress={(event) => {
109
+ event.preventDefault();
110
+ setFullyExpanded();
111
+ }}
112
+ >
113
+ {isFullyExpanded ? <CollapseAllIcon /> : <ExpandAllIcon />}
114
+ </IconButton>
115
+ )}
116
+ </RootNode>
117
+ );
118
+ }
119
+
120
+ if (item.type === 'component' || item.type === 'group') {
121
+ const BranchNode = item.type === 'component' ? ComponentNode : GroupNode;
122
+ return (
123
+ <BranchNode
124
+ key={id}
125
+ id={id}
126
+ aria-controls={item.children && item.children[0]}
127
+ aria-expanded={isExpanded}
128
+ depth={isOrphan ? item.depth : item.depth - 1}
129
+ isComponent={item.type === 'component'}
130
+ isExpandable={item.children && item.children.length > 0}
131
+ isExpanded={isExpanded}
132
+ onPress={(event) => {
133
+ event.preventDefault();
134
+ setExpanded({ ids: [item.id], value: !isExpanded });
135
+ }}
136
+ >
137
+ {(item.renderLabel as (i: typeof item) => React.ReactNode)?.(item) || item.name}
138
+ </BranchNode>
139
+ );
140
+ }
141
+
142
+ return null;
143
+ });
144
+
145
+ export const LeafNodeStyleWrapper = styled.View(({ theme }) => ({
146
+ position: 'relative',
147
+ display: 'flex',
148
+ flexDirection: 'row',
149
+ justifyContent: 'space-between',
150
+ alignItems: 'center',
151
+ paddingRight: 20,
152
+ color: theme.color.defaultText,
153
+ backgroundColor: 'transparent',
154
+ minHeight: 28,
155
+ borderRadius: 4,
156
+ }));
157
+
158
+ export const RootNode = styled.View(() => ({
159
+ display: 'flex',
160
+ flexDirection: 'row',
161
+ alignItems: 'center',
162
+ justifyContent: 'space-between',
163
+ marginTop: 16,
164
+ marginBottom: 4,
165
+ minHeight: 28,
166
+ }));
167
+
168
+ export const RootNodeText = styled.Text(({ theme }) => ({
169
+ fontSize: theme.typography.size.s1 - 1,
170
+ fontWeight: theme.typography.weight.bold,
171
+ color: theme.textMutedColor,
172
+ lineHeight: 16,
173
+ letterSpacing: 2.5,
174
+ textTransform: 'uppercase',
175
+ }));
176
+
177
+ const CollapseButton = styled.TouchableOpacity(() => ({
178
+ display: 'flex',
179
+ flexDirection: 'row',
180
+ paddingVertical: 0,
181
+ paddingHorizontal: 8,
182
+ borderRadius: 4,
183
+ gap: 6,
184
+ alignItems: 'center',
185
+ cursor: 'pointer',
186
+ height: 28,
187
+ }));
188
+
189
+ export const Tree = React.memo<{
190
+ isBrowsing: boolean;
191
+ isMain: boolean;
192
+ status?: State['status'];
193
+ refId: string;
194
+ data: StoriesHash;
195
+ docsMode: boolean;
196
+ selectedStoryId: string | null;
197
+ onSelectStoryId: (storyId: string) => void;
198
+ }>(function Tree({ isMain, refId, data, status, docsMode, selectedStoryId, onSelectStoryId }) {
199
+ const containerRef = useRef<View>(null);
200
+
201
+ // Find top-level nodes and group them so we can hoist any orphans and expand any roots.
202
+ const [rootIds, orphanIds, initialExpanded] = useMemo(
203
+ () =>
204
+ Object.keys(data).reduce<[string[], string[], ExpandedState]>(
205
+ (acc, id) => {
206
+ const item = data[id];
207
+ if (item.type === 'root') acc[0].push(id);
208
+ else if (!item.parent) acc[1].push(id);
209
+ if (item.type === 'root' && item.startCollapsed) acc[2][id] = false;
210
+ return acc;
211
+ },
212
+ [[], [], {}]
213
+ ),
214
+ [data]
215
+ );
216
+
217
+ // Create a map of expandable descendants for each root/orphan item, which is needed later.
218
+ // Doing that here is a performance enhancement, as it avoids traversing the tree again later.
219
+ const { expandableDescendants } = useMemo(() => {
220
+ return [...orphanIds, ...rootIds].reduce(
221
+ (acc, nodeId) => {
222
+ acc.expandableDescendants[nodeId] = getDescendantIds(data, nodeId, false).filter(
223
+ (d) => !['story', 'docs'].includes(data[d].type)
224
+ );
225
+ return acc;
226
+ },
227
+ { orphansFirst: [] as string[], expandableDescendants: {} as Record<string, string[]> }
228
+ );
229
+ }, [data, rootIds, orphanIds]);
230
+
231
+ // Create a list of component IDs which should be collapsed into their (only) child.
232
+ // That is:
233
+ // - components with a single story child with the same name
234
+ // - components with only a single docs child
235
+ const singleStoryComponentIds = useMemo(() => {
236
+ return Object.keys(data).filter((id) => {
237
+ const entry = data[id];
238
+ if (entry.type !== 'component') return false;
239
+
240
+ const { children = [], name } = entry;
241
+ if (children.length !== 1) return false;
242
+
243
+ const onlyChild = data[children[0]];
244
+
245
+ if (onlyChild.type === 'docs') return true;
246
+ if (onlyChild.type === 'story') return isStoryHoistable(onlyChild.name, name);
247
+ return false;
248
+ });
249
+ }, [data]);
250
+
251
+ // Omit single-story components from the list of nodes.
252
+ const collapsedItems = useMemo(
253
+ () => Object.keys(data).filter((id) => !singleStoryComponentIds.includes(id)),
254
+ // eslint-disable-next-line react-hooks/exhaustive-deps
255
+ [singleStoryComponentIds]
256
+ );
257
+
258
+ // Rewrite the dataset to place the child story in place of the component.
259
+ const collapsedData = useMemo(() => {
260
+ return singleStoryComponentIds.reduce(
261
+ (acc, id) => {
262
+ const { children, parent, name } = data[id] as ComponentEntry;
263
+ const [childId] = children;
264
+ if (parent) {
265
+ const siblings = [...(data[parent] as GroupEntry).children];
266
+ siblings[siblings.indexOf(id)] = childId;
267
+ acc[parent] = { ...data[parent], children: siblings } as GroupEntry;
268
+ }
269
+ acc[childId] = {
270
+ ...data[childId],
271
+ name,
272
+ parent,
273
+ depth: data[childId].depth - 1,
274
+ } as StoryEntry;
275
+ return acc;
276
+ },
277
+ { ...data }
278
+ );
279
+ // eslint-disable-next-line react-hooks/exhaustive-deps
280
+ }, [data]);
281
+
282
+ const ancestry = useMemo(() => {
283
+ return collapsedItems.reduce(
284
+ (acc, id) => Object.assign(acc, { [id]: getAncestorIds(collapsedData, id) }),
285
+ {} as { [key: string]: string[] }
286
+ );
287
+ }, [collapsedItems, collapsedData]);
288
+
289
+ // Track expanded nodes, keep it in sync with props and enable keyboard shortcuts.
290
+ const [expanded, setExpanded] = useExpanded({
291
+ refId,
292
+ data: collapsedData,
293
+ initialExpanded,
294
+ rootIds,
295
+ selectedStoryId,
296
+ onSelectStoryId,
297
+ });
298
+
299
+ const treeItems = useMemo(() => {
300
+ return collapsedItems.map((itemId) => {
301
+ const item = collapsedData[itemId];
302
+ const id = createId(itemId, refId);
303
+
304
+ if (item.type === 'root') {
305
+ const descendants = expandableDescendants[item.id];
306
+ const isFullyExpanded = descendants.every((d: string) => expanded[d]);
307
+ return (
308
+ <Root
309
+ key={id}
310
+ item={item}
311
+ refId={refId}
312
+ isOrphan={false}
313
+ isDisplayed
314
+ isSelected={selectedStoryId === itemId}
315
+ isExpanded={!!expanded[itemId]}
316
+ setExpanded={setExpanded}
317
+ isFullyExpanded={isFullyExpanded}
318
+ expandableDescendants={descendants}
319
+ onSelectStoryId={onSelectStoryId}
320
+ docsMode={false}
321
+ color=""
322
+ status={{}}
323
+ />
324
+ );
325
+ }
326
+
327
+ const isDisplayed = !item.parent || ancestry[itemId].every((a: string) => expanded[a]);
328
+
329
+ return (
330
+ <Node
331
+ key={id}
332
+ item={item}
333
+ status={status?.[itemId]}
334
+ refId={refId}
335
+ color={null}
336
+ docsMode={docsMode}
337
+ isOrphan={orphanIds.some((oid) => itemId === oid || itemId.startsWith(`${oid}-`))}
338
+ isDisplayed={isDisplayed}
339
+ isSelected={selectedStoryId === itemId}
340
+ isExpanded={!!expanded[itemId]}
341
+ setExpanded={setExpanded}
342
+ onSelectStoryId={onSelectStoryId}
343
+ />
344
+ );
345
+ });
346
+ }, [
347
+ ancestry,
348
+ collapsedData,
349
+ collapsedItems,
350
+ docsMode,
351
+ expandableDescendants,
352
+ expanded,
353
+ onSelectStoryId,
354
+ orphanIds,
355
+ refId,
356
+ selectedStoryId,
357
+ setExpanded,
358
+ status,
359
+ ]);
360
+ return (
361
+ <Container ref={containerRef} hasOrphans={isMain && orphanIds.length > 0}>
362
+ {treeItems}
363
+ </Container>
364
+ );
365
+ });
366
+
367
+ const Container = styled.View<{ hasOrphans: boolean }>((props) => ({
368
+ marginTop: props.hasOrphans ? 20 : 0,
369
+ marginBottom: 20,
370
+ }));
371
+
372
+ const Root = React.memo<NodeProps & { expandableDescendants: string[] }>(function Root({
373
+ setExpanded,
374
+ isFullyExpanded,
375
+ expandableDescendants,
376
+ ...props
377
+ }) {
378
+ const setFullyExpanded = useCallback(
379
+ () => setExpanded({ ids: expandableDescendants, value: !isFullyExpanded }),
380
+ [setExpanded, isFullyExpanded, expandableDescendants]
381
+ );
382
+ return (
383
+ <Node
384
+ {...props}
385
+ setExpanded={setExpanded}
386
+ isFullyExpanded={isFullyExpanded}
387
+ setFullyExpanded={setFullyExpanded}
388
+ />
389
+ );
390
+ });
@@ -0,0 +1,117 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ComponentNode, GroupNode, StoryNode } from './TreeNode';
3
+ import { View } from 'react-native';
4
+
5
+ const meta = {
6
+ title: 'UI/Sidebar/TreeNode',
7
+ parameters: { layout: 'fullscreen' },
8
+ component: StoryNode,
9
+ } satisfies Meta<typeof StoryNode>;
10
+ export default meta;
11
+
12
+ export const Types: StoryObj<typeof meta> = {
13
+ render: () => (
14
+ <View style={{ flex: 1 }}>
15
+ <ComponentNode>Component</ComponentNode>
16
+ <GroupNode>Group</GroupNode>
17
+ <StoryNode>Story</StoryNode>
18
+ </View>
19
+ ),
20
+ args: {
21
+ children: <></>,
22
+ },
23
+ };
24
+
25
+ export const Expandable = () => (
26
+ <>
27
+ <ComponentNode isExpandable>Collapsed component</ComponentNode>
28
+ <ComponentNode isExpandable isExpanded>
29
+ Expanded component
30
+ </ComponentNode>
31
+ <GroupNode isExpandable>Collapsed group</GroupNode>
32
+ <GroupNode isExpandable isExpanded>
33
+ Expanded group
34
+ </GroupNode>
35
+ </>
36
+ );
37
+
38
+ export const ExpandableLongName = () => (
39
+ <>
40
+ <ComponentNode isExpandable>
41
+ Collapsed component with a very very very very very very very very very very very very very
42
+ very very very very very very veryvery very very very very very very very very veryvery very
43
+ very very very very very very very veryvery very very very very very very very very veryvery
44
+ very very very very very very very very veryvery very very very very very very very very
45
+ veryvery very very very very very very very very veryvery very very very very very very very
46
+ very very long name
47
+ </ComponentNode>
48
+ <ComponentNode isExpandable isExpanded>
49
+ Expanded component with a very very very very very very very very very very very very very
50
+ very very very very very very veryvery very very very very very very very very veryvery very
51
+ very very very very very very very veryvery very very very very very very very very veryvery
52
+ very very very very very very very very veryvery very very very very very very very very
53
+ veryvery very very very very very very very very veryvery very very very very very very very
54
+ very very long name
55
+ </ComponentNode>
56
+ <GroupNode isExpandable>
57
+ Collapsed group with a very very very very very very very very very very very very very very
58
+ very very very very very veryvery very very very very very very very very veryvery very very
59
+ very very very very very very veryvery very very very very very very very very veryvery very
60
+ very very very very very very very veryvery very very very very very very very very veryvery
61
+ very very very very very very very very veryvery very very very very very very very very very
62
+ long name
63
+ </GroupNode>
64
+ <GroupNode isExpandable isExpanded>
65
+ Expanded group with a very very very very very very very very very very very very very very
66
+ very very very very very veryvery very very very very very very very very veryvery very very
67
+ very very very very very very veryvery very very very very very very very very veryvery very
68
+ very very very very very very very veryvery very very very very very very very very veryvery
69
+ very very very very very very very very veryvery very very very very very very very very very
70
+ long name
71
+ </GroupNode>
72
+ </>
73
+ );
74
+
75
+ export const Nested = () => (
76
+ <>
77
+ <GroupNode isExpandable isExpanded depth={0}>
78
+ Zero
79
+ </GroupNode>
80
+ <GroupNode isExpandable isExpanded depth={1}>
81
+ One
82
+ </GroupNode>
83
+ <StoryNode depth={2}>Two</StoryNode>
84
+ <ComponentNode isExpandable isExpanded depth={2}>
85
+ Two
86
+ </ComponentNode>
87
+ <StoryNode depth={3}>Three</StoryNode>
88
+ </>
89
+ );
90
+ export const Selection = () => (
91
+ <>
92
+ <StoryNode selected={false}>Default story</StoryNode>
93
+ <StoryNode selected>Selected story</StoryNode>
94
+ </>
95
+ );
96
+
97
+ export const SelectionWithLongName = () => (
98
+ <>
99
+ <StoryNode>
100
+ Default story with a very very very very very very very very very very very very very very
101
+ very very very very very veryvery very very very very very very very very veryvery very very
102
+ very very very very very very veryvery very very very very very very very very veryvery very
103
+ very very very very very very very veryvery very very very very very very very very veryvery
104
+ very very very very very very very very veryvery very very very very very very very very very
105
+ long name
106
+ </StoryNode>
107
+
108
+ <StoryNode selected>
109
+ Selected story with a very very very very very very very very very very very very very very
110
+ very very very very very veryvery very very very very very very very very veryvery very very
111
+ very very very very very very veryvery very very very very very very very very veryvery very
112
+ very very very very very very very veryvery very very very very very very very very veryvery
113
+ very very very very very very very very veryvery very very very very very very very very very
114
+ long name
115
+ </StoryNode>
116
+ </>
117
+ );
@@ -0,0 +1,154 @@
1
+ import { styled, useTheme } from '@storybook/react-native-theming';
2
+
3
+ import React, { ComponentProps, FC, forwardRef, useMemo } from 'react';
4
+ import { transparentize } from 'polished';
5
+ import { View } from 'react-native';
6
+ import { CollapseIcon, ComponentIcon, GroupIcon, StoryIcon } from './icon/iconDataUris';
7
+
8
+ export interface NodeProps {
9
+ children: React.ReactNode | React.ReactNode[];
10
+ isExpandable?: boolean;
11
+ isExpanded?: boolean;
12
+ }
13
+
14
+ const BranchNodeText = styled.Text<{ isSelected?: boolean }>(({ theme }) => ({
15
+ textAlign: 'left',
16
+ fontSize: theme.typography.size.s2,
17
+ flexShrink: 1,
18
+ color: theme.color.defaultText,
19
+ }));
20
+
21
+ const BranchNode = styled.TouchableOpacity<{
22
+ depth?: number;
23
+ isExpandable?: boolean;
24
+ isExpanded?: boolean;
25
+ isComponent?: boolean;
26
+ isSelected?: boolean;
27
+ }>(({ depth = 0, isExpandable = false, theme }) => ({
28
+ width: '100%',
29
+ border: 'none',
30
+ cursor: 'pointer',
31
+ display: 'flex',
32
+ flexDirection: 'row',
33
+ alignItems: 'flex-start',
34
+ alignSelf: 'flex-start',
35
+ paddingLeft: (isExpandable ? 8 : 22) + depth * 18,
36
+
37
+ backgroundColor: 'transparent',
38
+ minHeight: 28,
39
+ borderRadius: 4,
40
+ gap: 6,
41
+ paddingTop: 5,
42
+ paddingBottom: 4,
43
+
44
+ // will this actually do anything?
45
+ '&:hover, &:focus': {
46
+ backgroundColor: transparentize(0.93, theme.color.secondary),
47
+ outline: 'none',
48
+ },
49
+ }));
50
+
51
+ const LeafNode = styled.TouchableOpacity<{ depth?: number; selected?: boolean }>(
52
+ ({ depth = 0, selected, theme }) => ({
53
+ alignSelf: 'flex-start',
54
+ cursor: 'pointer',
55
+ color: 'inherit',
56
+ display: 'flex',
57
+ gap: 6,
58
+ flexDirection: 'row',
59
+ alignItems: 'flex-start',
60
+ paddingLeft: 22 + depth * 18,
61
+ paddingTop: 5,
62
+ paddingBottom: 4,
63
+ backgroundColor: selected ? theme.color.secondary : undefined,
64
+ // not sure 👇
65
+ width: '100%',
66
+ borderRadius: 4,
67
+ paddingRight: 20,
68
+ minHeight: 28,
69
+ })
70
+ );
71
+
72
+ const LeafNodeText = styled.Text<{ depth?: number; selected?: boolean }>(({ theme, selected }) => ({
73
+ fontSize: theme.typography.size.s2,
74
+ flexShrink: 1,
75
+ fontWeight: selected ? 'bold' : 'normal',
76
+ color: selected ? theme.color.lightest : theme.color.defaultText,
77
+ }));
78
+
79
+ const Wrapper = styled.View({
80
+ display: 'flex',
81
+ flexDirection: 'row',
82
+ alignItems: 'center',
83
+ gap: 6,
84
+ marginTop: 2,
85
+ });
86
+
87
+ export const GroupNode: FC<
88
+ ComponentProps<typeof BranchNode> & { isExpanded?: boolean; isExpandable?: boolean }
89
+ > = React.memo(function GroupNode({
90
+ children,
91
+ isExpanded = false,
92
+ isExpandable = false,
93
+ ...props
94
+ }) {
95
+ const theme = useTheme();
96
+
97
+ const color = useMemo(() => {
98
+ return theme.base === 'dark' ? theme.color.primary : theme.color.ultraviolet;
99
+ }, [theme.base, theme.color.primary, theme.color.ultraviolet]);
100
+
101
+ return (
102
+ <BranchNode isExpandable={isExpandable} {...props}>
103
+ <Wrapper>
104
+ {isExpandable && <CollapseIcon isExpanded={isExpanded} />}
105
+ <GroupIcon width={14} height={14} color={color} />
106
+ </Wrapper>
107
+ <BranchNodeText>{children}</BranchNodeText>
108
+ </BranchNode>
109
+ );
110
+ });
111
+
112
+ export const ComponentNode: FC<ComponentProps<typeof BranchNode>> = React.memo(
113
+ function ComponentNode({ children, isExpanded, isExpandable, ...props }) {
114
+ const theme = useTheme();
115
+
116
+ const color = useMemo(() => {
117
+ return theme.color.secondary;
118
+ }, [theme.color.secondary]);
119
+
120
+ return (
121
+ <BranchNode isExpandable={isExpandable} {...props}>
122
+ <Wrapper>
123
+ {isExpandable && <CollapseIcon isExpanded={isExpanded} />}
124
+ <ComponentIcon width={12} height={12} color={color} />
125
+ </Wrapper>
126
+ <BranchNodeText>{children}</BranchNodeText>
127
+ </BranchNode>
128
+ );
129
+ }
130
+ );
131
+
132
+ export const StoryNode = React.memo(
133
+ forwardRef<View, ComponentProps<typeof LeafNode>>(function StoryNode(
134
+ { children, ...props },
135
+ ref
136
+ ) {
137
+ const theme = useTheme();
138
+
139
+ const color = useMemo(() => {
140
+ return props.selected ? theme.color.lightest : theme.color.seafoam;
141
+ }, [props.selected, theme.color.lightest, theme.color.seafoam]);
142
+
143
+ return (
144
+ <LeafNode {...props} ref={ref}>
145
+ <Wrapper>
146
+ <StoryIcon width={14} height={14} color={color} />
147
+ </Wrapper>
148
+ <LeafNodeText selected={props.selected}>{children}</LeafNodeText>
149
+ </LeafNode>
150
+ );
151
+ })
152
+ );
153
+
154
+ StoryNode.displayName = 'StoryNode';
Binary file
@@ -0,0 +1,4 @@
1
+ export const BREAKPOINT = 1000;
2
+ export const MEDIA_DESKTOP_BREAKPOINT = `@media (min-width: ${BREAKPOINT}px)`;
3
+ export const MOBILE_TRANSITION_DURATION = 300;
4
+ export const DEFAULT_REF_ID = 'storybook_internal';