@ledgerhq/lumen-ui-rnative 0.1.21 → 0.1.23

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 (99) hide show
  1. package/dist/module/lib/Components/ListItem/ListItem.js +57 -27
  2. package/dist/module/lib/Components/ListItem/ListItem.js.map +1 -1
  3. package/dist/module/lib/Components/ListItem/ListItem.mdx +15 -7
  4. package/dist/module/lib/Components/ListItem/ListItem.stories.js +497 -283
  5. package/dist/module/lib/Components/ListItem/ListItem.stories.js.map +1 -1
  6. package/dist/module/lib/Components/ListItem/ListItem.test.js +153 -0
  7. package/dist/module/lib/Components/ListItem/ListItem.test.js.map +1 -0
  8. package/dist/module/lib/Components/{TriggerButton/TriggerButton.js → MediaButton/MediaButton.js} +13 -10
  9. package/dist/module/lib/Components/MediaButton/MediaButton.js.map +1 -0
  10. package/{src/lib/Components/TriggerButton/TriggerButton.mdx → dist/module/lib/Components/MediaButton/MediaButton.mdx} +10 -10
  11. package/dist/module/lib/Components/{TriggerButton/TriggerButton.stories.js → MediaButton/MediaButton.stories.js} +18 -18
  12. package/dist/module/lib/Components/MediaButton/MediaButton.stories.js.map +1 -0
  13. package/dist/module/lib/Components/{TriggerButton/TriggerButton.test.js → MediaButton/MediaButton.test.js} +14 -14
  14. package/dist/module/lib/Components/MediaButton/MediaButton.test.js.map +1 -0
  15. package/dist/module/lib/Components/MediaButton/index.js +5 -0
  16. package/dist/module/lib/Components/MediaButton/index.js.map +1 -0
  17. package/dist/module/lib/Components/MediaButton/types.js.map +1 -0
  18. package/dist/module/lib/Components/NavBar/NavBar.js +0 -2
  19. package/dist/module/lib/Components/NavBar/NavBar.js.map +1 -1
  20. package/dist/module/lib/Components/OptionList/OptionList.figma.js +28 -0
  21. package/dist/module/lib/Components/OptionList/OptionList.figma.js.map +1 -0
  22. package/dist/module/lib/Components/OptionList/OptionList.js +452 -0
  23. package/dist/module/lib/Components/OptionList/OptionList.js.map +1 -0
  24. package/dist/module/lib/Components/OptionList/OptionList.mdx +304 -0
  25. package/dist/module/lib/Components/OptionList/OptionList.stories.js +735 -0
  26. package/dist/module/lib/Components/OptionList/OptionList.stories.js.map +1 -0
  27. package/dist/module/lib/Components/OptionList/OptionList.test.js +443 -0
  28. package/dist/module/lib/Components/OptionList/OptionList.test.js.map +1 -0
  29. package/dist/module/lib/Components/OptionList/index.js +5 -0
  30. package/dist/module/lib/Components/OptionList/index.js.map +1 -0
  31. package/dist/module/lib/Components/OptionList/types.js +4 -0
  32. package/dist/module/lib/Components/OptionList/types.js.map +1 -0
  33. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.js +36 -0
  34. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.js.map +1 -0
  35. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.test.js +84 -0
  36. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.test.js.map +1 -0
  37. package/dist/module/lib/Components/index.js +2 -1
  38. package/dist/module/lib/Components/index.js.map +1 -1
  39. package/dist/typescript/src/lib/Components/ListItem/ListItem.d.ts +8 -8
  40. package/dist/typescript/src/lib/Components/ListItem/ListItem.d.ts.map +1 -1
  41. package/dist/typescript/src/lib/Components/ListItem/types.d.ts +11 -7
  42. package/dist/typescript/src/lib/Components/ListItem/types.d.ts.map +1 -1
  43. package/dist/typescript/src/lib/Components/MediaButton/MediaButton.d.ts +23 -0
  44. package/dist/typescript/src/lib/Components/MediaButton/MediaButton.d.ts.map +1 -0
  45. package/dist/typescript/src/lib/Components/MediaButton/index.d.ts +3 -0
  46. package/dist/typescript/src/lib/Components/MediaButton/index.d.ts.map +1 -0
  47. package/dist/typescript/src/lib/Components/{TriggerButton → MediaButton}/types.d.ts +10 -5
  48. package/dist/typescript/src/lib/Components/MediaButton/types.d.ts.map +1 -0
  49. package/dist/typescript/src/lib/Components/OptionList/OptionList.d.ts +12 -0
  50. package/dist/typescript/src/lib/Components/OptionList/OptionList.d.ts.map +1 -0
  51. package/dist/typescript/src/lib/Components/OptionList/OptionList.figma.d.ts +2 -0
  52. package/dist/typescript/src/lib/Components/OptionList/OptionList.figma.d.ts.map +1 -0
  53. package/dist/typescript/src/lib/Components/OptionList/index.d.ts +3 -0
  54. package/dist/typescript/src/lib/Components/OptionList/index.d.ts.map +1 -0
  55. package/dist/typescript/src/lib/Components/OptionList/types.d.ts +97 -0
  56. package/dist/typescript/src/lib/Components/OptionList/types.d.ts.map +1 -0
  57. package/dist/typescript/src/lib/Components/OptionList/useOptionList/useOptionListItems.d.ts +12 -0
  58. package/dist/typescript/src/lib/Components/OptionList/useOptionList/useOptionListItems.d.ts.map +1 -0
  59. package/dist/typescript/src/lib/Components/index.d.ts +2 -1
  60. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  61. package/dist/typescript/src/styles/types/theme.types.d.ts +7 -6
  62. package/dist/typescript/src/styles/types/theme.types.d.ts.map +1 -1
  63. package/package.json +1 -1
  64. package/src/lib/Components/ListItem/ListItem.mdx +15 -7
  65. package/src/lib/Components/ListItem/ListItem.stories.tsx +354 -220
  66. package/src/lib/Components/ListItem/ListItem.test.tsx +152 -0
  67. package/src/lib/Components/ListItem/ListItem.tsx +63 -28
  68. package/src/lib/Components/ListItem/types.ts +11 -8
  69. package/{dist/module/lib/Components/TriggerButton/TriggerButton.mdx → src/lib/Components/MediaButton/MediaButton.mdx} +10 -10
  70. package/src/lib/Components/{TriggerButton/TriggerButton.stories.tsx → MediaButton/MediaButton.stories.tsx} +28 -28
  71. package/src/lib/Components/{TriggerButton/TriggerButton.test.tsx → MediaButton/MediaButton.test.tsx} +22 -22
  72. package/src/lib/Components/{TriggerButton/TriggerButton.tsx → MediaButton/MediaButton.tsx} +27 -21
  73. package/src/lib/Components/MediaButton/index.ts +2 -0
  74. package/src/lib/Components/{TriggerButton → MediaButton}/types.ts +10 -5
  75. package/src/lib/Components/NavBar/NavBar.tsx +0 -3
  76. package/src/lib/Components/OptionList/OptionList.figma.tsx +37 -0
  77. package/src/lib/Components/OptionList/OptionList.mdx +304 -0
  78. package/src/lib/Components/OptionList/OptionList.stories.tsx +755 -0
  79. package/src/lib/Components/OptionList/OptionList.test.tsx +412 -0
  80. package/src/lib/Components/OptionList/OptionList.tsx +532 -0
  81. package/src/lib/Components/OptionList/index.ts +2 -0
  82. package/src/lib/Components/OptionList/types.ts +115 -0
  83. package/src/lib/Components/OptionList/useOptionList/useOptionListItems.test.ts +73 -0
  84. package/src/lib/Components/OptionList/useOptionList/useOptionListItems.ts +49 -0
  85. package/src/lib/Components/index.ts +2 -1
  86. package/src/styles/types/theme.types.ts +8 -6
  87. package/dist/module/lib/Components/TriggerButton/TriggerButton.js.map +0 -1
  88. package/dist/module/lib/Components/TriggerButton/TriggerButton.stories.js.map +0 -1
  89. package/dist/module/lib/Components/TriggerButton/TriggerButton.test.js.map +0 -1
  90. package/dist/module/lib/Components/TriggerButton/index.js +0 -5
  91. package/dist/module/lib/Components/TriggerButton/index.js.map +0 -1
  92. package/dist/module/lib/Components/TriggerButton/types.js.map +0 -1
  93. package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts +0 -23
  94. package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts.map +0 -1
  95. package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts +0 -3
  96. package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts.map +0 -1
  97. package/dist/typescript/src/lib/Components/TriggerButton/types.d.ts.map +0 -1
  98. package/src/lib/Components/TriggerButton/index.ts +0 -2
  99. /package/dist/module/lib/Components/{TriggerButton → MediaButton}/types.js +0 -0
