@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,258 @@
1
+ /**
2
+ * ActionSheet Component
3
+ * Bottom sheet with action options
4
+ */
5
+
6
+ import React, { forwardRef, useRef, useEffect } from 'react';
7
+ import { View, ViewProps, Modal, Pressable, Text, Animated, Dimensions, ScrollView, StyleSheet } from 'react-native';
8
+ import { colors, spacing, borderRadius, typography, elevation } from '../../styles/tokens';
9
+
10
+ const { height: SCREEN_HEIGHT } = Dimensions.get('window');
11
+
12
+ export interface ActionSheetItem {
13
+ label: string;
14
+ value: string;
15
+ icon?: React.ReactNode;
16
+ isDestructive?: boolean;
17
+ isDisabled?: boolean;
18
+ }
19
+
20
+ export interface ActionSheetProps extends ViewProps {
21
+ /** Is open */
22
+ isOpen: boolean;
23
+ /** On close handler */
24
+ onClose: () => void;
25
+ /** Title */
26
+ title?: string;
27
+ /** Description */
28
+ description?: string;
29
+ /** Action items */
30
+ items: ActionSheetItem[];
31
+ /** On item press handler */
32
+ onItemPress?: (value: string) => void;
33
+ /** Cancel text */
34
+ cancelText?: string;
35
+ /** Show cancel button */
36
+ showCancel?: boolean;
37
+ }
38
+
39
+ export const ActionSheet = forwardRef<View, ActionSheetProps>(
40
+ (
41
+ {
42
+ style,
43
+ isOpen,
44
+ onClose,
45
+ title,
46
+ description,
47
+ items,
48
+ onItemPress,
49
+ cancelText = 'Cancel',
50
+ showCancel = true,
51
+ ...props
52
+ },
53
+ ref
54
+ ) => {
55
+ const translateY = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
56
+ const opacity = useRef(new Animated.Value(0)).current;
57
+
58
+ useEffect(() => {
59
+ if (isOpen) {
60
+ Animated.parallel([
61
+ Animated.spring(translateY, {
62
+ toValue: 0,
63
+ useNativeDriver: true,
64
+ tension: 65,
65
+ friction: 11,
66
+ }),
67
+ Animated.timing(opacity, {
68
+ toValue: 1,
69
+ duration: 200,
70
+ useNativeDriver: true,
71
+ }),
72
+ ]).start();
73
+ } else {
74
+ Animated.parallel([
75
+ Animated.timing(translateY, {
76
+ toValue: SCREEN_HEIGHT,
77
+ duration: 200,
78
+ useNativeDriver: true,
79
+ }),
80
+ Animated.timing(opacity, {
81
+ toValue: 0,
82
+ duration: 200,
83
+ useNativeDriver: true,
84
+ }),
85
+ ]).start();
86
+ }
87
+ }, [isOpen]);
88
+
89
+ const handleItemPress = (item: ActionSheetItem) => {
90
+ if (item.isDisabled) return;
91
+ onItemPress?.(item.value);
92
+ onClose();
93
+ };
94
+
95
+ return (
96
+ <Modal
97
+ visible={isOpen}
98
+ transparent
99
+ animationType="none"
100
+ onRequestClose={onClose}
101
+ >
102
+ <View style={styles.container}>
103
+ <Animated.View style={[styles.overlay, { opacity }]}>
104
+ <Pressable style={styles.overlayPressable} onPress={onClose} />
105
+ </Animated.View>
106
+ <Animated.View
107
+ ref={ref}
108
+ style={[
109
+ styles.sheet,
110
+ { transform: [{ translateY }] },
111
+ style,
112
+ ]}
113
+ {...props}
114
+ >
115
+ <View style={styles.handle} />
116
+ {(title || description) && (
117
+ <View style={styles.header}>
118
+ {title && <Text style={styles.title}>{title}</Text>}
119
+ {description && <Text style={styles.description}>{description}</Text>}
120
+ </View>
121
+ )}
122
+ <ScrollView style={styles.itemsContainer}>
123
+ {items.map((item, index) => (
124
+ <Pressable
125
+ key={item.value}
126
+ onPress={() => handleItemPress(item)}
127
+ disabled={item.isDisabled}
128
+ style={[
129
+ styles.item,
130
+ index === items.length - 1 && styles.itemLast,
131
+ item.isDisabled && styles.itemDisabled,
132
+ ]}
133
+ >
134
+ {item.icon && <View style={styles.itemIcon}>{item.icon}</View>}
135
+ <Text
136
+ style={[
137
+ styles.itemLabel,
138
+ item.isDestructive && styles.itemDestructive,
139
+ item.isDisabled && styles.itemLabelDisabled,
140
+ ]}
141
+ >
142
+ {item.label}
143
+ </Text>
144
+ </Pressable>
145
+ ))}
146
+ </ScrollView>
147
+ {showCancel && (
148
+ <View style={styles.cancelContainer}>
149
+ <Pressable onPress={onClose} style={styles.cancelButton}>
150
+ <Text style={styles.cancelText}>{cancelText}</Text>
151
+ </Pressable>
152
+ </View>
153
+ )}
154
+ </Animated.View>
155
+ </View>
156
+ </Modal>
157
+ );
158
+ }
159
+ );
160
+
161
+ ActionSheet.displayName = 'ActionSheet';
162
+
163
+ const styles = StyleSheet.create({
164
+ container: {
165
+ flex: 1,
166
+ justifyContent: 'flex-end',
167
+ },
168
+ overlay: {
169
+ ...StyleSheet.absoluteFillObject,
170
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
171
+ },
172
+ overlayPressable: {
173
+ flex: 1,
174
+ },
175
+ sheet: {
176
+ backgroundColor: colors.background.default,
177
+ borderTopLeftRadius: borderRadius.xl,
178
+ borderTopRightRadius: borderRadius.xl,
179
+ maxHeight: SCREEN_HEIGHT * 0.7,
180
+ ...elevation['40'],
181
+ },
182
+ handle: {
183
+ width: 36,
184
+ height: 4,
185
+ backgroundColor: colors.border.default,
186
+ borderRadius: 2,
187
+ alignSelf: 'center',
188
+ marginTop: spacing['2x'],
189
+ marginBottom: spacing['3x'],
190
+ },
191
+ header: {
192
+ paddingHorizontal: spacing['4x'],
193
+ paddingBottom: spacing['3x'],
194
+ borderBottomWidth: 1,
195
+ borderBottomColor: colors.border.disabled,
196
+ },
197
+ title: {
198
+ fontSize: typography.fontSize.h4,
199
+ fontWeight: typography.fontWeight.semiBold,
200
+ color: colors.text.default,
201
+ textAlign: 'center',
202
+ },
203
+ description: {
204
+ fontSize: typography.fontSize.caption,
205
+ color: colors.text.secondary,
206
+ textAlign: 'center',
207
+ marginTop: spacing.base,
208
+ },
209
+ itemsContainer: {
210
+ maxHeight: SCREEN_HEIGHT * 0.4,
211
+ },
212
+ item: {
213
+ flexDirection: 'row',
214
+ alignItems: 'center',
215
+ justifyContent: 'center',
216
+ paddingVertical: spacing['4x'],
217
+ paddingHorizontal: spacing['4x'],
218
+ borderBottomWidth: 1,
219
+ borderBottomColor: colors.border.disabled,
220
+ },
221
+ itemLast: {
222
+ borderBottomWidth: 0,
223
+ },
224
+ itemDisabled: {
225
+ opacity: 0.5,
226
+ },
227
+ itemIcon: {
228
+ marginRight: spacing['3x'],
229
+ },
230
+ itemLabel: {
231
+ fontSize: typography.fontSize.body,
232
+ color: colors.brand.blue,
233
+ fontWeight: typography.fontWeight.medium,
234
+ },
235
+ itemDestructive: {
236
+ color: colors.feedback.error.content,
237
+ },
238
+ itemLabelDisabled: {
239
+ color: colors.text.disabled,
240
+ },
241
+ cancelContainer: {
242
+ paddingHorizontal: spacing['3x'],
243
+ paddingVertical: spacing['3x'],
244
+ borderTopWidth: 8,
245
+ borderTopColor: colors.background.secondary,
246
+ },
247
+ cancelButton: {
248
+ alignItems: 'center',
249
+ paddingVertical: spacing['3x'],
250
+ backgroundColor: colors.background.secondary,
251
+ borderRadius: borderRadius.lg,
252
+ },
253
+ cancelText: {
254
+ fontSize: typography.fontSize.body,
255
+ fontWeight: typography.fontWeight.semiBold,
256
+ color: colors.text.default,
257
+ },
258
+ });
@@ -0,0 +1,2 @@
1
+ export { ActionSheet, type ActionSheetProps, type ActionSheetItem } from './ActionSheet';
2
+
@@ -0,0 +1,165 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { View, StyleSheet } from 'react-native';
4
+ import { Alert } from './Alert';
5
+ import { VStack } from '../VStack';
6
+ import { Text } from '../Text';
7
+ import { colors, spacing, borderRadius, typography } from '../../styles/tokens';
8
+
9
+ /**
10
+ * Themed container wrapper for stories
11
+ * Uses design tokens from the theme
12
+ */
13
+ const StoryContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => {
14
+ return <View style={styles.container}>{children}</View>;
15
+ };
16
+
17
+ const meta: Meta<typeof Alert> = {
18
+ title: 'Components/Alert',
19
+ component: Alert,
20
+ decorators: [
21
+ (Story) => (
22
+ <StoryContainer>
23
+ <Story />
24
+ </StoryContainer>
25
+ ),
26
+ ],
27
+ argTypes: {
28
+ status: {
29
+ control: 'select',
30
+ options: ['info', 'success', 'warning', 'error'],
31
+ },
32
+ variant: {
33
+ control: 'select',
34
+ options: ['subtle', 'solid', 'left-accent', 'top-accent'],
35
+ },
36
+ isClosable: {
37
+ control: 'boolean',
38
+ },
39
+ },
40
+ args: {
41
+ title: 'Alert Title',
42
+ description: 'This is an alert message with a description.',
43
+ status: 'info',
44
+ variant: 'subtle',
45
+ isClosable: false,
46
+ },
47
+ };
48
+
49
+ export default meta;
50
+
51
+ type Story = StoryObj<typeof Alert>;
52
+
53
+ export const Default: Story = {};
54
+
55
+ export const Statuses: Story = {
56
+ render: () => (
57
+ <VStack space={spacing.md}>
58
+ <Text weight="semiBold" style={styles.sectionHeader}>Alert Statuses</Text>
59
+ <Alert
60
+ status="info"
61
+ title="Information"
62
+ description="This is an informational alert."
63
+ />
64
+ <Alert
65
+ status="success"
66
+ title="Success"
67
+ description="Your action was completed successfully."
68
+ />
69
+ <Alert
70
+ status="warning"
71
+ title="Warning"
72
+ description="Please review before proceeding."
73
+ />
74
+ <Alert
75
+ status="error"
76
+ title="Error"
77
+ description="Something went wrong. Please try again."
78
+ />
79
+ </VStack>
80
+ ),
81
+ };
82
+
83
+ export const Variants: Story = {
84
+ render: () => (
85
+ <VStack space={spacing.md}>
86
+ <Text weight="semiBold" style={styles.sectionHeader}>Alert Variants</Text>
87
+ <Alert
88
+ status="info"
89
+ variant="subtle"
90
+ title="Subtle"
91
+ description="Subtle variant with light background"
92
+ />
93
+ <Alert
94
+ status="info"
95
+ variant="solid"
96
+ title="Solid"
97
+ description="Solid variant with filled background"
98
+ />
99
+ <Alert
100
+ status="info"
101
+ variant="left-accent"
102
+ title="Left Accent"
103
+ description="Left accent variant with border"
104
+ />
105
+ <Alert
106
+ status="info"
107
+ variant="top-accent"
108
+ title="Top Accent"
109
+ description="Top accent variant with border"
110
+ />
111
+ </VStack>
112
+ ),
113
+ };
114
+
115
+ export const Closable: Story = {
116
+ render: () => (
117
+ <VStack space={spacing.md}>
118
+ <Text weight="semiBold" style={styles.sectionHeader}>Closable Alerts</Text>
119
+ <Alert
120
+ status="success"
121
+ title="Changes saved"
122
+ description="Your changes have been saved successfully."
123
+ isClosable
124
+ onClose={() => console.log('Alert closed')}
125
+ />
126
+ <Alert
127
+ status="error"
128
+ title="Upload failed"
129
+ description="The file could not be uploaded. Please try again."
130
+ isClosable
131
+ onClose={() => console.log('Alert closed')}
132
+ />
133
+ </VStack>
134
+ ),
135
+ };
136
+
137
+ export const TitleOnly: Story = {
138
+ render: () => (
139
+ <VStack space={spacing.md}>
140
+ <Text weight="semiBold" style={styles.sectionHeader}>Title Only Alerts</Text>
141
+ <Alert status="info" title="New features are available!" />
142
+ <Alert status="success" title="Payment successful!" />
143
+ <Alert status="warning" title="Your session will expire soon" />
144
+ <Alert status="error" title="Connection lost" />
145
+ </VStack>
146
+ ),
147
+ };
148
+
149
+ /**
150
+ * Styles using design system tokens
151
+ * All values reference centralized tokens for consistency
152
+ */
153
+ const styles = StyleSheet.create({
154
+ container: {
155
+ padding: spacing.lg,
156
+ backgroundColor: colors.background.default,
157
+ borderRadius: borderRadius.lg,
158
+ },
159
+ sectionHeader: {
160
+ color: colors.text.default,
161
+ fontSize: typography.fontSize.body,
162
+ marginBottom: spacing.xs,
163
+ },
164
+ });
165
+
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Alert Component
3
+ * Feedback messages for user actions
4
+ */
5
+
6
+ import React, { forwardRef } from 'react';
7
+ import { View, ViewProps, Text, Pressable, StyleSheet } from 'react-native';
8
+ import Svg, { Path } from 'react-native-svg';
9
+ import { colors, spacing, borderRadius, typography } from '../../styles/tokens';
10
+
11
+ export interface AlertProps extends ViewProps {
12
+ /** Alert status */
13
+ status?: 'info' | 'success' | 'warning' | 'error';
14
+ /** Alert variant */
15
+ variant?: 'subtle' | 'solid' | 'left-accent' | 'top-accent';
16
+ /** Alert title */
17
+ title?: string;
18
+ /** Alert description */
19
+ description?: string;
20
+ /** Is closable */
21
+ isClosable?: boolean;
22
+ /** On close handler */
23
+ onClose?: () => void;
24
+ /** Custom icon */
25
+ icon?: React.ReactNode;
26
+ }
27
+
28
+ const statusIcons = {
29
+ info: 'M12 16v-4m0-4h.01M22 12a10 10 0 11-20 0 10 10 0 0120 0z',
30
+ success: 'M9 12l2 2 4-4m6 2a10 10 0 11-20 0 10 10 0 0120 0z',
31
+ 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',
32
+ error: 'M12 8v4m0 4h.01M22 12a10 10 0 11-20 0 10 10 0 0120 0z',
33
+ };
34
+
35
+ const statusColors = {
36
+ info: colors.feedback.info,
37
+ success: colors.feedback.success,
38
+ warning: colors.feedback.warning,
39
+ error: colors.feedback.error,
40
+ };
41
+
42
+ export const Alert = forwardRef<View, AlertProps>(
43
+ (
44
+ {
45
+ style,
46
+ status = 'info',
47
+ variant = 'subtle',
48
+ title,
49
+ description,
50
+ isClosable = false,
51
+ onClose,
52
+ icon,
53
+ children,
54
+ ...props
55
+ },
56
+ ref
57
+ ) => {
58
+ const statusColor = statusColors[status];
59
+ const isSolid = variant === 'solid';
60
+
61
+ return (
62
+ <View
63
+ ref={ref}
64
+ style={[
65
+ styles.alert,
66
+ styles[variant],
67
+ {
68
+ backgroundColor: isSolid ? statusColor.content : statusColor.background,
69
+ borderColor: statusColor.border,
70
+ },
71
+ style,
72
+ ]}
73
+ accessibilityRole="alert"
74
+ {...props}
75
+ >
76
+ <View style={styles.iconContainer}>
77
+ {icon || (
78
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
79
+ <Path
80
+ d={statusIcons[status]}
81
+ stroke={isSolid ? colors.white : statusColor.content}
82
+ strokeWidth={2}
83
+ strokeLinecap="round"
84
+ strokeLinejoin="round"
85
+ />
86
+ </Svg>
87
+ )}
88
+ </View>
89
+ <View style={styles.content}>
90
+ {title && (
91
+ <Text style={[styles.title, isSolid && styles.solidText]}>{title}</Text>
92
+ )}
93
+ {description && (
94
+ <Text style={[styles.description, isSolid && styles.solidText]}>{description}</Text>
95
+ )}
96
+ {children}
97
+ </View>
98
+ {isClosable && (
99
+ <Pressable onPress={onClose} style={styles.closeButton}>
100
+ <Svg width={16} height={16} viewBox="0 0 24 24" fill="none">
101
+ <Path
102
+ d="M18 6L6 18M6 6l12 12"
103
+ stroke={isSolid ? colors.white : colors.text.secondary}
104
+ strokeWidth={2}
105
+ strokeLinecap="round"
106
+ strokeLinejoin="round"
107
+ />
108
+ </Svg>
109
+ </Pressable>
110
+ )}
111
+ </View>
112
+ );
113
+ }
114
+ );
115
+
116
+ Alert.displayName = 'Alert';
117
+
118
+ const styles = StyleSheet.create({
119
+ alert: {
120
+ flexDirection: 'row',
121
+ alignItems: 'flex-start',
122
+ padding: spacing['3x'],
123
+ borderRadius: borderRadius.md,
124
+ },
125
+ subtle: {},
126
+ solid: {},
127
+ 'left-accent': {
128
+ borderLeftWidth: 4,
129
+ borderRadius: 0,
130
+ borderTopRightRadius: borderRadius.md,
131
+ borderBottomRightRadius: borderRadius.md,
132
+ },
133
+ 'top-accent': {
134
+ borderTopWidth: 4,
135
+ borderRadius: 0,
136
+ borderBottomLeftRadius: borderRadius.md,
137
+ borderBottomRightRadius: borderRadius.md,
138
+ },
139
+ iconContainer: {
140
+ marginRight: spacing['3x'],
141
+ marginTop: 2,
142
+ },
143
+ content: {
144
+ flex: 1,
145
+ },
146
+ title: {
147
+ fontSize: typography.fontSize.body,
148
+ fontWeight: typography.fontWeight.semiBold,
149
+ color: colors.text.default,
150
+ marginBottom: spacing.base,
151
+ },
152
+ description: {
153
+ fontSize: typography.fontSize.body,
154
+ color: colors.text.secondary,
155
+ lineHeight: typography.fontSize.body * typography.lineHeight.normal,
156
+ },
157
+ solidText: {
158
+ color: colors.white,
159
+ },
160
+ closeButton: {
161
+ marginLeft: spacing['2x'],
162
+ padding: spacing.base,
163
+ },
164
+ });
@@ -0,0 +1,2 @@
1
+ export { Alert, type AlertProps } from './Alert';
2
+