@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.
- package/.cursor/rules +313 -0
- package/.rnstorybook/index.ts +11 -0
- package/.rnstorybook/main.ts +8 -0
- package/.rnstorybook/preview.tsx +14 -0
- package/.rnstorybook/storybook.requires.ts +49 -0
- package/.storybook/main.ts +16 -0
- package/.storybook/preview.ts +32 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/App.tsx +422 -0
- package/README.md +229 -0
- package/app.json +33 -0
- package/assets/adaptive-icon.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/splash-icon.png +0 -0
- package/babel.config.js +16 -0
- package/docs/components/CodeBlock.tsx +80 -0
- package/docs/components/PropTable.tsx +93 -0
- package/docs/components/Sidebar.tsx +199 -0
- package/docs/components/index.ts +8 -0
- package/docs/data/colorTokens.ts +70 -0
- package/docs/data/componentData.tsx +1685 -0
- package/docs/data/index.ts +7 -0
- package/docs/index.ts +19 -0
- package/docs/navigation.ts +94 -0
- package/docs/pages/ColorsPage.tsx +226 -0
- package/docs/pages/ComponentPage.tsx +235 -0
- package/docs/pages/InstallationPage.tsx +232 -0
- package/docs/pages/IntroductionPage.tsx +163 -0
- package/docs/pages/ThemingPage.tsx +251 -0
- package/docs/pages/index.ts +10 -0
- package/docs/theme.ts +64 -0
- package/docs/types.ts +54 -0
- package/index.ts +8 -0
- package/llms.txt +1893 -0
- package/mcp-config.example.json +10 -0
- package/mcp-server/README.md +192 -0
- package/mcp-server/package-lock.json +1707 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/src/index.ts +1136 -0
- package/mcp-server/src/registry/components.ts +1446 -0
- package/mcp-server/src/registry/index.ts +3 -0
- package/mcp-server/src/registry/tokens.ts +256 -0
- package/mcp-server/tsconfig.json +19 -0
- package/package.json +92 -0
- package/src/components/Accordion/Accordion.stories.tsx +226 -0
- package/src/components/Accordion/Accordion.tsx +255 -0
- package/src/components/Accordion/index.ts +12 -0
- package/src/components/ActionSheet/ActionSheet.stories.tsx +393 -0
- package/src/components/ActionSheet/ActionSheet.tsx +258 -0
- package/src/components/ActionSheet/index.ts +2 -0
- package/src/components/Alert/Alert.stories.tsx +165 -0
- package/src/components/Alert/Alert.tsx +164 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/AlertDialog/AlertDialog.stories.tsx +330 -0
- package/src/components/AlertDialog/AlertDialog.tsx +234 -0
- package/src/components/AlertDialog/index.ts +2 -0
- package/src/components/Avatar/Avatar.stories.tsx +154 -0
- package/src/components/Avatar/Avatar.tsx +219 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Badge/Badge.stories.tsx +146 -0
- package/src/components/Badge/Badge.tsx +125 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Box/Box.stories.tsx +192 -0
- package/src/components/Box/Box.tsx +184 -0
- package/src/components/Box/index.ts +2 -0
- package/src/components/Button/Button.stories.tsx +157 -0
- package/src/components/Button/Button.tsx +180 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Card/Card.stories.tsx +145 -0
- package/src/components/Card/Card.tsx +169 -0
- package/src/components/Card/index.ts +11 -0
- package/src/components/Center/Center.stories.tsx +215 -0
- package/src/components/Center/Center.tsx +29 -0
- package/src/components/Center/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +94 -0
- package/src/components/Checkbox/Checkbox.tsx +242 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +623 -0
- package/src/components/DatePicker/DatePicker.tsx +1228 -0
- package/src/components/DatePicker/index.ts +8 -0
- package/src/components/Divider/Divider.stories.tsx +224 -0
- package/src/components/Divider/Divider.tsx +73 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Drawer/Drawer.stories.tsx +414 -0
- package/src/components/Drawer/Drawer.tsx +342 -0
- package/src/components/Drawer/index.ts +11 -0
- package/src/components/Fab/Fab.stories.tsx +360 -0
- package/src/components/Fab/Fab.tsx +185 -0
- package/src/components/Fab/index.ts +2 -0
- package/src/components/FormControl/FormControl.stories.tsx +276 -0
- package/src/components/FormControl/FormControl.tsx +185 -0
- package/src/components/FormControl/index.ts +12 -0
- package/src/components/Grid/Grid.stories.tsx +244 -0
- package/src/components/Grid/Grid.tsx +93 -0
- package/src/components/Grid/index.ts +2 -0
- package/src/components/HStack/HStack.stories.tsx +230 -0
- package/src/components/HStack/HStack.tsx +80 -0
- package/src/components/HStack/index.ts +2 -0
- package/src/components/Heading/Heading.stories.tsx +111 -0
- package/src/components/Heading/Heading.tsx +85 -0
- package/src/components/Heading/index.ts +2 -0
- package/src/components/Icon/Icon.stories.tsx +320 -0
- package/src/components/Icon/Icon.tsx +117 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Image/Image.stories.tsx +357 -0
- package/src/components/Image/Image.tsx +168 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/Input/Input.stories.tsx +164 -0
- package/src/components/Input/Input.tsx +274 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Link/Link.stories.tsx +187 -0
- package/src/components/Link/Link.tsx +104 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/Menu/Menu.stories.tsx +363 -0
- package/src/components/Menu/Menu.tsx +238 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/Modal/Modal.stories.tsx +156 -0
- package/src/components/Modal/Modal.tsx +280 -0
- package/src/components/Modal/index.ts +11 -0
- package/src/components/Popover/Popover.stories.tsx +330 -0
- package/src/components/Popover/Popover.tsx +315 -0
- package/src/components/Popover/index.ts +11 -0
- package/src/components/Portal/Portal.stories.tsx +376 -0
- package/src/components/Portal/Portal.tsx +100 -0
- package/src/components/Portal/index.ts +2 -0
- package/src/components/Pressable/Pressable.stories.tsx +338 -0
- package/src/components/Pressable/Pressable.tsx +71 -0
- package/src/components/Pressable/index.ts +2 -0
- package/src/components/Progress/Progress.stories.tsx +131 -0
- package/src/components/Progress/Progress.tsx +219 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/Radio/Radio.stories.tsx +101 -0
- package/src/components/Radio/Radio.tsx +234 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/Select/Select.stories.tsx +908 -0
- package/src/components/Select/Select.tsx +659 -0
- package/src/components/Select/index.ts +8 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +154 -0
- package/src/components/Skeleton/Skeleton.tsx +192 -0
- package/src/components/Skeleton/index.ts +8 -0
- package/src/components/Slider/Slider.stories.tsx +363 -0
- package/src/components/Slider/Slider.tsx +209 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/Spinner/Spinner.stories.tsx +108 -0
- package/src/components/Spinner/Spinner.tsx +121 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Switch/Switch.stories.tsx +116 -0
- package/src/components/Switch/Switch.tsx +172 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.stories.tsx +417 -0
- package/src/components/Table/Table.tsx +233 -0
- package/src/components/Table/index.ts +2 -0
- package/src/components/Text/Text.stories.tsx +93 -0
- package/src/components/Text/Text.tsx +119 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.stories.tsx +280 -0
- package/src/components/Textarea/Textarea.tsx +212 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Toast/Toast.stories.tsx +446 -0
- package/src/components/Toast/Toast.tsx +221 -0
- package/src/components/Toast/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +354 -0
- package/src/components/Tooltip/Tooltip.tsx +261 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/VStack/VStack.stories.tsx +183 -0
- package/src/components/VStack/VStack.tsx +76 -0
- package/src/components/VStack/index.ts +2 -0
- package/src/components/index.ts +62 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useControllableState.ts +41 -0
- package/src/hooks/useDisclosure.ts +51 -0
- package/src/index.ts +22 -0
- package/src/stories/Button.stories.tsx +53 -0
- package/src/stories/Button.tsx +101 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.stories.tsx +33 -0
- package/src/stories/Header.tsx +75 -0
- package/src/stories/Page.stories.tsx +25 -0
- package/src/stories/Page.tsx +154 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/styles/index.ts +7 -0
- package/src/styles/tokens.ts +318 -0
- package/src/styles/unistyles.ts +254 -0
- package/src/utils/createContext.tsx +25 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/mergeRefs.ts +21 -0
- package/tsconfig.json +26 -0
- package/urbint-cl-1.0.0.tgz +0 -0
- package/vitest.config.ts +37 -0
- 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
|
+
});
|