@storybook/react-native-ui 8.0.0-alpha.3

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +492 -0
  3. package/dist/index.js +2860 -0
  4. package/package.json +85 -0
  5. package/src/Button.stories.tsx +134 -0
  6. package/src/Button.tsx +243 -0
  7. package/src/Explorer.stories.tsx +46 -0
  8. package/src/Explorer.tsx +54 -0
  9. package/src/IconButton.tsx +11 -0
  10. package/src/Layout.stories.tsx +38 -0
  11. package/src/Layout.tsx +103 -0
  12. package/src/LayoutProvider.tsx +90 -0
  13. package/src/MobileAddonsPanel.tsx +166 -0
  14. package/src/MobileMenuDrawer.tsx +75 -0
  15. package/src/Refs.tsx +142 -0
  16. package/src/Search.tsx +336 -0
  17. package/src/SearchResults.tsx +315 -0
  18. package/src/Sidebar.stories.tsx +262 -0
  19. package/src/Sidebar.tsx +200 -0
  20. package/src/Tree.stories.tsx +139 -0
  21. package/src/Tree.tsx +441 -0
  22. package/src/TreeNode.stories.tsx +122 -0
  23. package/src/TreeNode.tsx +146 -0
  24. package/src/constants.ts +4 -0
  25. package/src/icon/BottomBarToggleIcon.tsx +23 -0
  26. package/src/icon/CloseIcon.tsx +22 -0
  27. package/src/icon/CollapseAllIcon.tsx +17 -0
  28. package/src/icon/CollapseIcon.tsx +39 -0
  29. package/src/icon/ComponentIcon.tsx +14 -0
  30. package/src/icon/ExpandAllIcon.tsx +17 -0
  31. package/src/icon/FaceHappyIcon.tsx +18 -0
  32. package/src/icon/GroupIcon.tsx +14 -0
  33. package/src/icon/MenuIcon.tsx +18 -0
  34. package/src/icon/SearchIcon.tsx +17 -0
  35. package/src/icon/StoryIcon.tsx +14 -0
  36. package/src/index.tsx +9 -0
  37. package/src/mockdata.large.ts +25217 -0
  38. package/src/mockdata.ts +287 -0
  39. package/src/types.ts +78 -0
  40. package/src/useExpanded.ts +130 -0
  41. package/src/useLastViewed.ts +48 -0
  42. package/src/util/status.tsx +70 -0
  43. package/src/util/tree.ts +91 -0