@@ -0,0 +1,755 @@
1
+ import { CryptoIcon } from '@ledgerhq/crypto-icons';
2
+ import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
3
+ import { useState } from 'react';
4
+ import { Settings } from '../../Symbols';
5
+ import { BottomSheet } from '../BottomSheet';
6
+ import { BottomSheetHeader } from '../BottomSheet/BottomSheetHeader';
7
+ import {
8
+ BottomSheetScrollView,
9
+ BottomSheetView,
10
+ } from '../BottomSheet/Scrollables';
11
+ import { useBottomSheetRef } from '../BottomSheet/useBottomSheetRef';
12
+ import { MediaButton } from '../MediaButton';
13
+ import { Spot } from '../Spot';
14
+ import { Tag } from '../Tag/Tag';
15
+ import { Box, Text } from '../Utility';
16
+ import {
17
+ OptionList,
18
+ OptionListContent,
19
+ OptionListEmptyState,
20
+ OptionListItem,
21
+ OptionListItemLeading,
22
+ OptionListItemContent,
23
+ OptionListItemDescription,
24
+ OptionListItemContentRow,
25
+ OptionListTrigger,
26
+ OptionListItemText,
27
+ } from './OptionList';
28
+ import type { OptionListItemData } from './types';
29
+
30
+ const meta = {
31
+ component: OptionList,
32
+ title: 'Selection/OptionList',
33
+ subcomponents: {
34
+ OptionListContent,
35
+ OptionListItem,
36
+ OptionListItemLeading,
37
+ OptionListItemContent,
38
+ OptionListItemText,
39
+ OptionListItemDescription,
40
+ OptionListItemContentRow,
41
+ OptionListTrigger,
42
+ },
43
+ decorators: [
44
+ (Story) => (
45
+ <div>
46
+ <Box
47
+ lx={{
48
+ flex: 1,
49
+ padding: 's24',
50
+ alignItems: 'flex-start',
51
+ width: 's320',
52
+ height: 's480',
53
+ }}
54
+ >
55
+ <Story />
56
+ </Box>
57
+ </div>
58
+ ),
59
+ ],
60
+ parameters: {
61
+ layout: 'fullscreen',
62
+ docs: {
63
+ source: {
64
+ language: 'tsx',
65
+ format: true,
66
+ type: 'code',
67
+ },
68
+ },
69
+ },
70
+ } satisfies Meta<typeof OptionList>;
71
+
72
+ export default meta;
73
+ type Story = StoryObj<typeof OptionList>;
74
+
75
+ const CURRENCIES: OptionListItemData[] = [
76
+ {
77
+ value: 'btc',
78
+ label: 'Bitcoin',
79
+ meta: { ticker: 'BTC', ledgerId: 'bitcoin' },
80
+ },
81
+ {
82
+ value: 'eth',
83
+ label: 'Ethereum',
84
+ meta: { ticker: 'ETH', ledgerId: 'ethereum' },
85
+ },
86
+ {
87
+ value: 'sol',
88
+ label: 'Solana',
89
+ meta: { ticker: 'SOL', ledgerId: 'solana' },
90
+ },
91
+ {
92
+ value: 'dot',
93
+ label: 'Polkadot',
94
+ meta: { ticker: 'DOT', ledgerId: 'polkadot' },
95
+ },
96
+ ];
97
+
98
+ export const Base: Story = {
99
+ render: () => {
100
+ const [value, setValue] = useState<string | null>(null);
101
+ const bottomSheetRef = useBottomSheetRef();
102
+ const selected = CURRENCIES.find((c) => c.value === value);
103
+
104
+ return (
105
+ <>
106
+ <OptionListTrigger
107
+ label='Currency'
108
+ onPress={() => bottomSheetRef.current?.present()}
109
+ >
110
+ {selected && <Text lx={{ color: 'base' }}>{selected.label}</Text>}
111
+ </OptionListTrigger>
112
+ <BottomSheet
113
+ ref={bottomSheetRef}
114
+ enableDynamicSizing
115
+ snapPoints={null}
116
+ onClose={() => bottomSheetRef.current?.dismiss()}
117
+ >
118
+ <BottomSheetView>
119
+ <BottomSheetHeader title='Select currency' />
120
+ <OptionList
121
+ items={CURRENCIES}
122
+ value={value}
123
+ onValueChange={(v) => {
124
+ setValue(v);
125
+ bottomSheetRef.current?.dismiss();
126
+ }}
127
+ >
128
+ <OptionListContent
129
+ renderItem={(item) => {
130
+ const ticker = (item.meta as { ticker: string }).ticker;
131
+ return (
132
+ <OptionListItem value={item.value}>
133
+ <OptionListItemLeading>
134
+ <CryptoIcon
135
+ ledgerId={item.meta?.ledgerId ?? ''}
136
+ ticker={ticker}
137
+ size='32px'
138
+ />
139
+ </OptionListItemLeading>
140
+ <OptionListItemContent>
141
+ <OptionListItemText>{item.label}</OptionListItemText>
142
+ <OptionListItemDescription>
143
+ {ticker}
144
+ </OptionListItemDescription>
145
+ </OptionListItemContent>
146
+ </OptionListItem>
147
+ );
148
+ }}
149
+ />
150
+ </OptionList>
151
+ </BottomSheetView>
152
+ </BottomSheet>
153
+ </>
154
+ );
155
+ },
156
+ };
157
+
158
+ const FOODS: OptionListItemData[] = [
159
+ { value: 'apple', label: 'Apple', group: 'Fruits' },
160
+ { value: 'banana', label: 'Banana', group: 'Fruits' },
161
+ { value: 'orange', label: 'Orange', group: 'Fruits' },
162
+ { value: 'carrot', label: 'Carrot', group: 'Vegetables' },
163
+ { value: 'broccoli', label: 'Broccoli', group: 'Vegetables' },
164
+ { value: 'spinach', label: 'Spinach', group: 'Vegetables' },
165
+ ];
166
+
167
+ export const WithGroups: Story = {
168
+ render: () => {
169
+ const [value, setValue] = useState<string | null>(null);
170
+ const bottomSheetRef = useBottomSheetRef();
171
+ const selected = FOODS.find((f) => f.value === value);
172
+
173
+ return (
174
+ <>
175
+ <OptionListTrigger
176
+ label='Food'
177
+ onPress={() => bottomSheetRef.current?.present()}
178
+ >
179
+ {selected && <Text lx={{ color: 'base' }}>{selected.label}</Text>}
180
+ </OptionListTrigger>
181
+ <BottomSheet
182
+ ref={bottomSheetRef}
183
+ enableDynamicSizing
184
+ snapPoints={null}
185
+ onClose={() => bottomSheetRef.current?.dismiss()}
186
+ >
187
+ <BottomSheetScrollView>
188
+ <BottomSheetHeader title='Pick a food' />
189
+ <OptionList
190
+ items={FOODS}
191
+ value={value}
192
+ onValueChange={(v) => {
193
+ setValue(v);
194
+ bottomSheetRef.current?.dismiss();
195
+ }}
196
+ >
197
+ <OptionListContent
198
+ renderItem={(item) => (
199
+ <OptionListItem value={item.value}>
200
+ <OptionListItemContent>
201
+ <OptionListItemText>{item.label}</OptionListItemText>
202
+ </OptionListItemContent>
203
+ </OptionListItem>
204
+ )}
205
+ />
206
+ </OptionList>
207
+ </BottomSheetScrollView>
208
+ </BottomSheet>
209
+ </>
210
+ );
211
+ },
212
+ };
213
+
214
+ const NETWORKS: OptionListItemData[] = [
215
+ {
216
+ value: 'ethereum',
217
+ label: 'Ethereum',
218
+ meta: { ticker: 'ETH', ledgerId: 'ethereum', tag: 'ERC-20' },
219
+ },
220
+ {
221
+ value: 'polygon',
222
+ label: 'Polygon',
223
+ meta: { ticker: 'MATIC', ledgerId: 'polygon', tag: 'Layer 2' },
224
+ },
225
+ {
226
+ value: 'arbitrum',
227
+ label: 'Arbitrum',
228
+ meta: { ticker: 'ARB', ledgerId: 'arbitrum', tag: 'Layer 2' },
229
+ },
230
+ {
231
+ value: 'optimism',
232
+ label: 'Optimism',
233
+ meta: { ticker: 'OP', ledgerId: 'optimism', tag: 'Layer 2' },
234
+ },
235
+ ];
236
+
237
+ export const WithContentRow: Story = {
238
+ render: () => {
239
+ const [value, setValue] = useState<string | null>(null);
240
+ const bottomSheetRef = useBottomSheetRef();
241
+ const selected = NETWORKS.find((n) => n.value === value);
242
+
243
+ return (
244
+ <>
245
+ <OptionListTrigger
246
+ label='Network'
247
+ onPress={() => bottomSheetRef.current?.present()}
248
+ >
249
+ {selected && <Text lx={{ color: 'base' }}>{selected.label}</Text>}
250
+ </OptionListTrigger>
251
+ <BottomSheet
252
+ ref={bottomSheetRef}
253
+ enableDynamicSizing
254
+ snapPoints={null}
255
+ onClose={() => bottomSheetRef.current?.dismiss()}
256
+ >
257
+ <BottomSheetView>
258
+ <BottomSheetHeader title='Select network' />
259
+ <OptionList
260
+ items={NETWORKS}
261
+ value={value}
262
+ onValueChange={(v) => {
263
+ setValue(v);
264
+ bottomSheetRef.current?.dismiss();
265
+ }}
266
+ >
267
+ <OptionListContent
268
+ renderItem={(item) => {
269
+ const meta = item.meta as {
270
+ ticker: string;
271
+ ledgerId: string;
272
+ tag: string;
273
+ };
274
+ return (
275
+ <OptionListItem value={item.value}>
276
+ <OptionListItemLeading>
277
+ <CryptoIcon
278
+ ledgerId={meta.ledgerId}
279
+ ticker={meta.ticker}
280
+ size='32px'
281
+ />
282
+ </OptionListItemLeading>
283
+ <OptionListItemContent>
284
+ <OptionListItemContentRow>
285
+ <OptionListItemText>{item.label}</OptionListItemText>
286
+ <Tag label={meta.tag} appearance='gray' size='sm' />
287
+ </OptionListItemContentRow>
288
+ <OptionListItemDescription>
289
+ {meta.ticker}
290
+ </OptionListItemDescription>
291
+ </OptionListItemContent>
292
+ </OptionListItem>
293
+ );
294
+ }}
295
+ />
296
+ </OptionList>
297
+ </BottomSheetView>
298
+ </BottomSheet>
299
+ </>
300
+ );
301
+ },
302
+ };
303
+
304
+ const ACCOUNTS: OptionListItemData[] = [
305
+ {
306
+ value: 'savings',
307
+ label: 'Savings Account',
308
+ description: 'High-yield savings',
309
+ },
310
+ {
311
+ value: 'checking',
312
+ label: 'Checking Account',
313
+ description: 'Primary checking',
314
+ disabled: true,
315
+ },
316
+ { value: 'investment', label: 'Investment Account' },
317
+ {
318
+ value: 'retirement',
319
+ label: 'Retirement Fund',
320
+ description: 'Long-term growth',
321
+ disabled: true,
322
+ },
323
+ ];
324
+
325
+ export const WithDisabledItems: Story = {
326
+ render: () => {
327
+ const [value, setValue] = useState<string | null>(null);
328
+ const bottomSheetRef = useBottomSheetRef();
329
+ const selected = ACCOUNTS.find((a) => a.value === value);
330
+
331
+ return (
332
+ <>
333
+ <OptionListTrigger
334
+ label='Account'
335
+ onPress={() => bottomSheetRef.current?.present()}
336
+ >
337
+ {selected && <Text lx={{ color: 'base' }}>{selected.label}</Text>}
338
+ </OptionListTrigger>
339
+ <BottomSheet
340
+ ref={bottomSheetRef}
341
+ enableDynamicSizing
342
+ snapPoints={null}
343
+ onClose={() => bottomSheetRef.current?.dismiss()}
344
+ >
345
+ <BottomSheetView>
346
+ <BottomSheetHeader title='Select account' />
347
+ <OptionList
348
+ items={ACCOUNTS}
349
+ value={value}
350
+ onValueChange={(v) => {
351
+ setValue(v);
352
+ bottomSheetRef.current?.dismiss();
353
+ }}
354
+ >
355
+ <OptionListContent
356
+ renderItem={(item) => (
357
+ <OptionListItem value={item.value} disabled={item.disabled}>
358
+ <OptionListItemLeading>
359
+ <Spot appearance='icon' icon={Settings} />
360
+ </OptionListItemLeading>
361
+ <OptionListItemContent>
362
+ <OptionListItemText>{item.label}</OptionListItemText>
363
+ {item.description && (
364
+ <OptionListItemDescription>
365
+ {item.description}
366
+ </OptionListItemDescription>
367
+ )}
368
+ </OptionListItemContent>
369
+ </OptionListItem>
370
+ )}
371
+ />
372
+ </OptionList>
373
+ </BottomSheetView>
374
+ </BottomSheet>
375
+ </>
376
+ );
377
+ },
378
+ };
379
+
380
+ const GROUPED_NETWORKS: OptionListItemData[] = [
381
+ {
382
+ value: 'eth-main',
383
+ label: 'Ethereum',
384
+ group: 'Layer 1',
385
+ meta: { ticker: 'ETH', ledgerId: 'ethereum', tag: 'Mainnet' },
386
+ },
387
+ {
388
+ value: 'btc-main',
389
+ label: 'Bitcoin',
390
+ group: 'Layer 1',
391
+ meta: { ticker: 'BTC', ledgerId: 'bitcoin', tag: 'Mainnet' },
392
+ },
393
+ {
394
+ value: 'polygon',
395
+ label: 'Polygon',
396
+ group: 'Layer 2',
397
+ meta: { ticker: 'MATIC', ledgerId: 'polygon', tag: 'Rollup' },
398
+ },
399
+ {
400
+ value: 'arbitrum',
401
+ label: 'Arbitrum',
402
+ group: 'Layer 2',
403
+ meta: { ticker: 'ARB', ledgerId: 'arbitrum', tag: 'Rollup' },
404
+ },
405
+ {
406
+ value: 'optimism',
407
+ label: 'Optimism',
408
+ group: 'Layer 2',
409
+ meta: { ticker: 'OP', ledgerId: 'optimism', tag: 'Rollup' },
410
+ },
411
+ ];
412
+
413
+ export const GroupedWithContentRow: Story = {
414
+ render: () => {
415
+ const [value, setValue] = useState<string | null>(null);
416
+ const bottomSheetRef = useBottomSheetRef();
417
+ const selected = GROUPED_NETWORKS.find((n) => n.value === value);
418
+
419
+ return (
420
+ <>
421
+ <OptionListTrigger
422
+ label='Network'
423
+ onPress={() => bottomSheetRef.current?.present()}
424
+ >
425
+ {selected && <Text lx={{ color: 'base' }}>{selected.label}</Text>}
426
+ </OptionListTrigger>
427
+ <BottomSheet
428
+ ref={bottomSheetRef}
429
+ enableDynamicSizing
430
+ snapPoints={null}
431
+ onClose={() => bottomSheetRef.current?.dismiss()}
432
+ >
433
+ <BottomSheetScrollView>
434
+ <BottomSheetHeader title='Select network' />
435
+ <OptionList
436
+ items={GROUPED_NETWORKS}
437
+ value={value}
438
+ onValueChange={(v) => {
439
+ setValue(v);
440
+ bottomSheetRef.current?.dismiss();
441
+ }}
442
+ >
443
+ <OptionListContent
444
+ renderItem={(item) => {
445
+ const meta = item.meta as {
446
+ ticker: string;
447
+ ledgerId: string;
448
+ tag: string;
449
+ };
450
+ return (
451
+ <OptionListItem value={item.value}>
452
+ <OptionListItemLeading>
453
+ <CryptoIcon
454
+ ledgerId={meta.ledgerId}
455
+ ticker={meta.ticker}
456
+ size='32px'
457
+ />
458
+ </OptionListItemLeading>
459
+ <OptionListItemContent>
460
+ <OptionListItemContentRow>
461
+ <OptionListItemText>{item.label}</OptionListItemText>
462
+ <Tag label={meta.tag} appearance='gray' size='sm' />
463
+ </OptionListItemContentRow>
464
+ <OptionListItemDescription>
465
+ {meta.ticker}
466
+ </OptionListItemDescription>
467
+ </OptionListItemContent>
468
+ </OptionListItem>
469
+ );
470
+ }}
471
+ />
472
+ </OptionList>
473
+ </BottomSheetScrollView>
474
+ </BottomSheet>
475
+ </>
476
+ );
477
+ },
478
+ };
479
+
480
+ export const EmptyState: Story = {
481
+ render: () => {
482
+ const bottomSheetRef = useBottomSheetRef();
483
+
484
+ return (
485
+ <>
486
+ <OptionListTrigger
487
+ label='Currency'
488
+ onPress={() => bottomSheetRef.current?.present()}
489
+ />
490
+ <BottomSheet
491
+ ref={bottomSheetRef}
492
+ enableDynamicSizing
493
+ snapPoints={null}
494
+ onClose={() => bottomSheetRef.current?.dismiss()}
495
+ >
496
+ <BottomSheetView>
497
+ <BottomSheetHeader title='Select currency' />
498
+ <OptionList items={[]} value={null}>
499
+ <OptionListContent
500
+ renderItem={(item) => (
501
+ <OptionListItem value={item.value}>
502
+ <OptionListItemContent>
503
+ <OptionListItemText>{item.label}</OptionListItemText>
504
+ </OptionListItemContent>
505
+ </OptionListItem>
506
+ )}
507
+ />
508
+ <OptionListEmptyState
509
+ title='No options available'
510
+ description='There are no items to display'
511
+ />
512
+ </OptionList>
513
+ </BottomSheetView>
514
+ </BottomSheet>
515
+ </>
516
+ );
517
+ },
518
+ };
519
+
520
+ const SIMPLE_OPTIONS: OptionListItemData[] = [
521
+ { value: 'all', label: 'All accounts' },
522
+ { value: 'savings', label: 'Savings' },
523
+ { value: 'checking', label: 'Checking' },
524
+ ];
525
+
526
+ const SETTINGS_OPTIONS: OptionListItemData[] = [
527
+ { value: 'general', label: 'General' },
528
+ { value: 'security', label: 'Security' },
529
+ { value: 'notifications', label: 'Notifications' },
530
+ ];
531
+
532
+ const appearances = ['gray', 'transparent', 'no-background'] as const;
533
+
534
+ const SimpleOptionListSheet = ({
535
+ sheetRef,
536
+ items,
537
+ title,
538
+ value,
539
+ onValueChange,
540
+ }: {
541
+ sheetRef: ReturnType<typeof useBottomSheetRef>;
542
+ items: OptionListItemData[];
543
+ title: string;
544
+ value: string | null;
545
+ onValueChange: (v: string | null) => void;
546
+ }) => (
547
+ <BottomSheet
548
+ ref={sheetRef}
549
+ enableDynamicSizing
550
+ snapPoints={null}
551
+ onClose={() => sheetRef.current?.dismiss()}
552
+ >
553
+ <BottomSheetView>
554
+ <BottomSheetHeader title={title} />
555
+ <OptionList
556
+ items={items}
557
+ value={value}
558
+ onValueChange={(v) => {
559
+ onValueChange(v);
560
+ sheetRef.current?.dismiss();
561
+ }}
562
+ >
563
+ <OptionListContent
564
+ renderItem={(item) => (
565
+ <OptionListItem value={item.value}>
566
+ <OptionListItemContent>
567
+ <OptionListItemText>{item.label}</OptionListItemText>
568
+ </OptionListItemContent>
569
+ </OptionListItem>
570
+ )}
571
+ />
572
+ </OptionList>
573
+ </BottomSheetView>
574
+ </BottomSheet>
575
+ );
576
+
577
+ export const TriggerShowcase: Story = {
578
+ render: () => {
579
+ const [buttonValue, setButtonValue] = useState<string | null>(null);
580
+ const [iconValue, setIconValue] = useState<string | null>(null);
581
+ const [cryptoValue, setCryptoValue] = useState<string | null>(null);
582
+ const [appearanceValues, setAppearanceValues] = useState<
583
+ Record<string, string | null>
584
+ >({});
585
+ const buttonRef = useBottomSheetRef();
586
+ const iconRef = useBottomSheetRef();
587
+ const cryptoRef = useBottomSheetRef();
588
+ const appearanceRefs = {
589
+ gray: useBottomSheetRef(),
590
+ transparent: useBottomSheetRef(),
591
+ 'no-background': useBottomSheetRef(),
592
+ };
593
+
594
+ const selectedButton = SIMPLE_OPTIONS.find((o) => o.value === buttonValue);
595
+ const selectedIcon = SETTINGS_OPTIONS.find((o) => o.value === iconValue);
596
+ const selectedCrypto = CURRENCIES.find((c) => c.value === cryptoValue);
597
+
598
+ return (
599
+ <Box lx={{ gap: 's16', alignItems: 'flex-start' }}>
600
+ <MediaButton
601
+ appearance='gray'
602
+ onPress={() => buttonRef.current?.present()}
603
+ >
604
+ {selectedButton?.label ?? 'All accounts'}
605
+ </MediaButton>
606
+
607
+ <MediaButton appearance='gray' disabled>
608
+ Disabled
609
+ </MediaButton>
610
+
611
+ <MediaButton
612
+ appearance='gray'
613
+ onPress={() => iconRef.current?.present()}
614
+ icon={<Settings size={20} />}
615
+ iconType='flat'
616
+ >
617
+ {selectedIcon?.label ?? 'Settings'}
618
+ </MediaButton>
619
+
620
+ <MediaButton
621
+ appearance='gray'
622
+ onPress={() => cryptoRef.current?.present()}
623
+ icon={
624
+ selectedCrypto?.meta ? (
625
+ <CryptoIcon
626
+ ledgerId={selectedCrypto.meta.ledgerId}
627
+ ticker={selectedCrypto.meta.ticker}
628
+ size='32px'
629
+ />
630
+ ) : undefined
631
+ }
632
+ iconType='rounded'
633
+ >
634
+ {selectedCrypto?.label ?? 'Network'}
635
+ </MediaButton>
636
+
637
+ <Box lx={{ flexDirection: 'row', gap: 's16' }}>
638
+ {appearances.map((appearance) => {
639
+ const selected = SIMPLE_OPTIONS.find(
640
+ (o) => o.value === appearanceValues[appearance],
641
+ );
642
+ return (
643
+ <MediaButton
644
+ key={appearance}
645
+ appearance={appearance}
646
+ onPress={() => appearanceRefs[appearance].current?.present()}
647
+ >
648
+ {selected?.label ?? appearance}
649
+ </MediaButton>
650
+ );
651
+ })}
652
+ </Box>
653
+
654
+ <SimpleOptionListSheet
655
+ sheetRef={buttonRef}
656
+ items={SIMPLE_OPTIONS}
657
+ title='All accounts'
658
+ value={buttonValue}
659
+ onValueChange={setButtonValue}
660
+ />
661
+ <SimpleOptionListSheet
662
+ sheetRef={iconRef}
663
+ items={SETTINGS_OPTIONS}
664
+ title='Settings'
665
+ value={iconValue}
666
+ onValueChange={setIconValue}
667
+ />
668
+ <BottomSheet
669
+ ref={cryptoRef}
670
+ enableDynamicSizing
671
+ snapPoints={null}
672
+ onClose={() => cryptoRef.current?.dismiss()}
673
+ >
674
+ <BottomSheetView>
675
+ <BottomSheetHeader title='Select network' />
676
+ <OptionList
677
+ items={CURRENCIES}
678
+ value={cryptoValue}
679
+ onValueChange={(v) => {
680
+ setCryptoValue(v);
681
+ cryptoRef.current?.dismiss();
682
+ }}
683
+ >
684
+ <OptionListContent
685
+ renderItem={(item) => {
686
+ const ticker = (item.meta as { ticker: string }).ticker;
687
+ return (
688
+ <OptionListItem value={item.value}>
689
+ <OptionListItemLeading>
690
+ <CryptoIcon
691
+ ledgerId={item.meta?.ledgerId ?? ''}
692
+ ticker={ticker}
693
+ size='32px'
694
+ />
695
+ </OptionListItemLeading>
696
+ <OptionListItemContent>
697
+ <OptionListItemText>{item.label}</OptionListItemText>
698
+ <OptionListItemDescription>
699
+ {ticker}
700
+ </OptionListItemDescription>
701
+ </OptionListItemContent>
702
+ </OptionListItem>
703
+ );
704
+ }}
705
+ />
706
+ </OptionList>
707
+ </BottomSheetView>
708
+ </BottomSheet>
709
+ {appearances.map((appearance) => (
710
+ <SimpleOptionListSheet
711
+ key={appearance}
712
+ sheetRef={appearanceRefs[appearance]}
713
+ items={SIMPLE_OPTIONS}
714
+ title={appearance}
715
+ value={appearanceValues[appearance] ?? null}
716
+ onValueChange={(v) =>
717
+ setAppearanceValues((prev) => ({ ...prev, [appearance]: v }))
718
+ }
719
+ />
720
+ ))}
721
+ </Box>
722
+ );
723
+ },
724
+ };
725
+
726
+ export const WithDefaultValue: Story = {
727
+ render: () => (
728
+ <Box lx={{ width: 's320' }}>
729
+ <OptionList items={CURRENCIES} defaultValue='eth'>
730
+ <OptionListContent
731
+ renderItem={(item) => {
732
+ const meta = item.meta as { ticker: string; ledgerId: string };
733
+ return (
734
+ <OptionListItem value={item.value}>
735
+ <OptionListItemLeading>
736
+ <CryptoIcon
737
+ ledgerId={meta.ledgerId}
738
+ ticker={meta.ticker}
739
+ size='32px'
740
+ />
741
+ </OptionListItemLeading>
742
+ <OptionListItemContent>
743
+ <OptionListItemText>{item.label}</OptionListItemText>
744
+ <OptionListItemDescription>
745
+ {meta.ticker}
746
+ </OptionListItemDescription>
747
+ </OptionListItemContent>
748
+ </OptionListItem>
749
+ );
750
+ }}
751
+ />
752
+ </OptionList>
753
+ </Box>
754
+ ),
755
+ };