@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,354 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Tooltip } from './Tooltip';
3
+ import { Button } from '../Button';
4
+ import { VStack } from '../VStack';
5
+ import { HStack } from '../HStack';
6
+ import { Text } from '../Text';
7
+ import { Box } from '../Box';
8
+ import Svg, { Path } from 'react-native-svg';
9
+ import { colors, spacing, borderRadius, elevation } from '../../styles/tokens';
10
+
11
+ const meta: Meta<typeof Tooltip> = {
12
+ title: 'Overlay/Tooltip',
13
+ component: Tooltip,
14
+ argTypes: {
15
+ placement: {
16
+ control: 'select',
17
+ options: ['top', 'bottom', 'left', 'right'],
18
+ },
19
+ isDisabled: { control: 'boolean' },
20
+ hasArrow: { control: 'boolean' },
21
+ openDelay: { control: 'number' },
22
+ closeDelay: { control: 'number' },
23
+ },
24
+ args: {
25
+ label: 'Tooltip text',
26
+ placement: 'top',
27
+ isDisabled: false,
28
+ hasArrow: true,
29
+ openDelay: 0,
30
+ closeDelay: 0,
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof Tooltip>;
37
+
38
+ export const Default: Story = {
39
+ render: () => (
40
+ <Box p={50} alignItems="center">
41
+ <Tooltip label="This is a tooltip">
42
+ <Button>Hover me</Button>
43
+ </Tooltip>
44
+ </Box>
45
+ ),
46
+ };
47
+
48
+ const PlacementDemo = () => (
49
+ <VStack space={spacing.lg}>
50
+ <Text weight="semiBold">Tooltip Placement</Text>
51
+ <Box p={100} alignItems="center">
52
+ <VStack space={spacing.xl} alignItems="center">
53
+ <Tooltip label="I appear on top" placement="top">
54
+ <Button size="sm">Top</Button>
55
+ </Tooltip>
56
+
57
+ <HStack space={100}>
58
+ <Tooltip label="I appear on the left" placement="left">
59
+ <Button size="sm">Left</Button>
60
+ </Tooltip>
61
+
62
+ <Tooltip label="I appear on the right" placement="right">
63
+ <Button size="sm">Right</Button>
64
+ </Tooltip>
65
+ </HStack>
66
+
67
+ <Tooltip label="I appear on bottom" placement="bottom">
68
+ <Button size="sm">Bottom</Button>
69
+ </Tooltip>
70
+ </VStack>
71
+ </Box>
72
+ </VStack>
73
+ );
74
+
75
+ export const Placement: Story = {
76
+ render: () => <PlacementDemo />,
77
+ };
78
+
79
+ const WithDelays = () => (
80
+ <VStack space={spacing.lg}>
81
+ <Text weight="semiBold">Tooltip with Delays</Text>
82
+ <HStack space={spacing.lg} wrap>
83
+ <Tooltip label="No delay" openDelay={0}>
84
+ <Button variant="outline" size="sm">Instant</Button>
85
+ </Tooltip>
86
+ <Tooltip label="Opens after 500ms" openDelay={500}>
87
+ <Button variant="outline" size="sm">500ms delay</Button>
88
+ </Tooltip>
89
+ <Tooltip label="Opens after 1000ms" openDelay={1000}>
90
+ <Button variant="outline" size="sm">1s delay</Button>
91
+ </Tooltip>
92
+ </HStack>
93
+ </VStack>
94
+ );
95
+
96
+ export const Delays: Story = {
97
+ render: () => <WithDelays />,
98
+ };
99
+
100
+ const DisabledTooltip = () => (
101
+ <VStack space={spacing.lg}>
102
+ <Text weight="semiBold">Disabled Tooltip</Text>
103
+ <HStack space={spacing.lg}>
104
+ <Tooltip label="This tooltip is enabled">
105
+ <Button size="sm">Enabled</Button>
106
+ </Tooltip>
107
+ <Tooltip label="You won't see this" isDisabled>
108
+ <Button size="sm" variant="secondary">Disabled</Button>
109
+ </Tooltip>
110
+ </HStack>
111
+ </VStack>
112
+ );
113
+
114
+ export const Disabled: Story = {
115
+ render: () => <DisabledTooltip />,
116
+ };
117
+
118
+ const IconTooltips = () => {
119
+ const InfoIcon = (
120
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
121
+ <Path
122
+ d="M12 16v-4m0-4h.01M22 12a10 10 0 11-20 0 10 10 0 0120 0z"
123
+ stroke={colors.text.secondary}
124
+ strokeWidth={2}
125
+ strokeLinecap="round"
126
+ strokeLinejoin="round"
127
+ />
128
+ </Svg>
129
+ );
130
+
131
+ const EditIcon = (
132
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
133
+ <Path
134
+ d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
135
+ stroke={colors.text.secondary}
136
+ strokeWidth={2}
137
+ strokeLinecap="round"
138
+ strokeLinejoin="round"
139
+ />
140
+ </Svg>
141
+ );
142
+
143
+ const DeleteIcon = (
144
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
145
+ <Path
146
+ d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
147
+ stroke={colors.feedback.error.content}
148
+ strokeWidth={2}
149
+ strokeLinecap="round"
150
+ strokeLinejoin="round"
151
+ />
152
+ </Svg>
153
+ );
154
+
155
+ return (
156
+ <VStack space={spacing.lg}>
157
+ <Text weight="semiBold">Icon Button Tooltips</Text>
158
+ <HStack space={spacing.sm}>
159
+ <Tooltip label="More information">
160
+ <Box p={spacing.sm} rounded="md" bg={colors.background.secondary}>
161
+ {InfoIcon}
162
+ </Box>
163
+ </Tooltip>
164
+ <Tooltip label="Edit item">
165
+ <Box p={spacing.sm} rounded="md" bg={colors.background.secondary}>
166
+ {EditIcon}
167
+ </Box>
168
+ </Tooltip>
169
+ <Tooltip label="Delete item">
170
+ <Box p={spacing.sm} rounded="md" bg={colors.feedback.error.background}>
171
+ {DeleteIcon}
172
+ </Box>
173
+ </Tooltip>
174
+ </HStack>
175
+ </VStack>
176
+ );
177
+ };
178
+
179
+ export const Icons: Story = {
180
+ render: () => <IconTooltips />,
181
+ };
182
+
183
+ const ToolbarTooltips = () => (
184
+ <VStack space={spacing.lg}>
185
+ <Text weight="semiBold">Toolbar with Tooltips</Text>
186
+ <Box p={spacing.md} bg={colors.background.secondary} rounded="lg">
187
+ <HStack space={spacing.xs}>
188
+ <Tooltip label="Bold (Ctrl+B)">
189
+ <Box p={spacing.sm} rounded="md" bg={colors.border.default}>
190
+ <Text weight="bold">B</Text>
191
+ </Box>
192
+ </Tooltip>
193
+ <Tooltip label="Italic (Ctrl+I)">
194
+ <Box p={spacing.sm} rounded="md" bg={colors.border.default}>
195
+ <Text style={{ fontStyle: 'italic' }}>I</Text>
196
+ </Box>
197
+ </Tooltip>
198
+ <Tooltip label="Underline (Ctrl+U)">
199
+ <Box p={spacing.sm} rounded="md" bg={colors.border.default}>
200
+ <Text style={{ textDecorationLine: 'underline' }}>U</Text>
201
+ </Box>
202
+ </Tooltip>
203
+ <Box w={1} h={24} bg={colors.border.subtle} mx={spacing.xs} />
204
+ <Tooltip label="Align Left">
205
+ <Box p={spacing.sm} rounded="md" bg={colors.border.default}>
206
+ <Text>⫷</Text>
207
+ </Box>
208
+ </Tooltip>
209
+ <Tooltip label="Align Center">
210
+ <Box p={spacing.sm} rounded="md" bg={colors.border.default}>
211
+ <Text>≡</Text>
212
+ </Box>
213
+ </Tooltip>
214
+ <Tooltip label="Align Right">
215
+ <Box p={spacing.sm} rounded="md" bg={colors.border.default}>
216
+ <Text>⫸</Text>
217
+ </Box>
218
+ </Tooltip>
219
+ </HStack>
220
+ </Box>
221
+ </VStack>
222
+ );
223
+
224
+ export const Toolbar: Story = {
225
+ render: () => <ToolbarTooltips />,
226
+ };
227
+
228
+ const FormFieldTooltips = () => (
229
+ <VStack space={spacing.lg}>
230
+ <Text weight="semiBold">Form Field Help Tooltips</Text>
231
+ <VStack space={spacing.md}>
232
+ <HStack space={spacing.sm} alignItems="center">
233
+ <Text>Username</Text>
234
+ <Tooltip label="Your username must be unique and contain only letters, numbers, and underscores">
235
+ <Box
236
+ w={16}
237
+ h={16}
238
+ rounded="full"
239
+ bg={colors.brand.blue + '20'}
240
+ alignItems="center"
241
+ justifyContent="center"
242
+ >
243
+ <Text variant="small">?</Text>
244
+ </Box>
245
+ </Tooltip>
246
+ </HStack>
247
+ <HStack space={spacing.sm} alignItems="center">
248
+ <Text>Password</Text>
249
+ <Tooltip label="Password must be at least 8 characters with one uppercase, one lowercase, and one number">
250
+ <Box
251
+ w={16}
252
+ h={16}
253
+ rounded="full"
254
+ bg={colors.brand.blue + '20'}
255
+ alignItems="center"
256
+ justifyContent="center"
257
+ >
258
+ <Text variant="small">?</Text>
259
+ </Box>
260
+ </Tooltip>
261
+ </HStack>
262
+ </VStack>
263
+ </VStack>
264
+ );
265
+
266
+ export const FormHelp: Story = {
267
+ render: () => <FormFieldTooltips />,
268
+ };
269
+
270
+ const StatusTooltips = () => (
271
+ <VStack space={spacing.lg}>
272
+ <Text weight="semiBold">Status Indicator Tooltips</Text>
273
+ <HStack space={spacing.lg}>
274
+ <Tooltip label="Online - Active now">
275
+ <HStack space={spacing.sm} alignItems="center">
276
+ <Box w={10} h={10} rounded="full" bg={colors.feedback.success.content} />
277
+ <Text>User A</Text>
278
+ </HStack>
279
+ </Tooltip>
280
+ <Tooltip label="Away - Last seen 5 minutes ago">
281
+ <HStack space={spacing.sm} alignItems="center">
282
+ <Box w={10} h={10} rounded="full" bg={colors.feedback.warning.content} />
283
+ <Text>User B</Text>
284
+ </HStack>
285
+ </Tooltip>
286
+ <Tooltip label="Offline">
287
+ <HStack space={spacing.sm} alignItems="center">
288
+ <Box w={10} h={10} rounded="full" bg={colors.text.disabled} />
289
+ <Text>User C</Text>
290
+ </HStack>
291
+ </Tooltip>
292
+ </HStack>
293
+ </VStack>
294
+ );
295
+
296
+ export const Status: Story = {
297
+ render: () => <StatusTooltips />,
298
+ };
299
+
300
+ const LongContentTooltip = () => (
301
+ <VStack space={spacing.lg}>
302
+ <Text weight="semiBold">Long Content Tooltip</Text>
303
+ <Box p={50}>
304
+ <Tooltip
305
+ label="This is a longer tooltip that contains more detailed information. It will wrap to multiple lines if necessary to fit within the maximum width."
306
+ >
307
+ <Button variant="outline">View details</Button>
308
+ </Tooltip>
309
+ </Box>
310
+ </VStack>
311
+ );
312
+
313
+ export const LongContent: Story = {
314
+ render: () => <LongContentTooltip />,
315
+ };
316
+
317
+ const DataTableTooltips = () => (
318
+ <VStack space={spacing.lg}>
319
+ <Text weight="semiBold">Data Table Cell Tooltips</Text>
320
+ <Box p={spacing.lg} bg={colors.white} rounded="lg" shadow="10">
321
+ <VStack space={spacing.sm}>
322
+ <HStack justifyContent="space-between">
323
+ <Text weight="semiBold">Name</Text>
324
+ <Text weight="semiBold">Status</Text>
325
+ </HStack>
326
+ <HStack justifyContent="space-between" py={spacing.sm}>
327
+ <Tooltip label="John Doe - Senior Developer">
328
+ <Text>John D.</Text>
329
+ </Tooltip>
330
+ <Tooltip label="Completed all assigned tasks">
331
+ <Box px={spacing.sm} py={spacing.xs} bg={colors.feedback.success.background} rounded="full">
332
+ <Text variant="small" color={colors.feedback.success.content}>Active</Text>
333
+ </Box>
334
+ </Tooltip>
335
+ </HStack>
336
+ <HStack justifyContent="space-between" py={spacing.sm}>
337
+ <Tooltip label="Jane Smith - Product Manager">
338
+ <Text>Jane S.</Text>
339
+ </Tooltip>
340
+ <Tooltip label="On vacation until Jan 20">
341
+ <Box px={spacing.sm} py={spacing.xs} bg={colors.feedback.warning.background} rounded="full">
342
+ <Text variant="small" color={colors.feedback.warning.content}>Away</Text>
343
+ </Box>
344
+ </Tooltip>
345
+ </HStack>
346
+ </VStack>
347
+ </Box>
348
+ </VStack>
349
+ );
350
+
351
+ export const DataTable: Story = {
352
+ render: () => <DataTableTooltips />,
353
+ };
354
+
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Tooltip Component
3
+ * Informational popup on hover/press
4
+ */
5
+
6
+ import React, { forwardRef, useState, useRef, useCallback } from 'react';
7
+ import {
8
+ View,
9
+ ViewProps,
10
+ Text,
11
+ Animated,
12
+ LayoutRectangle,
13
+ Dimensions,
14
+ StyleSheet,
15
+ Platform,
16
+ } from 'react-native';
17
+ import { colors, spacing, borderRadius, typography, elevation } from '../../styles/tokens';
18
+
19
+ const { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get('window');
20
+
21
+ export interface TooltipProps extends ViewProps {
22
+ /** Tooltip content */
23
+ label: string;
24
+ /** Tooltip trigger element */
25
+ children: React.ReactNode;
26
+ /** Placement */
27
+ placement?: 'top' | 'bottom' | 'left' | 'right';
28
+ /** Is disabled */
29
+ isDisabled?: boolean;
30
+ /** Show delay in ms */
31
+ openDelay?: number;
32
+ /** Hide delay in ms */
33
+ closeDelay?: number;
34
+ /** Has arrow */
35
+ hasArrow?: boolean;
36
+ }
37
+
38
+ export const Tooltip = forwardRef<View, TooltipProps>(
39
+ (
40
+ {
41
+ style,
42
+ label,
43
+ children,
44
+ placement = 'top',
45
+ isDisabled = false,
46
+ openDelay = 0,
47
+ closeDelay = 0,
48
+ hasArrow = true,
49
+ ...props
50
+ },
51
+ ref
52
+ ) => {
53
+ const [isOpen, setIsOpen] = useState(false);
54
+ const [triggerLayout, setTriggerLayout] = useState<LayoutRectangle | null>(null);
55
+ const triggerRef = useRef<View>(null);
56
+ const opacityAnim = useRef(new Animated.Value(0)).current;
57
+ const openTimeout = useRef<NodeJS.Timeout>();
58
+ const closeTimeout = useRef<NodeJS.Timeout>();
59
+
60
+ const show = useCallback(() => {
61
+ if (isDisabled) return;
62
+
63
+ clearTimeout(closeTimeout.current);
64
+
65
+ triggerRef.current?.measureInWindow((x, y, width, height) => {
66
+ setTriggerLayout({ x, y, width, height });
67
+
68
+ openTimeout.current = setTimeout(() => {
69
+ setIsOpen(true);
70
+ Animated.timing(opacityAnim, {
71
+ toValue: 1,
72
+ duration: 150,
73
+ useNativeDriver: true,
74
+ }).start();
75
+ }, openDelay);
76
+ });
77
+ }, [isDisabled, openDelay]);
78
+
79
+ const hide = useCallback(() => {
80
+ clearTimeout(openTimeout.current);
81
+
82
+ closeTimeout.current = setTimeout(() => {
83
+ Animated.timing(opacityAnim, {
84
+ toValue: 0,
85
+ duration: 100,
86
+ useNativeDriver: true,
87
+ }).start(() => {
88
+ setIsOpen(false);
89
+ });
90
+ }, closeDelay);
91
+ }, [closeDelay]);
92
+
93
+ const getTooltipPosition = () => {
94
+ if (!triggerLayout) return {};
95
+
96
+ const tooltipPadding = 8;
97
+
98
+ // For web with fixed positioning, we can use screen coordinates directly
99
+ switch (placement) {
100
+ case 'top':
101
+ return {
102
+ bottom: SCREEN_HEIGHT - triggerLayout.y + tooltipPadding,
103
+ left: triggerLayout.x,
104
+ width: triggerLayout.width,
105
+ };
106
+ case 'bottom':
107
+ return {
108
+ top: triggerLayout.y + triggerLayout.height + tooltipPadding,
109
+ left: triggerLayout.x,
110
+ width: triggerLayout.width,
111
+ };
112
+ case 'left':
113
+ return {
114
+ top: triggerLayout.y,
115
+ right: SCREEN_WIDTH - triggerLayout.x + tooltipPadding,
116
+ };
117
+ case 'right':
118
+ return {
119
+ top: triggerLayout.y,
120
+ left: triggerLayout.x + triggerLayout.width + tooltipPadding,
121
+ };
122
+ default:
123
+ return {};
124
+ }
125
+ };
126
+
127
+ const getArrowStyle = () => {
128
+ switch (placement) {
129
+ case 'top':
130
+ return styles.arrowBottom;
131
+ case 'bottom':
132
+ return styles.arrowTop;
133
+ case 'left':
134
+ return styles.arrowRight;
135
+ case 'right':
136
+ return styles.arrowLeft;
137
+ default:
138
+ return {};
139
+ }
140
+ };
141
+
142
+ // Clone children and inject handlers (hover for web, press for native)
143
+ const child = React.Children.only(children) as React.ReactElement;
144
+ const handlers: any = Platform.OS === 'web'
145
+ ? {
146
+ // Web: use hover events for tooltip
147
+ onHoverIn: (e: any) => {
148
+ (child.props as any)?.onHoverIn?.(e);
149
+ show();
150
+ },
151
+ onHoverOut: (e: any) => {
152
+ (child.props as any)?.onHoverOut?.(e);
153
+ hide();
154
+ },
155
+ }
156
+ : {
157
+ // Native: use press events for tooltip
158
+ onPressIn: (e: any) => {
159
+ (child.props as any)?.onPressIn?.(e);
160
+ show();
161
+ },
162
+ onPressOut: (e: any) => {
163
+ (child.props as any)?.onPressOut?.(e);
164
+ hide();
165
+ },
166
+ onLongPress: (e: any) => {
167
+ (child.props as any)?.onLongPress?.(e);
168
+ show();
169
+ },
170
+ };
171
+ const childWithHandlers = React.cloneElement(child, handlers);
172
+
173
+ return (
174
+ <View ref={ref} {...props}>
175
+ <View ref={triggerRef} collapsable={false}>
176
+ {childWithHandlers}
177
+ </View>
178
+ {isOpen && (
179
+ <Animated.View
180
+ style={[
181
+ styles.tooltip,
182
+ getTooltipPosition(),
183
+ { opacity: opacityAnim },
184
+ style,
185
+ ]}
186
+ pointerEvents="none"
187
+ >
188
+ <Text style={styles.label}>{label}</Text>
189
+ {hasArrow && <View style={[styles.arrow, getArrowStyle()]} />}
190
+ </Animated.View>
191
+ )}
192
+ </View>
193
+ );
194
+ }
195
+ );
196
+
197
+ Tooltip.displayName = 'Tooltip';
198
+
199
+ const styles = StyleSheet.create({
200
+ tooltip: {
201
+ position: Platform.OS === 'web' ? ('fixed' as any) : 'absolute',
202
+ backgroundColor: colors.brand.navy,
203
+ paddingVertical: spacing['2x'],
204
+ paddingHorizontal: spacing['3x'],
205
+ borderRadius: borderRadius.md,
206
+ maxWidth: 200,
207
+ zIndex: 9999,
208
+ ...elevation['10'],
209
+ },
210
+ label: {
211
+ fontSize: typography.fontSize.small,
212
+ color: colors.white,
213
+ textAlign: 'center',
214
+ },
215
+ arrow: {
216
+ position: 'absolute',
217
+ width: 0,
218
+ height: 0,
219
+ borderStyle: 'solid',
220
+ },
221
+ arrowTop: {
222
+ top: -6,
223
+ alignSelf: 'center',
224
+ borderLeftWidth: 6,
225
+ borderRightWidth: 6,
226
+ borderBottomWidth: 6,
227
+ borderLeftColor: 'transparent',
228
+ borderRightColor: 'transparent',
229
+ borderBottomColor: colors.brand.navy,
230
+ },
231
+ arrowBottom: {
232
+ bottom: -6,
233
+ alignSelf: 'center',
234
+ borderLeftWidth: 6,
235
+ borderRightWidth: 6,
236
+ borderTopWidth: 6,
237
+ borderLeftColor: 'transparent',
238
+ borderRightColor: 'transparent',
239
+ borderTopColor: colors.brand.navy,
240
+ },
241
+ arrowLeft: {
242
+ left: -6,
243
+ alignSelf: 'center',
244
+ borderTopWidth: 6,
245
+ borderBottomWidth: 6,
246
+ borderRightWidth: 6,
247
+ borderTopColor: 'transparent',
248
+ borderBottomColor: 'transparent',
249
+ borderRightColor: colors.brand.navy,
250
+ },
251
+ arrowRight: {
252
+ right: -6,
253
+ alignSelf: 'center',
254
+ borderTopWidth: 6,
255
+ borderBottomWidth: 6,
256
+ borderLeftWidth: 6,
257
+ borderTopColor: 'transparent',
258
+ borderBottomColor: 'transparent',
259
+ borderLeftColor: colors.brand.navy,
260
+ },
261
+ });
@@ -0,0 +1,2 @@
1
+ export { Tooltip, type TooltipProps } from './Tooltip';
2
+