@@ -0,0 +1,262 @@
1
+ import React from 'react';
2
+
3
+ import type { IndexHash, State } from '@storybook/manager-api';
4
+ import { /* ManagerContext */ types } from '@storybook/manager-api';
5
+ import type { StoryObj, Meta } from '@storybook/react';
6
+ import type { Addon_SidebarTopType } from '@storybook/types';
7
+
8
+ import { Sidebar } from './Sidebar';
9
+ // import { standardData as standardHeaderData } from './Heading.stories';
10
+ import { mockDataset } from './mockdata';
11
+ import type { RefType } from './types';
12
+ import { LayoutProvider } from './LayoutProvider';
13
+ import { Button } from './Button';
14
+ import { IconButton } from './IconButton';
15
+ import { FaceHappyIcon } from './icon/FaceHappyIcon';
16
+ import { DEFAULT_REF_ID } from './constants';
17
+
18
+ // const menuItems = [
19
+ // { title: 'Menu Item 1', onClick: () => console.log('onActivateMenuItem'), id: '1' },
20
+ // { title: 'Menu Item 2', onClick: () => console.log('onActivateMenuItem'), id: '2' },
21
+ // { title: 'Menu Item 3', onClick: () => console.log('onActivateMenuItem'), id: '3' },
22
+ // ];
23
+ // export const menu = menuItems;
24
+ const index = mockDataset.withRoot as IndexHash;
25
+ const storyId = 'root-1-child-a2--grandchild-a1-1';
26
+
27
+ // export const simpleData = { menu, index, storyId };
28
+ // export const loadingData = { menu };
29
+
30
+ const meta = {
31
+ component: Sidebar,
32
+ title: 'UI/Sidebar/Sidebar',
33
+ excludeStories: /.*Data$/,
34
+ parameters: { layout: 'fullscreen' },
35
+ args: {
36
+ previewInitialized: true,
37
+ // menu,
38
+ extra: [] as Addon_SidebarTopType[],
39
+ index: index,
40
+ storyId,
41
+ refId: DEFAULT_REF_ID,
42
+ refs: {},
43
+ status: {},
44
+ setSelection: () => {},
45
+ },
46
+ decorators: [
47
+ (storyFn) => (
48
+ // <ManagerContext.Provider
49
+ // value={
50
+ // {
51
+ // state: {
52
+ // docsOptions: {
53
+ // defaultName: 'Docs',
54
+ // autodocs: 'tag',
55
+ // docsMode: false,
56
+ // },
57
+ // },
58
+ // api: {
59
+ // emit: () => {},
60
+ // on: () => {},
61
+ // off: () => {},
62
+ // getShortcutKeys: () => ({ search: ['control', 'shift', 's'] }),
63
+ // },
64
+ // } as any
65
+ // }
66
+ // >
67
+ <LayoutProvider>{storyFn()}</LayoutProvider>
68
+ // </ManagerContext.Provider>
69
+ ),
70
+ ],
71
+ } satisfies Meta<typeof Sidebar>;
72
+
73
+ export default meta;
74
+
75
+ type Story = StoryObj<typeof meta>;
76
+
77
+ const refs: Record<string, RefType> = {
78
+ optimized: {
79
+ id: 'optimized',
80
+ title: 'This is a ref',
81
+ url: 'https://example.com',
82
+ type: 'lazy',
83
+ index,
84
+ previewInitialized: true,
85
+ },
86
+ };
87
+
88
+ const indexError = new Error('Failed to load index');
89
+
90
+ const refsError = {
91
+ optimized: {
92
+ ...refs.optimized,
93
+ index: undefined as IndexHash,
94
+ indexError,
95
+ },
96
+ };
97
+
98
+ const refsEmpty = {
99
+ optimized: {
100
+ ...refs.optimized,
101
+ // type: 'auto-inject',
102
+ index: {} as IndexHash,
103
+ },
104
+ };
105
+
106
+ export const Simple: Story = {};
107
+
108
+ export const Loading: Story = {
109
+ args: {
110
+ previewInitialized: false,
111
+ index: undefined,
112
+ },
113
+ };
114
+
115
+ export const Empty: Story = {
116
+ args: {
117
+ index: {},
118
+ },
119
+ };
120
+
121
+ export const IndexError: Story = {
122
+ args: {
123
+ indexError,
124
+ },
125
+ };
126
+
127
+ export const WithRefs: Story = {
128
+ args: {
129
+ refs,
130
+ },
131
+ };
132
+
133
+ export const LoadingWithRefs: Story = {
134
+ args: {
135
+ ...Loading.args,
136
+ refs,
137
+ },
138
+ };
139
+
140
+ export const LoadingWithRefError: Story = {
141
+ args: {
142
+ ...Loading.args,
143
+ refs: refsError,
144
+ },
145
+ };
146
+
147
+ export const WithRefEmpty: Story = {
148
+ args: {
149
+ ...Empty.args,
150
+ refs: refsEmpty,
151
+ },
152
+ };
153
+
154
+ export const StatusesCollapsed: Story = {
155
+ args: {
156
+ status: Object.entries(index).reduce<State['status']>((acc, [id, item]) => {
157
+ if (item.type !== 'story') {
158
+ return acc;
159
+ }
160
+
161
+ if (item.name.includes('B')) {
162
+ return {
163
+ ...acc,
164
+ [id]: {
165
+ addonA: { status: 'warn', title: 'Addon A', description: 'We just wanted you to know' },
166
+ addonB: { status: 'error', title: 'Addon B', description: 'This is a big deal!' },
167
+ },
168
+ };
169
+ }
170
+ return acc;
171
+ }, {}),
172
+ },
173
+ };
174
+
175
+ export const StatusesOpen: Story = {
176
+ ...StatusesCollapsed,
177
+ args: {
178
+ ...StatusesCollapsed.args,
179
+ status: Object.entries(index).reduce<State['status']>((acc, [id, item]) => {
180
+ if (item.type !== 'story') {
181
+ return acc;
182
+ }
183
+
184
+ return {
185
+ ...acc,
186
+ [id]: {
187
+ addonA: { status: 'warn', title: 'Addon A', description: 'We just wanted you to know' },
188
+ addonB: { status: 'error', title: 'Addon B', description: 'This is a big deal!' },
189
+ },
190
+ };
191
+ }, {}),
192
+ },
193
+ };
194
+
195
+ export const Searching: Story = {
196
+ ...StatusesOpen,
197
+ parameters: { chromatic: { delay: 2200 } },
198
+ };
199
+
200
+ export const Bottom: Story = {
201
+ args: {
202
+ bottom: [
203
+ {
204
+ id: '1',
205
+ type: types.experimental_SIDEBAR_BOTTOM,
206
+ render: () => (
207
+ <Button>
208
+ <FaceHappyIcon />
209
+ Custom addon A
210
+ </Button>
211
+ ),
212
+ },
213
+ {
214
+ id: '2',
215
+ type: types.experimental_SIDEBAR_BOTTOM,
216
+ render: () => <Button text="Custom addon B" Icon={FaceHappyIcon} />,
217
+ },
218
+ {
219
+ id: '3',
220
+ type: types.experimental_SIDEBAR_BOTTOM,
221
+ render: () => (
222
+ <IconButton>
223
+ <FaceHappyIcon />
224
+ </IconButton>
225
+ ),
226
+ },
227
+ ],
228
+ },
229
+ };
230
+
231
+ /**
232
+ * Given the following sequence of events:
233
+ * 1. Story is selected at the top of the sidebar
234
+ * 2. The sidebar is scrolled to the bottom
235
+ * 3. Some re-rendering happens because of a changed state/prop
236
+ * The sidebar should remain scrolled to the bottom
237
+ */
238
+ export const Scrolled: Story = {
239
+ parameters: {
240
+ // we need a very short viewport
241
+ viewport: {
242
+ defaultViewport: 'mobile1',
243
+ defaultOrientation: 'landscape',
244
+ },
245
+ },
246
+ args: {
247
+ storyId: 'group-1--child-b1',
248
+ },
249
+ render: function Render(args) {
250
+ const [, setState] = React.useState(0);
251
+ return (
252
+ <>
253
+ <Button
254
+ style={{ position: 'absolute', zIndex: 10 }}
255
+ onPress={() => setState(() => Math.random())}
256
+ text="Change state"
257
+ />
258
+ <Sidebar {...args} />
259
+ </>
260
+ );
261
+ },
262
+ };
@@ -0,0 +1,200 @@
1
+ import React, { useMemo } from 'react';
2
+
3
+ import { styled } from '@storybook/react-native-theming';
4
+ // import { ScrollArea, Spaced } from '@storybook/components';
5
+ import type { State } from '@storybook/manager-api';
6
+
7
+ import type {
8
+ Addon_SidebarBottomType,
9
+ Addon_SidebarTopType,
10
+ API_LoadedRefData,
11
+ } from '@storybook/types';
12
+ // import type { HeadingProps } from './Heading';
13
+ // import { Heading } from './Heading';
14
+
15
+ import { Explorer } from './Explorer';
16
+
17
+ import { Search } from './Search';
18
+
19
+ import { SearchResults } from './SearchResults';
20
+ import type { CombinedDataset, Selection } from './types';
21
+ import { useLastViewed } from './useLastViewed';
22
+ import { DEFAULT_REF_ID } from './constants';
23
+ import { View } from 'react-native';
24
+
25
+ const Container = styled.View(({ theme }) => ({
26
+ // position: 'absolute',
27
+ // zIndex: 1,
28
+ // left: 0,
29
+ // top: 0,
30
+ // bottom: 0,
31
+ // right: 0,
32
+ width: '100%',
33
+ height: '100%',
34
+ display: 'flex',
35
+ flexDirection: 'column',
36
+ background: theme.background.content,
37
+
38
+ // [MEDIA_DESKTOP_BREAKPOINT]: {
39
+ // background: theme.background.app,
40
+ // },
41
+ }));
42
+
43
+ const Top = styled.View({
44
+ paddingLeft: 4,
45
+ paddingRight: 4,
46
+ // paddingBottom: 20,
47
+ paddingTop: 16,
48
+ flex: 1,
49
+ flexDirection: 'row',
50
+ });
51
+
52
+ // const Bottom = styled.View(({ theme }) => ({
53
+ // borderTopWidth: 1,
54
+ // borderTopColor: theme.appBorderColor,
55
+ // padding: theme.layoutMargin / 2,
56
+ // display: 'flex',
57
+ // flexDirection: 'row',
58
+ // flexWrap: 'wrap',
59
+ // gap: theme.layoutMargin / 2,
60
+ // backgroundColor: theme.barBg,
61
+ // }));
62
+
63
+ const Swap = React.memo(function Swap({
64
+ children,
65
+ condition,
66
+ }: {
67
+ children: React.ReactNode;
68
+ condition: boolean;
69
+ }) {
70
+ const [a, b] = React.Children.toArray(children);
71
+ return (
72
+ <>
73
+ <View style={{ display: condition ? 'flex' : 'none' }}>{a}</View>
74
+ <View style={{ display: condition ? 'none' : 'flex' }}>{b}</View>
75
+ </>
76
+ );
77
+ });
78
+
79
+ export const useCombination = (
80
+ index: SidebarProps['index'],
81
+ indexError: SidebarProps['indexError'],
82
+ previewInitialized: SidebarProps['previewInitialized'],
83
+ status: SidebarProps['status'],
84
+ refs: SidebarProps['refs']
85
+ ): CombinedDataset => {
86
+ const hash = useMemo(
87
+ () => ({
88
+ [DEFAULT_REF_ID]: {
89
+ index,
90
+ indexError,
91
+ previewInitialized,
92
+ status,
93
+ title: null,
94
+ id: DEFAULT_REF_ID,
95
+ url: 'iframe.html',
96
+ },
97
+ ...refs,
98
+ }),
99
+ [refs, index, indexError, previewInitialized, status]
100
+ );
101
+ return useMemo(() => ({ hash, entries: Object.entries(hash) }), [hash]);
102
+ };
103
+
104
+ export interface SidebarProps extends API_LoadedRefData {
105
+ refs: State['refs'];
106
+ status: State['status'];
107
+ // menu: any[];
108
+ extra: Addon_SidebarTopType[];
109
+ bottom?: Addon_SidebarBottomType[];
110
+ storyId?: string;
111
+ refId?: string;
112
+ menuHighlighted?: boolean;
113
+ setSelection: (selection: Selection) => void;
114
+ // enableShortcuts?: boolean;
115
+ // onMenuClick?: HeadingProps['onMenuClick'];
116
+ }
117
+
118
+ export const Sidebar = React.memo(function Sidebar({
119
+ storyId = null,
120
+ refId = DEFAULT_REF_ID,
121
+ index,
122
+ indexError,
123
+ status,
124
+ previewInitialized,
125
+ // menu,
126
+ // extra,
127
+ // bottom = [],
128
+ // menuHighlighted = false,
129
+ // enableShortcuts = true,
130
+ refs = {},
131
+ setSelection,
132
+ }: // onMenuClick,
133
+ SidebarProps) {
134
+ const selected: Selection = useMemo(() => storyId && { storyId, refId }, [storyId, refId]);
135
+ const dataset = useCombination(index, indexError, previewInitialized, status, refs);
136
+ // const isLoading = !index && !indexError;
137
+ const lastViewedProps = useLastViewed(selected);
138
+
139
+ // const scrollRef = useRef<ScrollView>(null);
140
+ // const insets = useSafeAreaInsets();
141
+ return (
142
+ <Container style={{ paddingHorizontal: 10 }} /* className="container sidebar-container" */>
143
+ <Top /* row={1.6} */>
144
+ {/* <Heading
145
+ className="sidebar-header"
146
+ menuHighlighted={menuHighlighted}
147
+ menu={menu}
148
+ extra={extra}
149
+ skipLinkHref="#storybook-preview-wrapper"
150
+ isLoading={isLoading}
151
+ onMenuClick={onMenuClick}
152
+ /> */}
153
+ <Search
154
+ dataset={dataset}
155
+ setSelection={setSelection}
156
+ /* enableShortcuts={enableShortcuts} */ {...lastViewedProps}
157
+ >
158
+ {({
159
+ query,
160
+ results,
161
+ isBrowsing,
162
+ closeMenu,
163
+ // getMenuProps,
164
+ getItemProps,
165
+ highlightedIndex,
166
+ }) => (
167
+ <Swap condition={isBrowsing}>
168
+ <Explorer
169
+ dataset={dataset}
170
+ selected={selected}
171
+ isLoading={false}
172
+ isBrowsing={isBrowsing} //todo check me
173
+ setSelection={setSelection}
174
+ />
175
+
176
+ <SearchResults
177
+ query={query}
178
+ results={results}
179
+ closeMenu={closeMenu}
180
+ // getMenuProps={getMenuProps}
181
+ getItemProps={getItemProps}
182
+ highlightedIndex={highlightedIndex}
183
+ // enableShortcuts={enableShortcuts}
184
+ isLoading={false}
185
+ clearLastViewed={lastViewedProps.clearLastViewed}
186
+ />
187
+ </Swap>
188
+ )}
189
+ </Search>
190
+ </Top>
191
+ {/* {isLoading ? null : (
192
+ <Bottom>
193
+ {bottom.map(({ id, render: Render }) => (
194
+ <Render key={id} />
195
+ ))}
196
+ </Bottom>
197
+ )} */}
198
+ </Container>
199
+ );
200
+ });
@@ -0,0 +1,139 @@
1
+ import { useState } from 'react';
2
+ import type { ComponentEntry, IndexHash } from '@storybook/manager-api';
3
+
4
+ import type { StoryObj, Meta } from '@storybook/react';
5
+ import { Tree } from './Tree';
6
+ import { index } from './mockdata.large';
7
+ import { DEFAULT_REF_ID } from './constants';
8
+ import { ScrollView, Text } from 'react-native';
9
+
10
+ const customViewports = {
11
+ sized: {
12
+ name: 'Sized',
13
+ styles: {
14
+ width: '380px',
15
+ height: '90%',
16
+ },
17
+ },
18
+ };
19
+
20
+ const meta = {
21
+ component: Tree,
22
+ title: 'UI/Sidebar/Tree',
23
+ excludeStories: /.*Data$/,
24
+ parameters: {
25
+ layout: 'fullscreen',
26
+ theme: 'light',
27
+ viewport: {
28
+ defaultViewport: 'sized',
29
+ viewports: customViewports,
30
+ },
31
+ },
32
+ decorators: [
33
+ (Story) => (
34
+ <ScrollView>
35
+ <Story />
36
+ </ScrollView>
37
+ ),
38
+ ],
39
+ } as Meta<typeof Tree>;
40
+
41
+ export default meta;
42
+
43
+ const storyId = Object.values(index).find((story) => story.type === 'story').id;
44
+
45
+ type Story = StoryObj<typeof meta>;
46
+
47
+ export const Full: Story = {
48
+ args: {
49
+ docsMode: false,
50
+ isBrowsing: true,
51
+ isMain: true,
52
+ refId: DEFAULT_REF_ID,
53
+ },
54
+ render: function Render(args) {
55
+ const [selectedId, setSelectedId] = useState(storyId);
56
+ return (
57
+ <Tree
58
+ {...args}
59
+ data={index}
60
+ selectedStoryId={selectedId}
61
+ onSelectStoryId={setSelectedId}
62
+ // highlightedRef={{ current: { itemId: selectedId, refId: DEFAULT_REF_ID } }}
63
+ />
64
+ );
65
+ },
66
+ };
67
+ export const Dark: Story = {
68
+ ...Full,
69
+ parameters: { theme: 'dark' },
70
+ };
71
+
72
+ export const SingleStoryComponents: Story = {
73
+ args: {
74
+ docsMode: false,
75
+ isBrowsing: true,
76
+ isMain: true,
77
+ refId: DEFAULT_REF_ID,
78
+ },
79
+ render: function Render(args) {
80
+ const [selectedId, setSelectedId] = useState('tooltip-tooltipbuildlist--default');
81
+ return (
82
+ <Tree
83
+ {...args}
84
+ data={{
85
+ ...{
86
+ single: {
87
+ type: 'component',
88
+ name: 'Single',
89
+ id: 'single',
90
+ parent: null,
91
+ depth: 0,
92
+ children: ['single--single'],
93
+ renderLabel: () => <Text>🔥 Single</Text>,
94
+ },
95
+ 'single--single': {
96
+ type: 'story',
97
+ id: 'single--single',
98
+ title: 'Single',
99
+ name: 'Single',
100
+ tags: [],
101
+ prepared: true,
102
+ args: {},
103
+ argTypes: {},
104
+ initialArgs: {},
105
+ depth: 1,
106
+ parent: 'single',
107
+ renderLabel: () => <Text>🔥 Single</Text>,
108
+ importPath: './single.stories.js',
109
+ },
110
+ },
111
+ ...Object.keys(index).reduce((acc, key) => {
112
+ if (key === 'tooltip-tooltipselect--default') {
113
+ acc['tooltip-tooltipselect--tooltipselect'] = {
114
+ ...index[key],
115
+ id: 'tooltip-tooltipselect--tooltipselect',
116
+ name: 'TooltipSelect',
117
+ };
118
+ return acc;
119
+ }
120
+ if (key === 'tooltip-tooltipselect') {
121
+ acc[key] = {
122
+ ...(index[key] as ComponentEntry),
123
+ children: ['tooltip-tooltipselect--tooltipselect'],
124
+ };
125
+ return acc;
126
+ }
127
+ if (key.startsWith('tooltip')) {
128
+ acc[key] = index[key];
129
+ }
130
+ return acc;
131
+ }, {} as IndexHash),
132
+ }}
133
+ // highlightedRef={{ current: { itemId: selectedId, refId: DEFAULT_REF_ID } }}
134
+ selectedStoryId={selectedId}
135
+ onSelectStoryId={setSelectedId}
136
+ />
137
+ );
138
+ },
139
+ };