@urbint/cl 1.0.1

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 (206) hide show
  1. package/.cursor/rules +313 -0
  2. package/.rnstorybook/index.ts +11 -0
  3. package/.rnstorybook/main.ts +8 -0
  4. package/.rnstorybook/preview.tsx +14 -0
  5. package/.rnstorybook/storybook.requires.ts +49 -0
  6. package/.storybook/main.ts +16 -0
  7. package/.storybook/preview.ts +32 -0
  8. package/.storybook/vitest.setup.ts +7 -0
  9. package/App.tsx +422 -0
  10. package/README.md +229 -0
  11. package/app.json +33 -0
  12. package/assets/adaptive-icon.png +0 -0
  13. package/assets/favicon.png +0 -0
  14. package/assets/icon.png +0 -0
  15. package/assets/splash-icon.png +0 -0
  16. package/babel.config.js +16 -0
  17. package/docs/components/CodeBlock.tsx +80 -0
  18. package/docs/components/PropTable.tsx +93 -0
  19. package/docs/components/Sidebar.tsx +199 -0
  20. package/docs/components/index.ts +8 -0
  21. package/docs/data/colorTokens.ts +70 -0
  22. package/docs/data/componentData.tsx +1685 -0
  23. package/docs/data/index.ts +7 -0
  24. package/docs/index.ts +19 -0
  25. package/docs/navigation.ts +94 -0
  26. package/docs/pages/ColorsPage.tsx +226 -0
  27. package/docs/pages/ComponentPage.tsx +235 -0
  28. package/docs/pages/InstallationPage.tsx +232 -0
  29. package/docs/pages/IntroductionPage.tsx +163 -0
  30. package/docs/pages/ThemingPage.tsx +251 -0
  31. package/docs/pages/index.ts +10 -0
  32. package/docs/theme.ts +64 -0
  33. package/docs/types.ts +54 -0
  34. package/index.ts +8 -0
  35. package/llms.txt +1893 -0
  36. package/mcp-config.example.json +10 -0
  37. package/mcp-server/README.md +192 -0
  38. package/mcp-server/package-lock.json +1707 -0
  39. package/mcp-server/package.json +38 -0
  40. package/mcp-server/src/index.ts +1136 -0
  41. package/mcp-server/src/registry/components.ts +1446 -0
  42. package/mcp-server/src/registry/index.ts +3 -0
  43. package/mcp-server/src/registry/tokens.ts +256 -0
  44. package/mcp-server/tsconfig.json +19 -0
  45. package/package.json +92 -0
  46. package/src/components/Accordion/Accordion.stories.tsx +226 -0
  47. package/src/components/Accordion/Accordion.tsx +255 -0
  48. package/src/components/Accordion/index.ts +12 -0
  49. package/src/components/ActionSheet/ActionSheet.stories.tsx +393 -0
  50. package/src/components/ActionSheet/ActionSheet.tsx +258 -0
  51. package/src/components/ActionSheet/index.ts +2 -0
  52. package/src/components/Alert/Alert.stories.tsx +165 -0
  53. package/src/components/Alert/Alert.tsx +164 -0
  54. package/src/components/Alert/index.ts +2 -0
  55. package/src/components/AlertDialog/AlertDialog.stories.tsx +330 -0
  56. package/src/components/AlertDialog/AlertDialog.tsx +234 -0
  57. package/src/components/AlertDialog/index.ts +2 -0
  58. package/src/components/Avatar/Avatar.stories.tsx +154 -0
  59. package/src/components/Avatar/Avatar.tsx +219 -0
  60. package/src/components/Avatar/index.ts +2 -0
  61. package/src/components/Badge/Badge.stories.tsx +146 -0
  62. package/src/components/Badge/Badge.tsx +125 -0
  63. package/src/components/Badge/index.ts +2 -0
  64. package/src/components/Box/Box.stories.tsx +192 -0
  65. package/src/components/Box/Box.tsx +184 -0
  66. package/src/components/Box/index.ts +2 -0
  67. package/src/components/Button/Button.stories.tsx +157 -0
  68. package/src/components/Button/Button.tsx +180 -0
  69. package/src/components/Button/index.ts +2 -0
  70. package/src/components/Card/Card.stories.tsx +145 -0
  71. package/src/components/Card/Card.tsx +169 -0
  72. package/src/components/Card/index.ts +11 -0
  73. package/src/components/Center/Center.stories.tsx +215 -0
  74. package/src/components/Center/Center.tsx +29 -0
  75. package/src/components/Center/index.ts +2 -0
  76. package/src/components/Checkbox/Checkbox.stories.tsx +94 -0
  77. package/src/components/Checkbox/Checkbox.tsx +242 -0
  78. package/src/components/Checkbox/index.ts +2 -0
  79. package/src/components/DatePicker/DatePicker.stories.tsx +623 -0
  80. package/src/components/DatePicker/DatePicker.tsx +1228 -0
  81. package/src/components/DatePicker/index.ts +8 -0
  82. package/src/components/Divider/Divider.stories.tsx +224 -0
  83. package/src/components/Divider/Divider.tsx +73 -0
  84. package/src/components/Divider/index.ts +2 -0
  85. package/src/components/Drawer/Drawer.stories.tsx +414 -0
  86. package/src/components/Drawer/Drawer.tsx +342 -0
  87. package/src/components/Drawer/index.ts +11 -0
  88. package/src/components/Fab/Fab.stories.tsx +360 -0
  89. package/src/components/Fab/Fab.tsx +185 -0
  90. package/src/components/Fab/index.ts +2 -0
  91. package/src/components/FormControl/FormControl.stories.tsx +276 -0
  92. package/src/components/FormControl/FormControl.tsx +185 -0
  93. package/src/components/FormControl/index.ts +12 -0
  94. package/src/components/Grid/Grid.stories.tsx +244 -0
  95. package/src/components/Grid/Grid.tsx +93 -0
  96. package/src/components/Grid/index.ts +2 -0
  97. package/src/components/HStack/HStack.stories.tsx +230 -0
  98. package/src/components/HStack/HStack.tsx +80 -0
  99. package/src/components/HStack/index.ts +2 -0
  100. package/src/components/Heading/Heading.stories.tsx +111 -0
  101. package/src/components/Heading/Heading.tsx +85 -0
  102. package/src/components/Heading/index.ts +2 -0
  103. package/src/components/Icon/Icon.stories.tsx +320 -0
  104. package/src/components/Icon/Icon.tsx +117 -0
  105. package/src/components/Icon/index.ts +2 -0
  106. package/src/components/Image/Image.stories.tsx +357 -0
  107. package/src/components/Image/Image.tsx +168 -0
  108. package/src/components/Image/index.ts +2 -0
  109. package/src/components/Input/Input.stories.tsx +164 -0
  110. package/src/components/Input/Input.tsx +274 -0
  111. package/src/components/Input/index.ts +2 -0
  112. package/src/components/Link/Link.stories.tsx +187 -0
  113. package/src/components/Link/Link.tsx +104 -0
  114. package/src/components/Link/index.ts +2 -0
  115. package/src/components/Menu/Menu.stories.tsx +363 -0
  116. package/src/components/Menu/Menu.tsx +238 -0
  117. package/src/components/Menu/index.ts +2 -0
  118. package/src/components/Modal/Modal.stories.tsx +156 -0
  119. package/src/components/Modal/Modal.tsx +280 -0
  120. package/src/components/Modal/index.ts +11 -0
  121. package/src/components/Popover/Popover.stories.tsx +330 -0
  122. package/src/components/Popover/Popover.tsx +315 -0
  123. package/src/components/Popover/index.ts +11 -0
  124. package/src/components/Portal/Portal.stories.tsx +376 -0
  125. package/src/components/Portal/Portal.tsx +100 -0
  126. package/src/components/Portal/index.ts +2 -0
  127. package/src/components/Pressable/Pressable.stories.tsx +338 -0
  128. package/src/components/Pressable/Pressable.tsx +71 -0
  129. package/src/components/Pressable/index.ts +2 -0
  130. package/src/components/Progress/Progress.stories.tsx +131 -0
  131. package/src/components/Progress/Progress.tsx +219 -0
  132. package/src/components/Progress/index.ts +2 -0
  133. package/src/components/Radio/Radio.stories.tsx +101 -0
  134. package/src/components/Radio/Radio.tsx +234 -0
  135. package/src/components/Radio/index.ts +2 -0
  136. package/src/components/Select/Select.stories.tsx +908 -0
  137. package/src/components/Select/Select.tsx +659 -0
  138. package/src/components/Select/index.ts +8 -0
  139. package/src/components/Skeleton/Skeleton.stories.tsx +154 -0
  140. package/src/components/Skeleton/Skeleton.tsx +192 -0
  141. package/src/components/Skeleton/index.ts +8 -0
  142. package/src/components/Slider/Slider.stories.tsx +363 -0
  143. package/src/components/Slider/Slider.tsx +209 -0
  144. package/src/components/Slider/index.ts +2 -0
  145. package/src/components/Spinner/Spinner.stories.tsx +108 -0
  146. package/src/components/Spinner/Spinner.tsx +121 -0
  147. package/src/components/Spinner/index.ts +2 -0
  148. package/src/components/Switch/Switch.stories.tsx +116 -0
  149. package/src/components/Switch/Switch.tsx +172 -0
  150. package/src/components/Switch/index.ts +2 -0
  151. package/src/components/Table/Table.stories.tsx +417 -0
  152. package/src/components/Table/Table.tsx +233 -0
  153. package/src/components/Table/index.ts +2 -0
  154. package/src/components/Text/Text.stories.tsx +93 -0
  155. package/src/components/Text/Text.tsx +119 -0
  156. package/src/components/Text/index.ts +2 -0
  157. package/src/components/Textarea/Textarea.stories.tsx +280 -0
  158. package/src/components/Textarea/Textarea.tsx +212 -0
  159. package/src/components/Textarea/index.ts +2 -0
  160. package/src/components/Toast/Toast.stories.tsx +446 -0
  161. package/src/components/Toast/Toast.tsx +221 -0
  162. package/src/components/Toast/index.ts +2 -0
  163. package/src/components/Tooltip/Tooltip.stories.tsx +354 -0
  164. package/src/components/Tooltip/Tooltip.tsx +261 -0
  165. package/src/components/Tooltip/index.ts +2 -0
  166. package/src/components/VStack/VStack.stories.tsx +183 -0
  167. package/src/components/VStack/VStack.tsx +76 -0
  168. package/src/components/VStack/index.ts +2 -0
  169. package/src/components/index.ts +62 -0
  170. package/src/hooks/index.ts +7 -0
  171. package/src/hooks/useControllableState.ts +41 -0
  172. package/src/hooks/useDisclosure.ts +51 -0
  173. package/src/index.ts +22 -0
  174. package/src/stories/Button.stories.tsx +53 -0
  175. package/src/stories/Button.tsx +101 -0
  176. package/src/stories/Configure.mdx +364 -0
  177. package/src/stories/Header.stories.tsx +33 -0
  178. package/src/stories/Header.tsx +75 -0
  179. package/src/stories/Page.stories.tsx +25 -0
  180. package/src/stories/Page.tsx +154 -0
  181. package/src/stories/assets/accessibility.png +0 -0
  182. package/src/stories/assets/accessibility.svg +1 -0
  183. package/src/stories/assets/addon-library.png +0 -0
  184. package/src/stories/assets/assets.png +0 -0
  185. package/src/stories/assets/avif-test-image.avif +0 -0
  186. package/src/stories/assets/context.png +0 -0
  187. package/src/stories/assets/discord.svg +1 -0
  188. package/src/stories/assets/docs.png +0 -0
  189. package/src/stories/assets/figma-plugin.png +0 -0
  190. package/src/stories/assets/github.svg +1 -0
  191. package/src/stories/assets/share.png +0 -0
  192. package/src/stories/assets/styling.png +0 -0
  193. package/src/stories/assets/testing.png +0 -0
  194. package/src/stories/assets/theming.png +0 -0
  195. package/src/stories/assets/tutorials.svg +1 -0
  196. package/src/stories/assets/youtube.svg +1 -0
  197. package/src/styles/index.ts +7 -0
  198. package/src/styles/tokens.ts +318 -0
  199. package/src/styles/unistyles.ts +254 -0
  200. package/src/utils/createContext.tsx +25 -0
  201. package/src/utils/index.ts +7 -0
  202. package/src/utils/mergeRefs.ts +21 -0
  203. package/tsconfig.json +26 -0
  204. package/urbint-cl-1.0.0.tgz +0 -0
  205. package/vitest.config.ts +37 -0
  206. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,446 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { View, StyleSheet } from 'react-native';
