@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
package/src/Search.tsx ADDED
@@ -0,0 +1,336 @@
1
+ import { useStorybookApi /* shortcutToHumanString */ } from '@storybook/manager-api';
2
+ import { styled } from '@storybook/react-native-theming';
3
+ // import type { DownshiftState, StateChangeOptions } from 'downshift';
4
+ // import Downshift from 'downshift';
5
+ import type { IFuseOptions } from 'fuse.js';
6
+ import Fuse from 'fuse.js';
7
+ // import { global } from '@storybook/global';
8
+ import React, { useRef, useState, useCallback } from 'react';
9
+ // import { CloseIcon, SearchIcon } from '@storybook/icons';
10
+ // import { DEFAULT_REF_ID } from './constants';
11
+ import {
12
+ type CombinedDataset,
13
+ type SearchItem,
14
+ type SearchResult,
15
+ // DownshiftItem,
16
+ type SearchChildrenFn,
17
+ type Selection,
18
+ type GetSearchItemProps,
19
+ isExpandType,
20
+ } from './types';
21
+ // import { isSearchResult, isExpandType } from './types';
22
+
23
+ import { /* scrollIntoView */ searchItem } from './util/tree';
24
+ import { getGroupStatus, getHighestStatus } from './util/status';
25
+ // import { useLayout } from './LayoutProvider';
26
+ import { SearchIcon } from './icon/SearchIcon';
27
+ import { CloseIcon } from './icon/CloseIcon';
28
+ import { TextInput, View } from 'react-native';
29
+ import { DEFAULT_REF_ID } from './constants';
30
+ import { BottomSheetTextInput } from '@gorhom/bottom-sheet';
31
+
32
+ const DEFAULT_MAX_SEARCH_RESULTS = 50;
33
+
34
+ const options = {
35
+ shouldSort: true,
36
+ tokenize: true,
37
+ findAllMatches: true,
38
+ includeScore: true,
39
+ includeMatches: true,
40
+ threshold: 0.2,
41
+ location: 0,
42
+ distance: 100,
43
+ maxPatternLength: 32,
44
+ minMatchCharLength: 1,
45
+ keys: [
46
+ { name: 'name', weight: 0.7 },
47
+ { name: 'path', weight: 0.3 },
48
+ ],
49
+ } as IFuseOptions<SearchItem>;
50
+
51
+ const SearchIconWrapper = styled.View(({ theme }) => ({
52
+ position: 'absolute',
53
+ top: 0,
54
+ left: 8,
55
+ zIndex: 1,
56
+ pointerEvents: 'none',
57
+ color: theme.textMutedColor,
58
+ display: 'flex',
59
+ flexDirection: 'row',
60
+ alignItems: 'center',
61
+ height: '100%',
62
+ }));
63
+
64
+ const SearchField = styled.View({
65
+ display: 'flex',
66
+ flexDirection: 'column',
67
+ position: 'relative',
68
+ // marginBottom: 16,
69
+ });
70
+
71
+ const Input = styled(BottomSheetTextInput)(({ theme }) => ({
72
+ // appearance: 'none',
73
+ height: 32,
74
+ paddingLeft: 28,
75
+ paddingRight: 28,
76
+ borderWidth: 1,
77
+ borderColor: theme.appBorderColor,
78
+ backgroundColor: 'transparent',
79
+ borderRadius: 4,
80
+ fontSize: theme.typography.size.s1 + 1,
81
+ // transition: 'all 150ms',
82
+ color: theme.color.defaultText,
83
+ width: '100%',
84
+
85
+ // '&:focus, &:active': {
86
+ // outline: 0,
87
+ // borderColor: theme.color.secondary,
88
+ // background: theme.background.app,
89
+ // },
90
+ // '&::placeholder': {
91
+ // color: theme.textMutedColor,
92
+ // opacity: 1,
93
+ // },
94
+ // '&:valid ~ code, &:focus ~ code': {
95
+ // display: 'none',
96
+ // },
97
+ // '&:invalid ~ svg': {
98
+ // display: 'none',
99
+ // },
100
+ // '&:valid ~ svg': {
101
+ // display: 'block',
102
+ // },
103
+ // '&::-ms-clear': {
104
+ // display: 'none',
105
+ // },
106
+ // '&::-webkit-search-decoration, &::-webkit-search-cancel-button, &::-webkit-search-results-button, &::-webkit-search-results-decoration':
107
+ // {
108
+ // display: 'none',
109
+ // },
110
+ }));
111
+
112
+ // const FocusKey = styled.Text(({ theme }) => ({
113
+ // position: 'absolute',
114
+ // top: 8,
115
+ // right: 9,
116
+ // height: 16,
117
+ // zIndex: 1,
118
+ // lineHeight: 16,
119
+ // textAlign: 'center',
120
+ // fontSize: 11,
121
+ // color: theme.base === 'light' ? theme.color.dark : theme.textMutedColor,
122
+ // userSelect: 'none',
123
+ // pointerEvents: 'none',
124
+ // display: 'flex',
125
+ // flexDirection: 'row',
126
+ // alignItems: 'center',
127
+ // gap: 4,
128
+ // }));
129
+
130
+ // const FocusKeyCmd = styled.Text({
131
+ // fontSize: 14,
132
+ // });
133
+
134
+ const ClearIcon = styled.TouchableOpacity(({ theme }) => ({
135
+ position: 'absolute',
136
+ top: 0,
137
+ bottom: 0,
138
+ right: 8,
139
+ zIndex: 1,
140
+ color: theme.textMutedColor,
141
+ cursor: 'pointer',
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ height: '100%',
146
+ }));
147
+
148
+ // const FocusContainer = styled.View({});
149
+
150
+ export const Search = React.memo<{
151
+ children: SearchChildrenFn;
152
+ dataset: CombinedDataset;
153
+ // enableShortcuts?: boolean;
154
+ setSelection: (selection: Selection) => void;
155
+ getLastViewed: () => Selection[];
156
+ initialQuery?: string;
157
+ }>(function Search({
158
+ children,
159
+ dataset,
160
+ setSelection,
161
+ // enableShortcuts = true,
162
+ getLastViewed,
163
+ initialQuery = '',
164
+ }) {
165
+ const api = useStorybookApi();
166
+ // const inputRef = useRef<HTMLInputElement>(null);
167
+ // const [inputPlaceholder, setPlaceholder] = useState('Find components');
168
+ // const isFocused = useRef(false);
169
+ const inputRef = useRef<TextInput>(null);
170
+ const [inputValue, setInputValue] = useState(initialQuery);
171
+ const [isOpen, setIsOpen] = useState(false);
172
+ const [allComponents, showAllComponents] = useState(false);
173
+ // const searchShortcut = api ? shortcutToHumanString(api.getShortcutKeys().search) : '/';
174
+
175
+ const selectStory = useCallback(
176
+ (id: string, refId: string) => {
177
+ if (api) {
178
+ api.selectStory(id, undefined, { ref: refId !== DEFAULT_REF_ID && refId });
179
+ }
180
+ setSelection({ storyId: id, refId });
181
+ inputRef.current?.blur();
182
+ // inputRef.current?.clear();
183
+ // setInputValue('');
184
+
185
+ showAllComponents(false);
186
+ },
187
+ [api, setSelection]
188
+ );
189
+
190
+ const getItemProps: GetSearchItemProps = useCallback(
191
+ ({ item: result }) => {
192
+ return {
193
+ icon: result?.item?.type === 'component' ? 'component' : 'story',
194
+ // isHighlighted:
195
+ result,
196
+ onPress: () => {
197
+ if (result?.item?.type === 'story') {
198
+ selectStory(result.item.id, result.item.refId);
199
+ } else if (result?.item?.type === 'component') {
200
+ selectStory(result.item.children[0], result.item.refId);
201
+ } else if (isExpandType(result) && result.showAll) {
202
+ result.showAll();
203
+ }
204
+
205
+ // selectStory(result.item.id, result.item.refId);
206
+ },
207
+ score: result.score,
208
+ refIndex: result.refIndex,
209
+ item: result.item,
210
+ matches: result.matches,
211
+ isHighlighted: false,
212
+ // isHighlighted: searchItem.
213
+ // isHighlighted: searchItem.item.
214
+ };
215
+ },
216
+ [selectStory]
217
+ );
218
+
219
+ const makeFuse = useCallback(() => {
220
+ const list = dataset.entries.reduce<SearchItem[]>((acc, [refId, { index, status }]) => {
221
+ const groupStatus = getGroupStatus(index || {}, status);
222
+
223
+ if (index) {
224
+ acc.push(
225
+ ...Object.values(index).map((item) => {
226
+ const statusValue =
227
+ status && status[item.id]
228
+ ? getHighestStatus(Object.values(status[item.id] || {}).map((s) => s.status))
229
+ : null;
230
+ return {
231
+ ...searchItem(item, dataset.hash[refId]),
232
+ status: statusValue || groupStatus[item.id] || null,
233
+ };
234
+ })
235
+ );
236
+ }
237
+ return acc;
238
+ }, []);
239
+ return new Fuse(list, options);
240
+ }, [dataset]);
241
+
242
+ const getResults = useCallback(
243
+ (input: string) => {
244
+ const fuse = makeFuse();
245
+ if (!input) return [];
246
+
247
+ let results = [];
248
+ const resultIds: Set<string> = new Set();
249
+ const distinctResults = (fuse.search(input) as SearchResult[]).filter(({ item }) => {
250
+ if (
251
+ !(item.type === 'component' || item.type === 'docs' || item.type === 'story') ||
252
+ resultIds.has(item.parent)
253
+ ) {
254
+ return false;
255
+ }
256
+ resultIds.add(item.id);
257
+ return true;
258
+ });
259
+
260
+ if (distinctResults.length) {
261
+ results = distinctResults.slice(0, allComponents ? 1000 : DEFAULT_MAX_SEARCH_RESULTS);
262
+ if (distinctResults.length > DEFAULT_MAX_SEARCH_RESULTS && !allComponents) {
263
+ results.push({
264
+ showAll: () => showAllComponents(true),
265
+ totalCount: distinctResults.length,
266
+ moreCount: distinctResults.length - DEFAULT_MAX_SEARCH_RESULTS,
267
+ });
268
+ }
269
+ }
270
+
271
+ const lastViewed = !input && getLastViewed();
272
+ if (lastViewed && lastViewed.length) {
273
+ results = lastViewed.reduce((acc, { storyId, refId }) => {
274
+ const data = dataset.hash[refId];
275
+ if (data && data.index && data.index[storyId]) {
276
+ const story = data.index[storyId];
277
+ const item = story.type === 'story' ? data.index[story.parent] : story;
278
+ // prevent duplicates
279
+ if (!acc.some((res) => res.item.refId === refId && res.item.id === item.id)) {
280
+ acc.push({ item: searchItem(item, dataset.hash[refId]), matches: [], score: 0 });
281
+ }
282
+ }
283
+ return acc;
284
+ }, []);
285
+ }
286
+
287
+ return results;
288
+ },
289
+ [allComponents, dataset.hash, getLastViewed, makeFuse]
290
+ );
291
+
292
+ // const { isMobile } = useLayout();
293
+
294
+ const input = inputValue ? inputValue.trim() : '';
295
+ const results = input ? getResults(input) : [];
296
+
297
+ return (
298
+ <View style={{ flex: 1 }}>
299
+ <SearchField
300
+ // {...getRootProps({ refKey: '' }, { suppressRefError: true })}
301
+ // className="search-field"
302
+ >
303
+ <SearchIconWrapper>
304
+ <SearchIcon />
305
+ </SearchIconWrapper>
306
+
307
+ <Input
308
+ ref={inputRef as any} // TODO find solution for this
309
+ onChangeText={setInputValue}
310
+ onFocus={() => setIsOpen(true)}
311
+ onBlur={() => setIsOpen(false)}
312
+ />
313
+ {isOpen && (
314
+ <ClearIcon
315
+ onPress={() => {
316
+ setInputValue('');
317
+ inputRef.current.clear();
318
+ }}
319
+ >
320
+ <CloseIcon />
321
+ </ClearIcon>
322
+ )}
323
+ </SearchField>
324
+
325
+ {children({
326
+ query: input,
327
+ results,
328
+ isBrowsing: !isOpen || !inputValue.length,
329
+ closeMenu: () => {},
330
+ // getMenuProps,
331
+ getItemProps,
332
+ highlightedIndex: null,
333
+ })}
334
+ </View>
335
+ );
336
+ });
@@ -0,0 +1,315 @@
1
+ import { styled } from '@storybook/react-native-theming';
2
+ import type { FC, PropsWithChildren, ReactNode } from 'react';
3
+ import React, { useCallback } from 'react';
4
+ // import type { ControllerStateAndHelpers } from 'downshift';
5
+
6
+ // import { useStorybookApi } from '@storybook/manager-api';
7
+ // import { PRELOAD_ENTRIES } from '@storybook/core-events';
8
+ import { transparentize } from 'polished';
9
+ // import { TrashIcon } from '@storybook/icons';
10
+ import type { GetSearchItemProps, SearchResult, SearchResultProps } from './types';
11
+ import { isExpandType } from './types';
12
+
13
+ import { FuseResultMatch } from 'fuse.js';
14
+ import { PressableProps, Text, View } from 'react-native';
15
+ import { Button } from './Button';
16
+ import { IconButton } from './IconButton';
17
+ import { ComponentIcon } from './icon/ComponentIcon';
18
+ import { StoryIcon } from './icon/StoryIcon';
19
+ import { statusMapping } from './util/status';
20
+ // import { UseSymbol } from './IconSymbols';
21
+
22
+ const ResultsList = styled.View({
23
+ margin: 0,
24
+ padding: 0,
25
+ marginTop: 8,
26
+ });
27
+
28
+ const ResultRow = styled.TouchableOpacity<{ isHighlighted: boolean }>(
29
+ ({ theme, isHighlighted }) => ({
30
+ width: '100%',
31
+ border: 'none',
32
+ cursor: 'pointer',
33
+ display: 'flex',
34
+ flexDirection: 'row',
35
+ alignItems: 'flex-start',
36
+ textAlign: 'left',
37
+ color: 'inherit',
38
+ fontSize: theme.typography.size.s2,
39
+ backgroundColor: isHighlighted ? theme.background.hoverable : 'transparent',
40
+ minHeight: 28,
41
+ borderRadius: 4,
42
+ gap: 6,
43
+ paddingTop: 7,
44
+ paddingBottom: 7,
45
+ paddingLeft: 8,
46
+ paddingRight: 8,
47
+
48
+ '&:hover, &:focus': {
49
+ backgroundColor: transparentize(0.93, theme.color.secondary),
50
+ outline: 'none',
51
+ },
52
+ })
53
+ );
54
+
55
+ const IconWrapper = styled.View({
56
+ marginTop: 2,
57
+ });
58
+
59
+ const ResultRowContent = styled.View(() => ({
60
+ display: 'flex',
61
+ flexDirection: 'column',
62
+ }));
63
+
64
+ const NoResults = styled.View(({ theme }) => ({
65
+ marginTop: 20,
66
+ textAlign: 'center',
67
+ fontSize: theme.typography.size.s2,
68
+ lineHeight: 18,
69
+ color: theme.color.defaultText,
70
+ // small: {
71
+ // color: theme.barTextColor,
72
+ // fontSize: theme.typography.size.s1,
73
+ // },
74
+ }));
75
+
76
+ const Mark = styled.Text(({ theme }) => ({
77
+ backgroundColor: 'transparent',
78
+ // fontSize: theme.typography.size.s1 - 1,
79
+ color: theme.color.secondary,
80
+ }));
81
+
82
+ const MoreWrapper = styled.View({
83
+ marginTop: 8,
84
+ });
85
+
86
+ const RecentlyOpenedTitle = styled.View(({ theme }) => ({
87
+ display: 'flex',
88
+ flexDirection: 'row',
89
+ justifyContent: 'space-between',
90
+ fontSize: theme.typography.size.s1 - 1,
91
+ fontWeight: theme.typography.weight.bold,
92
+ minHeight: 28,
93
+ // letterSpacing: '0.16em', <-- todo
94
+ textTransform: 'uppercase',
95
+ color: theme.textMutedColor,
96
+ marginTop: 16,
97
+ marginBottom: 4,
98
+ alignItems: 'center',
99
+
100
+ // '.search-result-recentlyOpened-clear': {
101
+ // visibility: 'hidden',
102
+ // },
103
+
104
+ // '&:hover': {
105
+ // '.search-result-recentlyOpened-clear': {
106
+ // visibility: 'visible',
107
+ // },
108
+ // },
109
+ }));
110
+
111
+ const Highlight: FC<PropsWithChildren<{ match?: FuseResultMatch }>> = React.memo(
112
+ function Highlight({ children, match }) {
113
+ if (!match) return children;
114
+ const { value, indices } = match;
115
+
116
+ const { nodes: result } = indices.reduce<{ cursor: number; nodes: ReactNode[] }>(
117
+ ({ cursor, nodes }, [start, end], index, { length }) => {
118
+ nodes.push(<Text key={`text-${index}`}>{value.slice(cursor, start)}</Text>);
119
+ nodes.push(<Mark key={`mark-${index}`}>{value.slice(start, end + 1)}</Mark>);
120
+ if (index === length - 1) {
121
+ nodes.push(<Text key={`last-${index}`}>{value.slice(end + 1)}</Text>);
122
+ }
123
+ return { cursor: end + 1, nodes };
124
+ },
125
+ { cursor: 0, nodes: [] }
126
+ );
127
+ return <Text key={`end-${match.key}`}>{result}</Text>;
128
+ }
129
+ );
130
+
131
+ const Title = styled.Text(({ theme }) => ({
132
+ // display: 'grid',
133
+ justifyContent: 'flex-start',
134
+ // gridAutoColumns: 'auto',
135
+ // gridAutoFlow: 'column',
136
+ color: theme.textMutedColor,
137
+ fontSize: theme.typography.size.s2,
138
+ // '& > span': {
139
+ // display: 'block',
140
+ // whiteSpace: 'nowrap',
141
+ // overflow: 'hidden',
142
+ // textOverflow: 'ellipsis',
143
+ // },
144
+ }));
145
+
146
+ const Path = styled.View(({ theme }) => ({
147
+ // display: 'grid',
148
+ justifyContent: 'flex-start',
149
+ marginVertical: 2,
150
+ // gridAutoColumns: 'auto',
151
+ // gridAutoFlow: 'column',
152
+ color: theme.textMutedColor,
153
+ fontSize: theme.typography.size.s1 - 1,
154
+ flexDirection: 'row',
155
+ // '& > span': {
156
+ // display: 'block',
157
+ // whiteSpace: 'nowrap',
158
+ // overflow: 'hidden',
159
+ // textOverflow: 'ellipsis',
160
+ // },
161
+
162
+ // '& > span + span': {
163
+ // '&:before': {
164
+ // content: "' / '",
165
+ // },
166
+ // },
167
+ }));
168
+
169
+ const PathText = styled.Text(({ theme }) => ({
170
+ fontSize: theme.typography.size.s1 - 1,
171
+ color: theme.textMutedColor,
172
+ }));
173
+
174
+ const Result: FC<SearchResultProps> = React.memo(function Result({
175
+ item,
176
+ matches,
177
+ icon: _icon,
178
+ onPress,
179
+ ...props
180
+ }) {
181
+ const press: PressableProps['onPress'] = useCallback(
182
+ (event) => {
183
+ event.preventDefault();
184
+ onPress?.(event);
185
+ },
186
+ [onPress]
187
+ );
188
+
189
+ // const api = useStorybookApi();
190
+ // useEffect(() => {
191
+ // if (api && props.isHighlighted && item.type === 'component') {
192
+ // // api.emit(PRELOAD_ENTRIES, { ids: [item.children[0]] }, { options: { target: item.refId } });
193
+ // }
194
+ // }, [props.isHighlighted, item]);
195
+
196
+ const nameMatch = matches.find((match: FuseResultMatch) => match.key === 'name');
197
+ const pathMatches = matches.filter((match: FuseResultMatch) => match.key === 'path');
198
+
199
+ const [i] = item.status ? statusMapping[item.status] : [];
200
+
201
+ return (
202
+ <ResultRow {...props} onPress={press}>
203
+ <IconWrapper>
204
+ {item.type === 'component' && <ComponentIcon width="14" height="14" />}
205
+ {item.type === 'story' && <StoryIcon width="14" height="14" />}
206
+ </IconWrapper>
207
+ <ResultRowContent testID="search-result-item--label">
208
+ <Title>
209
+ <Highlight key="search-result-item--label-highlight" match={nameMatch}>
210
+ {item.name}
211
+ </Highlight>
212
+ </Title>
213
+ <Path>
214
+ {item.path.map((group, index) => (
215
+ <View key={index} style={{ flexShrink: 1 }}>
216
+ <PathText>
217
+ <Highlight
218
+ match={pathMatches.find((match: FuseResultMatch) => match.refIndex === index)}
219
+ >
220
+ {/* {index === 0 ? '' : '/'} */}
221
+ {group}
222
+ </Highlight>
223
+ </PathText>
224
+ </View>
225
+ ))}
226
+ </Path>
227
+ </ResultRowContent>
228
+ {item.status ? i : null}
229
+ </ResultRow>
230
+ );
231
+ });
232
+
233
+ // type SearchResult = { item: API_HashEntry; matches: FuseResultMatch[]; score: number };
234
+
235
+ export const SearchResults: FC<{
236
+ query: string;
237
+ results: SearchResult[];
238
+ closeMenu: (cb?: () => void) => void;
239
+ // getMenuProps: any;
240
+ getItemProps: GetSearchItemProps;
241
+ highlightedIndex: number | null;
242
+ isLoading?: boolean;
243
+ enableShortcuts?: boolean;
244
+ clearLastViewed?: () => void;
245
+ }> = React.memo(function SearchResults({
246
+ query,
247
+ results,
248
+ closeMenu,
249
+ // getMenuProps,
250
+ getItemProps,
251
+ highlightedIndex,
252
+ // isLoading = false,
253
+ // enableShortcuts = true,
254
+ clearLastViewed,
255
+ }) {
256
+ // const api = useStorybookApi();
257
+
258
+ const handleClearLastViewed = () => {
259
+ clearLastViewed();
260
+ closeMenu();
261
+ };
262
+
263
+ return (
264
+ <ResultsList /* {...getMenuProps()} */>
265
+ {results.length > 0 && !query && (
266
+ <RecentlyOpenedTitle /* className="search-result-recentlyOpened" */>
267
+ Recently opened
268
+ <IconButton
269
+ // className="search-result-recentlyOpened-clear"
270
+ onPress={handleClearLastViewed}
271
+ >
272
+ {/* <TrashIcon /> */}
273
+ </IconButton>
274
+ </RecentlyOpenedTitle>
275
+ )}
276
+ {results.length === 0 && query && (
277
+ <View>
278
+ <NoResults>
279
+ <Text style={{ marginBottom: 8 }}>No components found</Text>
280
+ {/* <br /> */}
281
+ <Text>Find components by name or path.</Text>
282
+ </NoResults>
283
+ </View>
284
+ )}
285
+ {results.map((result, index) => {
286
+ if (isExpandType(result)) {
287
+ return (
288
+ <MoreWrapper key="search-result-expand">
289
+ <Button
290
+ {...result}
291
+ {...getItemProps({ key: `${index}`, index, item: result })}
292
+ size="small"
293
+ text={`Show ${result.moreCount} more results`}
294
+ />
295
+ </MoreWrapper>
296
+ );
297
+ }
298
+
299
+ const { item } = result;
300
+ const key = `${item.refId}::${item.id}`;
301
+ return (
302
+ <Result
303
+ {...result}
304
+ {...getItemProps({ key, index, item: result })}
305
+ isHighlighted={highlightedIndex === index}
306
+ key={item.id}
307
+ // data-id={result.item.id}
308
+ // data-refid={result.item.refId}
309
+ // className="search-result-item"
310
+ />
311
+ );
312
+ })}
313
+ </ResultsList>
314
+ );
315
+ });