@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,357 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Image } from './Image';
3
+ import { VStack } from '../VStack';
4
+ import { HStack } from '../HStack';
5
+ import { Text } from '../Text';
6
+ import { Box } from '../Box';
7
+ import { Grid, GridItem } from '../Grid';
8
+ import { colors, spacing, borderRadius, elevation } from '../../styles/tokens';
9
+
10
+ const meta: Meta<typeof Image> = {
11
+ title: 'Data Display/Image',
12
+ component: Image,
13
+ argTypes: {
14
+ objectFit: {
15
+ control: 'select',
16
+ options: ['cover', 'contain', 'fill', 'none', 'scale-down'],
17
+ },
18
+ isCircular: { control: 'boolean' },
19
+ showLoading: { control: 'boolean' },
20
+ },
21
+ args: {
22
+ src: 'https://picsum.photos/300/200',
23
+ width: 300,
24
+ height: 200,
25
+ objectFit: 'cover',
26
+ showLoading: true,
27
+ },
28
+ };
29
+
30
+ export default meta;
31
+
32
+ type Story = StoryObj<typeof Image>;
33
+
34
+ export const Default: Story = {
35
+ render: (args) => <Image {...args} />,
36
+ };
37
+
38
+ export const Sizes: Story = {
39
+ render: () => (
40
+ <VStack space={spacing.lg}>
41
+ <Text weight="semiBold">Different Sizes</Text>
42
+ <HStack space={spacing.lg} alignItems="flex-start">
43
+ <VStack space={spacing.xs}>
44
+ <Image src="https://picsum.photos/100/100" width={100} height={100} />
45
+ <Text size="sm" color={colors.text.secondary}>100×100</Text>
46
+ </VStack>
47
+ <VStack space={spacing.xs}>
48
+ <Image src="https://picsum.photos/150/100" width={150} height={100} />
49
+ <Text size="sm" color={colors.text.secondary}>150×100</Text>
50
+ </VStack>
51
+ <VStack space={spacing.xs}>
52
+ <Image src="https://picsum.photos/200/150" width={200} height={150} />
53
+ <Text size="sm" color={colors.text.secondary}>200×150</Text>
54
+ </VStack>
55
+ </HStack>
56
+ </VStack>
57
+ ),
58
+ };
59
+
60
+ export const ObjectFit: Story = {
61
+ render: () => (
62
+ <VStack space={spacing.lg}>
63
+ <Text weight="semiBold">Object Fit Options</Text>
64
+ <Grid columns={3} gap={spacing.lg}>
65
+ {(['cover', 'contain', 'fill'] as const).map((fit) => (
66
+ <GridItem key={fit}>
67
+ <VStack space={spacing.xs}>
68
+ <Image
69
+ src="https://picsum.photos/400/200"
70
+ width={150}
71
+ height={100}
72
+ objectFit={fit}
73
+ borderRadius={borderRadius.md}
74
+ />
75
+ <Text size="sm" color={colors.text.secondary}>{fit}</Text>
76
+ </VStack>
77
+ </GridItem>
78
+ ))}
79
+ </Grid>
80
+ </VStack>
81
+ ),
82
+ };
83
+
84
+ export const BorderRadius: Story = {
85
+ render: () => (
86
+ <VStack space={16}>
87
+ <Text weight="semiBold">Border Radius</Text>
88
+ <HStack space={16} alignItems="flex-start">
89
+ <VStack space={4}>
90
+ <Image
91
+ src="https://picsum.photos/100/100?1"
92
+ width={100}
93
+ height={100}
94
+ borderRadius={0}
95
+ />
96
+ <Text size="sm" color="#64748b">None</Text>
97
+ </VStack>
98
+ <VStack space={4}>
99
+ <Image
100
+ src="https://picsum.photos/100/100?2"
101
+ width={100}
102
+ height={100}
103
+ borderRadius={8}
104
+ />
105
+ <Text size="sm" color="#64748b">Rounded</Text>
106
+ </VStack>
107
+ <VStack space={4}>
108
+ <Image
109
+ src="https://picsum.photos/100/100?3"
110
+ width={100}
111
+ height={100}
112
+ borderRadius={16}
113
+ />
114
+ <Text size="sm" color="#64748b">More Rounded</Text>
115
+ </VStack>
116
+ <VStack space={4}>
117
+ <Image
118
+ src="https://picsum.photos/100/100?4"
119
+ width={100}
120
+ height={100}
121
+ isCircular
122
+ />
123
+ <Text size="sm" color="#64748b">Circular</Text>
124
+ </VStack>
125
+ </HStack>
126
+ </VStack>
127
+ ),
128
+ };
129
+
130
+ export const Circular: Story = {
131
+ render: () => (
132
+ <VStack space={16}>
133
+ <Text weight="semiBold">Circular Images (Avatars)</Text>
134
+ <HStack space={12}>
135
+ <Image
136
+ src="https://picsum.photos/50/50?5"
137
+ width={50}
138
+ height={50}
139
+ isCircular
140
+ alt="User 1"
141
+ />
142
+ <Image
143
+ src="https://picsum.photos/60/60?6"
144
+ width={60}
145
+ height={60}
146
+ isCircular
147
+ alt="User 2"
148
+ />
149
+ <Image
150
+ src="https://picsum.photos/70/70?7"
151
+ width={70}
152
+ height={70}
153
+ isCircular
154
+ alt="User 3"
155
+ />
156
+ <Image
157
+ src="https://picsum.photos/80/80?8"
158
+ width={80}
159
+ height={80}
160
+ isCircular
161
+ alt="User 4"
162
+ />
163
+ </HStack>
164
+ </VStack>
165
+ ),
166
+ };
167
+
168
+ export const Fallback: Story = {
169
+ render: () => (
170
+ <VStack space={16}>
171
+ <Text weight="semiBold">Fallback States</Text>
172
+ <HStack space={16} alignItems="flex-start">
173
+ <VStack space={4}>
174
+ <Image
175
+ src="https://invalid-url-that-will-fail.com/image.jpg"
176
+ width={150}
177
+ height={100}
178
+ borderRadius={8}
179
+ fallbackText="Image"
180
+ />
181
+ <Text size="sm" color="#64748b">Default Fallback</Text>
182
+ </VStack>
183
+ <VStack space={4}>
184
+ <Image
185
+ src="https://invalid-url-that-will-fail.com/image.jpg"
186
+ width={150}
187
+ height={100}
188
+ borderRadius={8}
189
+ alt="Product Image"
190
+ />
191
+ <Text size="sm" color="#64748b">With Alt Text</Text>
192
+ </VStack>
193
+ <VStack space={4}>
194
+ <Image
195
+ src="https://invalid-url-that-will-fail.com/image.jpg"
196
+ width={150}
197
+ height={100}
198
+ borderRadius={8}
199
+ fallback={
200
+ <Box
201
+ flex={1}
202
+ alignItems="center"
203
+ justifyContent="center"
204
+ bg="#fee2e2"
205
+ style={{ width: 150, height: 100, borderRadius: 8 }}
206
+ >
207
+ <Text color="#ef4444">Failed</Text>
208
+ </Box>
209
+ }
210
+ />
211
+ <Text size="sm" color="#64748b">Custom Fallback</Text>
212
+ </VStack>
213
+ </HStack>
214
+ </VStack>
215
+ ),
216
+ };
217
+
218
+ export const Gallery: Story = {
219
+ render: () => (
220
+ <VStack space={16}>
221
+ <Text weight="semiBold">Image Gallery</Text>
222
+ <Grid columns={3} gap={8}>
223
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => (
224
+ <GridItem key={i}>
225
+ <Image
226
+ src={`https://picsum.photos/200/200?random=${i}`}
227
+ width="100%"
228
+ height={120}
229
+ borderRadius={8}
230
+ objectFit="cover"
231
+ />
232
+ </GridItem>
233
+ ))}
234
+ </Grid>
235
+ </VStack>
236
+ ),
237
+ };
238
+
239
+ export const ProductCard: Story = {
240
+ render: () => (
241
+ <VStack space={spacing.lg}>
242
+ <Text weight="semiBold">Product Card</Text>
243
+ <Box w={250} bg={colors.white} rounded="lg" shadow="10" overflow="hidden">
244
+ <Image
245
+ src="https://picsum.photos/250/180"
246
+ width="100%"
247
+ height={180}
248
+ objectFit="cover"
249
+ />
250
+ <Box p={spacing.lg}>
251
+ <VStack space={spacing.sm}>
252
+ <Text weight="semiBold">Product Name</Text>
253
+ <Text size="sm" color={colors.text.secondary}>
254
+ Short product description goes here
255
+ </Text>
256
+ <HStack justifyContent="space-between" alignItems="center">
257
+ <Text weight="bold" color={colors.brand.blue}>$99.99</Text>
258
+ <Box px={spacing.md} py={6} bg={colors.brand.blue} rounded="md">
259
+ <Text size="sm" style={{ color: 'white' }}>Add to Cart</Text>
260
+ </Box>
261
+ </HStack>
262
+ </VStack>
263
+ </Box>
264
+ </Box>
265
+ </VStack>
266
+ ),
267
+ };
268
+
269
+ export const UserProfile: Story = {
270
+ render: () => (
271
+ <VStack space={spacing.lg}>
272
+ <Text weight="semiBold">User Profile</Text>
273
+ <Box p={20} bg={colors.white} rounded="lg" shadow="10" alignItems="center">
274
+ <Image
275
+ src="https://picsum.photos/120/120?random=profile"
276
+ width={120}
277
+ height={120}
278
+ isCircular
279
+ />
280
+ <VStack space={spacing.xs} alignItems="center" style={{ marginTop: spacing.lg }}>
281
+ <Text weight="semiBold" size="lg">John Doe</Text>
282
+ <Text color={colors.text.secondary}>Senior Developer</Text>
283
+ <Text size="sm" color={colors.text.secondary}>San Francisco, CA</Text>
284
+ </VStack>
285
+ </Box>
286
+ </VStack>
287
+ ),
288
+ };
289
+
290
+ export const HeroImage: Story = {
291
+ render: () => (
292
+ <VStack space={16}>
293
+ <Text weight="semiBold">Hero Image with Overlay</Text>
294
+ <Box w="100%" h={300} rounded="lg" overflow="hidden" position="relative">
295
+ <Image
296
+ src="https://picsum.photos/800/300?random=hero"
297
+ width="100%"
298
+ height="100%"
299
+ objectFit="cover"
300
+ />
301
+ <Box
302
+ position="absolute"
303
+ style={{ top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)' }}
304
+ alignItems="center"
305
+ justifyContent="center"
306
+ >
307
+ <VStack space={8} alignItems="center">
308
+ <Text weight="bold" size="lg" style={{ color: 'white' }}>
309
+ Welcome to Our Platform
310
+ </Text>
311
+ <Text style={{ color: 'white' }}>
312
+ Discover amazing content
313
+ </Text>
314
+ </VStack>
315
+ </Box>
316
+ </Box>
317
+ </VStack>
318
+ ),
319
+ };
320
+
321
+ export const AspectRatios: Story = {
322
+ render: () => (
323
+ <VStack space={16}>
324
+ <Text weight="semiBold">Common Aspect Ratios</Text>
325
+ <HStack space={16} alignItems="flex-start">
326
+ <VStack space={4}>
327
+ <Image
328
+ src="https://picsum.photos/100/100?aspect1"
329
+ width={100}
330
+ height={100}
331
+ borderRadius={8}
332
+ />
333
+ <Text size="sm" color="#64748b">1:1</Text>
334
+ </VStack>
335
+ <VStack space={4}>
336
+ <Image
337
+ src="https://picsum.photos/160/90?aspect2"
338
+ width={160}
339
+ height={90}
340
+ borderRadius={8}
341
+ />
342
+ <Text size="sm" color="#64748b">16:9</Text>
343
+ </VStack>
344
+ <VStack space={4}>
345
+ <Image
346
+ src="https://picsum.photos/120/160?aspect3"
347
+ width={120}
348
+ height={160}
349
+ borderRadius={8}
350
+ />
351
+ <Text size="sm" color="#64748b">3:4</Text>
352
+ </VStack>
353
+ </HStack>
354
+ </VStack>
355
+ ),
356
+ };
357
+
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Image Component
3
+ * Enhanced image with loading states and fallback
4
+ */
5
+
6
+ import React, { forwardRef, useState } from 'react';
7
+ import {
8
+ Image as RNImage,
9
+ ImageProps as RNImageProps,
10
+ View,
11
+ ActivityIndicator,
12
+ Text,
13
+ StyleSheet,
14
+ } from 'react-native';
15
+ import { colors, typography } from '../../styles/tokens';
16
+
17
+ export interface ImageProps extends Omit<RNImageProps, 'source'> {
18
+ /** Image source URL */
19
+ src?: string;
20
+ /** Image source (for local images) */
21
+ source?: RNImageProps['source'];
22
+ /** Alt text */
23
+ alt?: string;
24
+ /** Fallback element */
25
+ fallback?: React.ReactNode;
26
+ /** Fallback text */
27
+ fallbackText?: string;
28
+ /** Object fit */
29
+ objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
30
+ /** Border radius */
31
+ borderRadius?: number;
32
+ /** Is circular */
33
+ isCircular?: boolean;
34
+ /** Width */
35
+ width?: number | string;
36
+ /** Height */
37
+ height?: number | string;
38
+ /** Show loading indicator */
39
+ showLoading?: boolean;
40
+ }
41
+
42
+ export const Image = forwardRef<RNImage, ImageProps>(
43
+ (
44
+ {
45
+ style,
46
+ src,
47
+ source,
48
+ alt,
49
+ fallback,
50
+ fallbackText,
51
+ objectFit = 'cover',
52
+ borderRadius,
53
+ isCircular = false,
54
+ width,
55
+ height,
56
+ showLoading = true,
57
+ onLoadStart,
58
+ onLoadEnd,
59
+ onError,
60
+ ...props
61
+ },
62
+ ref
63
+ ) => {
64
+ const [isLoading, setIsLoading] = useState(true);
65
+ const [hasError, setHasError] = useState(false);
66
+
67
+ const imageSource = src ? { uri: src } : source;
68
+
69
+ const handleLoadStart = (e: any) => {
70
+ setIsLoading(true);
71
+ onLoadStart?.(e);
72
+ };
73
+
74
+ const handleLoadEnd = (e: any) => {
75
+ setIsLoading(false);
76
+ onLoadEnd?.(e);
77
+ };
78
+
79
+ const handleError = (e: any) => {
80
+ setIsLoading(false);
81
+ setHasError(true);
82
+ onError?.(e);
83
+ };
84
+
85
+ const resizeModeMap = {
86
+ cover: 'cover' as const,
87
+ contain: 'contain' as const,
88
+ fill: 'stretch' as const,
89
+ none: 'center' as const,
90
+ 'scale-down': 'contain' as const,
91
+ };
92
+
93
+ const containerStyle = [
94
+ styles.container,
95
+ width !== undefined && { width },
96
+ height !== undefined && { height },
97
+ borderRadius !== undefined && { borderRadius },
98
+ isCircular && { borderRadius: 9999 },
99
+ style,
100
+ ];
101
+
102
+ if (hasError) {
103
+ if (fallback) {
104
+ return <View style={containerStyle}>{fallback}</View>;
105
+ }
106
+ return (
107
+ <View style={[containerStyle, styles.fallback]}>
108
+ <Text style={styles.fallbackText}>{fallbackText || alt || '🖼'}</Text>
109
+ </View>
110
+ );
111
+ }
112
+
113
+ return (
114
+ <View style={containerStyle}>
115
+ <RNImage
116
+ ref={ref}
117
+ source={imageSource}
118
+ style={[
119
+ styles.image,
120
+ width !== undefined && { width },
121
+ height !== undefined && { height },
122
+ borderRadius !== undefined && { borderRadius },
123
+ isCircular && { borderRadius: 9999 },
124
+ ]}
125
+ resizeMode={resizeModeMap[objectFit]}
126
+ onLoadStart={handleLoadStart}
127
+ onLoadEnd={handleLoadEnd}
128
+ onError={handleError}
129
+ accessibilityLabel={alt}
130
+ {...props}
131
+ />
132
+ {isLoading && showLoading && (
133
+ <View style={styles.loadingOverlay}>
134
+ <ActivityIndicator size="small" color={colors.brand.blue} />
135
+ </View>
136
+ )}
137
+ </View>
138
+ );
139
+ }
140
+ );
141
+
142
+ Image.displayName = 'Image';
143
+
144
+ const styles = StyleSheet.create({
145
+ container: {
146
+ overflow: 'hidden',
147
+ backgroundColor: colors.background.secondary,
148
+ },
149
+ image: {
150
+ width: '100%',
151
+ height: '100%',
152
+ },
153
+ loadingOverlay: {
154
+ ...StyleSheet.absoluteFillObject,
155
+ alignItems: 'center',
156
+ justifyContent: 'center',
157
+ backgroundColor: colors.background.secondary,
158
+ },
159
+ fallback: {
160
+ alignItems: 'center',
161
+ justifyContent: 'center',
162
+ backgroundColor: colors.background.tertiary,
163
+ },
164
+ fallbackText: {
165
+ fontSize: typography.fontSize.caption,
166
+ color: colors.text.secondary,
167
+ },
168
+ });
@@ -0,0 +1,2 @@
1
+ export { Image, type ImageProps } from './Image';
2
+
@@ -0,0 +1,164 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import { Input } from './Input';
4
+ import { VStack } from '../VStack';
5
+ import { Text } from '../Text';
6
+ import Svg, { Path } from 'react-native-svg';
7
+ import { colors, spacing, borderRadius } from '../../styles/tokens';
8
+
9
+ /**
10
+ * Story container with design system tokens
11
+ */
12
+ const StoryContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => (
13
+ <View style={styles.container}>{children}</View>
14
+ );
15
+
16
+ const meta: Meta<typeof Input> = {
17
+ title: 'Components/Input',
18
+ component: Input,
19
+ decorators: [
20
+ (Story) => (
21
+ <StoryContainer>
22
+ <Story />
23
+ </StoryContainer>
24
+ ),
25
+ ],
26
+ argTypes: {
27
+ variant: {
28
+ control: 'select',
29
+ options: ['outline', 'filled', 'flushed'],
30
+ },
31
+ size: {
32
+ control: 'select',
33
+ options: ['sm', 'md', 'lg'],
34
+ },
35
+ isInvalid: {
36
+ control: 'boolean',
37
+ },
38
+ isDisabled: {
39
+ control: 'boolean',
40
+ },
41
+ },
42
+ args: {
43
+ placeholder: 'Enter text...',
44
+ variant: 'outline',
45
+ size: 'md',
46
+ isInvalid: false,
47
+ isDisabled: false,
48
+ },
49
+ };
50
+
51
+ export default meta;
52
+
53
+ type Story = StoryObj<typeof Input>;
54
+
55
+ export const Default: Story = {};
56
+
57
+ export const Variants: Story = {
58
+ render: () => (
59
+ <VStack space={spacing.lg}>
60
+ <Text weight="semiBold">Input Variants</Text>
61
+ <Input variant="outline" label="Outline" placeholder="Outline input" />
62
+ <Input variant="filled" label="Filled" placeholder="Filled input" />
63
+ <Input variant="flushed" label="Flushed" placeholder="Flushed input" />
64
+ </VStack>
65
+ ),
66
+ };
67
+
68
+ export const Sizes: Story = {
69
+ render: () => (
70
+ <VStack space={spacing.lg}>
71
+ <Text weight="semiBold">Input Sizes</Text>
72
+ <Input size="sm" placeholder="Small input" />
73
+ <Input size="md" placeholder="Medium input" />
74
+ <Input size="lg" placeholder="Large input" />
75
+ </VStack>
76
+ ),
77
+ };
78
+
79
+ export const WithLabels: Story = {
80
+ render: () => (
81
+ <VStack space={spacing.lg}>
82
+ <Text weight="semiBold">Input with Labels</Text>
83
+ <Input
84
+ label="Email"
85
+ placeholder="Enter your email"
86
+ helperText="We'll never share your email"
87
+ />
88
+ <Input
89
+ label="Username"
90
+ placeholder="Enter username"
91
+ isRequired
92
+ />
93
+ </VStack>
94
+ ),
95
+ };
96
+
97
+ export const States: Story = {
98
+ render: () => (
99
+ <VStack space={spacing.lg}>
100
+ <Text weight="semiBold">Input States</Text>
101
+ <Input label="Normal" placeholder="Normal input" />
102
+ <Input
103
+ label="Invalid"
104
+ placeholder="Invalid input"
105
+ isInvalid
106
+ errorMessage="This field is required"
107
+ />
108
+ <Input
109
+ label="Disabled"
110
+ placeholder="Disabled input"
111
+ isDisabled
112
+ />
113
+ </VStack>
114
+ ),
115
+ };
116
+
117
+ export const Password: Story = {
118
+ render: () => (
119
+ <VStack space={spacing.lg}>
120
+ <Text weight="semiBold">Password Input</Text>
121
+ <Input
122
+ label="Password"
123
+ placeholder="Enter password"
124
+ isPassword
125
+ />
126
+ </VStack>
127
+ ),
128
+ };
129
+
130
+ export const WithElements: Story = {
131
+ render: () => {
132
+ const SearchIcon = (
133
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
134
+ <Path
135
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
136
+ stroke={colors.text.disabled}
137
+ strokeWidth={2}
138
+ strokeLinecap="round"
139
+ />
140
+ </Svg>
141
+ );
142
+ return (
143
+ <VStack space={spacing.lg}>
144
+ <Text weight="semiBold">Input with Elements</Text>
145
+ <Input
146
+ placeholder="Search..."
147
+ leftElement={SearchIcon}
148
+ />
149
+ </VStack>
150
+ );
151
+ },
152
+ };
153
+
154
+ /**
155
+ * Styles using design system tokens
156
+ */
157
+ const styles = StyleSheet.create({
158
+ container: {
159
+ padding: spacing.lg,
160
+ backgroundColor: colors.background.default,
161
+ borderRadius: borderRadius.lg,
162
+ },
163
+ });
164
+