4
+ import { ToastProvider, useToast } from './Toast';
5
+ import { Button } from '../Button';
6
+ import { VStack } from '../VStack';
7
+ import { HStack } from '../HStack';
8
+ import { Text } from '../Text';
9
+ import { Box } from '../Box';
10
+ import { colors, spacing, borderRadius } from '../../styles/tokens';
11
+
12
+ /**
13
+ * Story container with design system tokens
14
+ */
15
+ const StoryContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => (
16
+ <View style={styles.container}>{children}</View>
17
+ );
18
+
19
+ const meta: Meta = {
20
+ title: 'Feedback/Toast',
21
+ decorators: [
22
+ (Story) => (
23
+ <ToastProvider>
24
+ <StoryContainer>
25
+ <Story />
26
+ </StoryContainer>
27
+ </ToastProvider>
28
+ ),
29
+ ],
30
+ };
31
+
32
+ export default meta;
33
+
34
+ type Story = StoryObj;
35
+
36
+ const ToastDemo = () => {
37
+ const toast = useToast();
38
+
39
+ return (
40
+ <VStack space={spacing.lg}>
41
+ <Text weight="semiBold">Toast Notifications</Text>
42
+ <VStack space={spacing.md}>
43
+ <Button
44
+ onPress={() =>
45
+ toast.show({
46
+ title: 'Info',
47
+ description: 'This is an informational message',
48
+ status: 'info',
49
+ })
50
+ }
51
+ >
52
+ Show Info Toast
53
+ </Button>
54
+ <Button
55
+ variant="secondary"
56
+ onPress={() =>
57
+ toast.show({
58
+ title: 'Success!',
59
+ description: 'Your action was completed successfully',
60
+ status: 'success',
61
+ })
62
+ }
63
+ >
64
+ Show Success Toast
65
+ </Button>
66
+ <Button
67
+ variant="outline"
68
+ onPress={() =>
69
+ toast.show({
70
+ title: 'Warning',
71
+ description: 'Please review before proceeding',
72
+ status: 'warning',
73
+ })
74
+ }
75
+ >
76
+ Show Warning Toast
77
+ </Button>
78
+ <Button
79
+ variant="danger"
80
+ onPress={() =>
81
+ toast.show({
82
+ title: 'Error',
83
+ description: 'Something went wrong. Please try again.',
84
+ status: 'error',
85
+ })
86
+ }
87
+ >
88
+ Show Error Toast
89
+ </Button>
90
+ </VStack>
91
+ </VStack>
92
+ );
93
+ };
94
+
95
+ export const Default: Story = {
96
+ render: () => <ToastDemo />,
97
+ };
98
+
99
+ const StatusToasts = () => {
100
+ const toast = useToast();
101
+
102
+ return (
103
+ <VStack space={spacing.lg}>
104
+ <Text weight="semiBold">Toast Status Types</Text>
105
+ <HStack space={spacing.sm} wrap>
106
+ <Button
107
+ size="sm"
108
+ onPress={() =>
109
+ toast.show({
110
+ title: 'Information',
111
+ description: 'Here is some helpful information',
112
+ status: 'info',
113
+ })
114
+ }
115
+ >
116
+ Info
117
+ </Button>
118
+ <Button
119
+ size="sm"
120
+ onPress={() =>
121
+ toast.show({
122
+ title: 'Success',
123
+ description: 'Operation completed successfully',
124
+ status: 'success',
125
+ })
126
+ }
127
+ >
128
+ Success
129
+ </Button>
130
+ <Button
131
+ size="sm"
132
+ onPress={() =>
133
+ toast.show({
134
+ title: 'Warning',
135
+ description: 'This action may have consequences',
136
+ status: 'warning',
137
+ })
138
+ }
139
+ >
140
+ Warning
141
+ </Button>
142
+ <Button
143
+ size="sm"
144
+ onPress={() =>
145
+ toast.show({
146
+ title: 'Error',
147
+ description: 'An error occurred during the operation',
148
+ status: 'error',
149
+ })
150
+ }
151
+ >
152
+ Error
153
+ </Button>
154
+ </HStack>
155
+ </VStack>
156
+ );
157
+ };
158
+
159
+ export const Statuses: Story = {
160
+ render: () => <StatusToasts />,
161
+ };
162
+
163
+ const TitleOnlyToasts = () => {
164
+ const toast = useToast();
165
+
166
+ return (
167
+ <VStack space={spacing.lg}>
168
+ <Text weight="semiBold">Title Only Toasts</Text>
169
+ <VStack space={spacing.sm}>
170
+ <Button
171
+ size="sm"
172
+ variant="secondary"
173
+ onPress={() =>
174
+ toast.show({
175
+ title: 'File uploaded successfully',
176
+ status: 'success',
177
+ })
178
+ }
179
+ >
180
+ Short Success
181
+ </Button>
182
+ <Button
183
+ size="sm"
184
+ variant="secondary"
185
+ onPress={() =>
186
+ toast.show({
187
+ title: 'Connection lost',
188
+ status: 'error',
189
+ })
190
+ }
191
+ >
192
+ Short Error
193
+ </Button>
194
+ <Button
195
+ size="sm"
196
+ variant="secondary"
197
+ onPress={() =>
198
+ toast.show({
199
+ title: 'New update available',
200
+ status: 'info',
201
+ })
202
+ }
203
+ >
204
+ Short Info
205
+ </Button>
206
+ </VStack>
207
+ </VStack>
208
+ );
209
+ };
210
+
211
+ export const TitleOnly: Story = {
212
+ render: () => <TitleOnlyToasts />,
213
+ };
214
+
215
+ const CustomDurationToasts = () => {
216
+ const toast = useToast();
217
+
218
+ return (
219
+ <VStack space={spacing.lg}>
220
+ <Text weight="semiBold">Custom Durations</Text>
221
+ <VStack space={spacing.sm}>
222
+ <Button
223
+ size="sm"
224
+ variant="secondary"
225
+ onPress={() =>
226
+ toast.show({
227
+ title: 'Quick toast',
228
+ description: 'Disappears in 2 seconds',
229
+ status: 'info',
230
+ duration: 2000,
231
+ })
232
+ }
233
+ >
234
+ 2 Second Toast
235
+ </Button>
236
+ <Button
237
+ size="sm"
238
+ variant="secondary"
239
+ onPress={() =>
240
+ toast.show({
241
+ title: 'Standard toast',
242
+ description: 'Disappears in 5 seconds (default)',
243
+ status: 'info',
244
+ duration: 5000,
245
+ })
246
+ }
247
+ >
248
+ 5 Second Toast (Default)
249
+ </Button>
250
+ <Button
251
+ size="sm"
252
+ variant="secondary"
253
+ onPress={() =>
254
+ toast.show({
255
+ title: 'Long toast',
256
+ description: 'Disappears in 10 seconds',
257
+ status: 'info',
258
+ duration: 10000,
259
+ })
260
+ }
261
+ >
262
+ 10 Second Toast
263
+ </Button>
264
+ <Button
265
+ size="sm"
266
+ variant="secondary"
267
+ onPress={() =>
268
+ toast.show({
269
+ title: 'Persistent toast',
270
+ description: 'Will not auto-dismiss',
271
+ status: 'warning',
272
+ duration: 0,
273
+ })
274
+ }
275
+ >
276
+ Persistent Toast
277
+ </Button>
278
+ </VStack>
279
+ </VStack>
280
+ );
281
+ };
282
+
283
+ export const CustomDuration: Story = {
284
+ render: () => <CustomDurationToasts />,
285
+ };
286
+
287
+ const ClosableToasts = () => {
288
+ const toast = useToast();
289
+
290
+ return (
291
+ <VStack space={spacing.lg}>
292
+ <Text weight="semiBold">Closable vs Non-Closable</Text>
293
+ <VStack space={spacing.sm}>
294
+ <Button
295
+ size="sm"
296
+ variant="secondary"
297
+ onPress={() =>
298
+ toast.show({
299
+ title: 'Closable toast',
300
+ description: 'Click the X to dismiss',
301
+ status: 'info',
302
+ isClosable: true,
303
+ })
304
+ }
305
+ >
306
+ Closable (Default)
307
+ </Button>
308
+ <Button
309
+ size="sm"
310
+ variant="secondary"
311
+ onPress={() =>
312
+ toast.show({
313
+ title: 'Non-closable toast',
314
+ description: 'Wait for it to auto-dismiss',
315
+ status: 'info',
316
+ isClosable: false,
317
+ duration: 3000,
318
+ })
319
+ }
320
+ >
321
+ Non-Closable
322
+ </Button>
323
+ </VStack>
324
+ </VStack>
325
+ );
326
+ };
327
+
328
+ export const Closable: Story = {
329
+ render: () => <ClosableToasts />,
330
+ };
331
+
332
+ const MultipleToasts = () => {
333
+ const toast = useToast();
334
+
335
+ const showMultiple = () => {
336
+ toast.show({
337
+ title: 'First notification',
338
+ status: 'info',
339
+ });
340
+ setTimeout(() => {
341
+ toast.show({
342
+ title: 'Second notification',
343
+ status: 'success',
344
+ });
345
+ }, 300);
346
+ setTimeout(() => {
347
+ toast.show({
348
+ title: 'Third notification',
349
+ status: 'warning',
350
+ });
351
+ }, 600);
352
+ };
353
+
354
+ return (
355
+ <VStack space={spacing.lg}>
356
+ <Text weight="semiBold">Multiple Toasts</Text>
357
+ <VStack space={spacing.sm}>
358
+ <Button onPress={showMultiple}>Show Multiple Toasts</Button>
359
+ <Button variant="secondary" onPress={() => toast.closeAll()}>
360
+ Close All Toasts
361
+ </Button>
362
+ </VStack>
363
+ </VStack>
364
+ );
365
+ };
366
+
367
+ export const Multiple: Story = {
368
+ render: () => <MultipleToasts />,
369
+ };
370
+
371
+ const RealWorldExamples = () => {
372
+ const toast = useToast();
373
+
374
+ return (
375
+ <VStack space={spacing.lg}>
376
+ <Text weight="semiBold">Real World Examples</Text>
377
+ <Box p={spacing.lg} bg={colors.background.secondary} rounded="lg">
378
+ <VStack space={spacing.md}>
379
+ <Button
380
+ onPress={() =>
381
+ toast.show({
382
+ title: 'Changes saved',
383
+ description: 'Your profile has been updated',
384
+ status: 'success',
385
+ })
386
+ }
387
+ >
388
+ Save Profile
389
+ </Button>
390
+ <Button
391
+ variant="danger"
392
+ onPress={() =>
393
+ toast.show({
394
+ title: 'Item deleted',
395
+ description: 'The item has been permanently removed',
396
+ status: 'success',
397
+ })
398
+ }
399
+ >
400
+ Delete Item
401
+ </Button>
402
+ <Button
403
+ variant="secondary"
404
+ onPress={() =>
405
+ toast.show({
406
+ title: 'Copied to clipboard',
407
+ status: 'info',
408
+ duration: 2000,
409
+ })
410
+ }
411
+ >
412
+ Copy Link
413
+ </Button>
414
+ <Button
415
+ variant="outline"
416
+ onPress={() =>
417
+ toast.show({
418
+ title: 'Network error',
419
+ description: 'Please check your connection and try again',
420
+ status: 'error',
421
+ })
422
+ }
423
+ >
424
+ Simulate Error
425
+ </Button>
426
+ </VStack>
427
+ </Box>
428
+ </VStack>
429
+ );
430
+ };
431
+
432
+ export const RealWorld: Story = {
433
+ render: () => <RealWorldExamples />,
434
+ };
435
+
436
+ /**
437
+ * Styles using design system tokens
438
+ */
439
+ const styles = StyleSheet.create({
440
+ container: {
441
+ padding: spacing.lg,
442
+ backgroundColor: colors.background.default,
443
+ borderRadius: borderRadius.lg,
444
+ },
445
+ });
446
+
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Toast Component and Provider
3
+ * Non-blocking notifications
4
+ */
5
+
6
+ import React, { createContext, useContext, useState, useCallback, useRef } from 'react';
7
+ import { View, Text, Pressable, Animated, StyleSheet } from 'react-native';
8
+ import Svg, { Path } from 'react-native-svg';
9
+ import { colors, spacing, borderRadius, typography, elevation, zIndex } from '../../styles/tokens';
10
+
11
+ export interface ToastConfig {
12
+ id?: string;
13
+ title: string;
14
+ description?: string;
15
+ status?: 'info' | 'success' | 'warning' | 'error';
16
+ duration?: number;
17
+ isClosable?: boolean;
18
+ position?: 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left';
19
+ }
20
+
21
+ interface ToastContextValue {
22
+ show: (config: ToastConfig) => string;
23
+ close: (id: string) => void;
24
+ closeAll: () => void;
25
+ }
26
+
27
+ const ToastContext = createContext<ToastContextValue | null>(null);
28
+
29
+ export const useToast = () => {
30
+ const context = useContext(ToastContext);
31
+ if (!context) {
32
+ throw new Error('useToast must be used within a ToastProvider');
33
+ }
34
+ return context;
35
+ };
36
+
37
+ const statusIcons = {
38
+ info: 'M12 16v-4m0-4h.01M22 12a10 10 0 11-20 0 10 10 0 0120 0z',
39
+ success: 'M9 12l2 2 4-4m6 2a10 10 0 11-20 0 10 10 0 0120 0z',
40
+ warning: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
41
+ error: 'M12 8v4m0 4h.01M22 12a10 10 0 11-20 0 10 10 0 0120 0z',
42
+ };
43
+
44
+ const statusColors = {
45
+ info: colors.feedback.info.content,
46
+ success: colors.feedback.success.content,
47
+ warning: colors.feedback.warning.content,
48
+ error: colors.feedback.error.content,
49
+ };
50
+
51
+ interface ToastItemProps {
52
+ config: ToastConfig & { id: string };
53
+ onClose: (id: string) => void;
54
+ }
55
+
56
+ const ToastItem = ({ config, onClose }: ToastItemProps) => {
57
+ const translateY = useRef(new Animated.Value(-100)).current;
58
+ const opacity = useRef(new Animated.Value(0)).current;
59
+
60
+ React.useEffect(() => {
61
+ Animated.parallel([
62
+ Animated.spring(translateY, {
63
+ toValue: 0,
64
+ useNativeDriver: true,
65
+ tension: 50,
66
+ friction: 8,
67
+ }),
68
+ Animated.timing(opacity, {
69
+ toValue: 1,
70
+ duration: 200,
71
+ useNativeDriver: true,
72
+ }),
73
+ ]).start();
74
+
75
+ if (config.duration !== 0) {
76
+ const timer = setTimeout(() => {
77
+ handleClose();
78
+ }, config.duration || 5000);
79
+ return () => clearTimeout(timer);
80
+ }
81
+ }, []);
82
+
83
+ const handleClose = () => {
84
+ Animated.parallel([
85
+ Animated.timing(translateY, {
86
+ toValue: -100,
87
+ duration: 200,
88
+ useNativeDriver: true,
89
+ }),
90
+ Animated.timing(opacity, {
91
+ toValue: 0,
92
+ duration: 200,
93
+ useNativeDriver: true,
94
+ }),
95
+ ]).start(() => {
96
+ onClose(config.id);
97
+ });
98
+ };
99
+
100
+ const statusColor = statusColors[config.status || 'info'];
101
+
102
+ return (
103
+ <Animated.View
104
+ style={[
105
+ styles.toast,
106
+ { transform: [{ translateY }], opacity },
107
+ { borderLeftColor: statusColor },
108
+ ]}
109
+ >
110
+ <View style={styles.iconContainer}>
111
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
112
+ <Path
113
+ d={statusIcons[config.status || 'info']}
114
+ stroke={statusColor}
115
+ strokeWidth={2}
116
+ strokeLinecap="round"
117
+ strokeLinejoin="round"
118
+ />
119
+ </Svg>
120
+ </View>
121
+ <View style={styles.content}>
122
+ <Text style={styles.title}>{config.title}</Text>
123
+ {config.description && (
124
+ <Text style={styles.description}>{config.description}</Text>
125
+ )}
126
+ </View>
127
+ {config.isClosable !== false && (
128
+ <Pressable onPress={handleClose} style={styles.closeButton}>
129
+ <Svg width={16} height={16} viewBox="0 0 24 24" fill="none">
130
+ <Path
131
+ d="M18 6L6 18M6 6l12 12"
132
+ stroke={colors.text.secondary}
133
+ strokeWidth={2}
134
+ strokeLinecap="round"
135
+ strokeLinejoin="round"
136
+ />
137
+ </Svg>
138
+ </Pressable>
139
+ )}
140
+ </Animated.View>
141
+ );
142
+ };
143
+
144
+ export interface ToastProviderProps {
145
+ children: React.ReactNode;
146
+ }
147
+
148
+ export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
149
+ const [toasts, setToasts] = useState<(ToastConfig & { id: string })[]>([]);
150
+ const idCounter = useRef(0);
151
+
152
+ const show = useCallback((config: ToastConfig) => {
153
+ const id = config.id || `toast-${idCounter.current++}`;
154
+ setToasts((prev) => [...prev, { ...config, id }]);
155
+ return id;
156
+ }, []);
157
+
158
+ const close = useCallback((id: string) => {
159
+ setToasts((prev) => prev.filter((t) => t.id !== id));
160
+ }, []);
161
+
162
+ const closeAll = useCallback(() => {
163
+ setToasts([]);
164
+ }, []);
165
+
166
+ return (
167
+ <ToastContext.Provider value={{ show, close, closeAll }}>
168
+ {children}
169
+ <View style={styles.container} pointerEvents="box-none">
170
+ {toasts.map((toast) => (
171
+ <ToastItem key={toast.id} config={toast} onClose={close} />
172
+ ))}
173
+ </View>
174
+ </ToastContext.Provider>
175
+ );
176
+ };
177
+
178
+ const styles = StyleSheet.create({
179
+ container: {
180
+ position: 'absolute',
181
+ top: 60,
182
+ left: 0,
183
+ right: 0,
184
+ alignItems: 'center',
185
+ zIndex: zIndex.toast,
186
+ paddingHorizontal: spacing['4x'],
187
+ },
188
+ toast: {
189
+ flexDirection: 'row',
190
+ alignItems: 'flex-start',
191
+ backgroundColor: colors.background.default,
192
+ borderRadius: borderRadius.md,
193
+ borderLeftWidth: 4,
194
+ padding: spacing['3x'],
195
+ marginBottom: spacing['2x'],
196
+ maxWidth: 400,
197
+ width: '100%',
198
+ ...elevation['30'],
199
+ },
200
+ iconContainer: {
201
+ marginRight: spacing['3x'],
202
+ marginTop: 2,
203
+ },
204
+ content: {
205
+ flex: 1,
206
+ },
207
+ title: {
208
+ fontSize: typography.fontSize.body,
209
+ fontWeight: typography.fontWeight.semiBold,
210
+ color: colors.text.default,
211
+ },
212
+ description: {
213
+ fontSize: typography.fontSize.caption,
214
+ color: colors.text.secondary,
215
+ marginTop: spacing.base,
216
+ },
217
+ closeButton: {
218
+ marginLeft: spacing['2x'],
219
+ padding: spacing.base,
220
+ },
221
+ });
@@ -0,0 +1,2 @@
1
+ export { ToastProvider, useToast, type ToastConfig, type ToastProviderProps } from './Toast';
2
+