@titan-design/react-ui 0.1.0 → 0.2.5

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 (236) hide show
  1. package/README.md +47 -1
  2. package/dist/{chunk-MGW37MPO.mjs → chunk-UXVRPOME.mjs} +173 -3
  3. package/dist/chunk-UXVRPOME.mjs.map +1 -0
  4. package/dist/index.d.mts +101 -12
  5. package/dist/index.d.ts +101 -12
  6. package/dist/index.js +1267 -468
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +734 -112
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/theme/index.d.mts +86 -1
  11. package/dist/theme/index.d.ts +86 -1
  12. package/dist/theme/index.js +166 -0
  13. package/dist/theme/index.js.map +1 -1
  14. package/dist/theme/index.mjs +1 -1
  15. package/package.json +22 -6
  16. package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
  17. package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
  18. package/src/components/custom/DateTime/DateTime.tsx +235 -0
  19. package/src/components/custom/DateTime/index.ts +2 -0
  20. package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
  21. package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
  22. package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
  23. package/src/components/custom/EmptyState/index.ts +2 -0
  24. package/src/components/custom/Metric/Metric.stories.tsx +133 -0
  25. package/src/components/custom/Metric/Metric.test.tsx +120 -0
  26. package/src/components/custom/Metric/Metric.tsx +92 -0
  27. package/src/components/custom/Metric/index.ts +2 -0
  28. package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
  29. package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
  30. package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
  31. package/src/components/custom/Sidebar/index.ts +18 -0
  32. package/src/components/custom/Table/Table.stories.tsx +481 -0
  33. package/src/components/custom/Table/Table.test.tsx +531 -0
  34. package/src/components/custom/Table/Table.tsx +696 -0
  35. package/src/components/custom/Table/index.ts +22 -0
  36. package/src/components/custom/Typography/Typography.stories.tsx +153 -0
  37. package/src/components/custom/Typography/Typography.test.tsx +107 -0
  38. package/src/components/custom/Typography/Typography.tsx +218 -0
  39. package/src/components/custom/Typography/index.ts +19 -0
  40. package/src/components/custom/Workout/RestTimer.stories.tsx +71 -0
  41. package/src/components/custom/Workout/RestTimer.test.tsx +145 -0
  42. package/src/components/custom/Workout/RestTimer.tsx +172 -0
  43. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +144 -0
  44. package/src/components/custom/Workout/SupersetWrapper.test.tsx +130 -0
  45. package/src/components/custom/Workout/SupersetWrapper.tsx +63 -0
  46. package/src/components/custom/Workout/index.ts +2 -0
  47. package/src/components/custom/index.ts +9 -0
  48. package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
  49. package/src/components/custom/stepper/Stepper.test.tsx +230 -0
  50. package/src/components/custom/stepper/Stepper.tsx +250 -0
  51. package/src/components/custom/stepper/index.ts +10 -0
  52. package/src/components/index.ts +5 -0
  53. package/src/components/ui/alert/Alert.stories.tsx +154 -0
  54. package/src/components/ui/alert/Alert.test.tsx +202 -0
  55. package/src/components/ui/alert/Alert.tsx +170 -0
  56. package/src/components/ui/alert/index.ts +8 -0
  57. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
  58. package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
  59. package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
  60. package/src/components/ui/autocomplete/index.ts +2 -0
  61. package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
  62. package/src/components/ui/avatar/Avatar.test.tsx +152 -0
  63. package/src/components/ui/avatar/Avatar.tsx +176 -0
  64. package/src/components/ui/avatar/index.ts +2 -0
  65. package/src/components/ui/badge/Badge.stories.tsx +100 -0
  66. package/src/components/ui/badge/Badge.test.tsx +103 -0
  67. package/src/components/ui/badge/Badge.tsx +119 -0
  68. package/src/components/ui/badge/index.ts +3 -0
  69. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
  70. package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
  71. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
  72. package/src/components/ui/breadcrumbs/index.ts +2 -0
  73. package/src/components/ui/button/Button.stories.tsx +216 -0
  74. package/src/components/ui/button/Button.test.tsx +132 -0
  75. package/src/components/ui/button/Button.tsx +297 -0
  76. package/src/components/ui/button/index.ts +10 -0
  77. package/src/components/ui/card/Card.stories.tsx +443 -0
  78. package/src/components/ui/card/Card.test.tsx +294 -0
  79. package/src/components/ui/card/Card.tsx +401 -0
  80. package/src/components/ui/card/index.ts +20 -0
  81. package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
  82. package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
  83. package/src/components/ui/checkbox/Checkbox.tsx +163 -0
  84. package/src/components/ui/checkbox/index.ts +2 -0
  85. package/src/components/ui/chip/Chip.stories.tsx +112 -0
  86. package/src/components/ui/chip/Chip.test.tsx +119 -0
  87. package/src/components/ui/chip/Chip.tsx +151 -0
  88. package/src/components/ui/chip/index.ts +2 -0
  89. package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
  90. package/src/components/ui/collapse/Collapse.test.tsx +298 -0
  91. package/src/components/ui/collapse/Collapse.tsx +275 -0
  92. package/src/components/ui/collapse/index.ts +18 -0
  93. package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
  94. package/src/components/ui/data-row/DataRow.test.tsx +78 -0
  95. package/src/components/ui/data-row/DataRow.tsx +29 -0
  96. package/src/components/ui/data-row/index.ts +2 -0
  97. package/src/components/ui/divider/Divider.stories.tsx +71 -0
  98. package/src/components/ui/divider/Divider.test.tsx +55 -0
  99. package/src/components/ui/divider/Divider.tsx +37 -0
  100. package/src/components/ui/divider/index.ts +2 -0
  101. package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
  102. package/src/components/ui/drawer/Drawer.test.tsx +142 -0
  103. package/src/components/ui/drawer/Drawer.tsx +228 -0
  104. package/src/components/ui/drawer/index.ts +9 -0
  105. package/src/components/ui/form-field/FormField.stories.tsx +260 -0
  106. package/src/components/ui/form-field/FormField.test.tsx +280 -0
  107. package/src/components/ui/form-field/FormField.tsx +308 -0
  108. package/src/components/ui/form-field/index.ts +2 -0
  109. package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
  110. package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
  111. package/src/components/ui/help-tip/HelpTip.tsx +229 -0
  112. package/src/components/ui/help-tip/index.ts +2 -0
  113. package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
  114. package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
  115. package/src/components/ui/icon-box/IconBox.tsx +57 -0
  116. package/src/components/ui/icon-box/index.ts +1 -0
  117. package/src/components/ui/index.ts +38 -0
  118. package/src/components/ui/indicator/Indicator.stories.tsx +74 -0
  119. package/src/components/ui/indicator/Indicator.test.tsx +55 -0
  120. package/src/components/ui/indicator/Indicator.tsx +73 -0
  121. package/src/components/ui/indicator/index.ts +2 -0
  122. package/src/components/ui/input/Input.stories.tsx +192 -0
  123. package/src/components/ui/input/Input.test.tsx +86 -0
  124. package/src/components/ui/input/Input.tsx +273 -0
  125. package/src/components/ui/input/index.ts +2 -0
  126. package/src/components/ui/link/Link.stories.tsx +78 -0
  127. package/src/components/ui/link/Link.test.tsx +94 -0
  128. package/src/components/ui/link/Link.tsx +86 -0
  129. package/src/components/ui/link/index.ts +2 -0
  130. package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
  131. package/src/components/ui/list-item/ListItem.test.tsx +128 -0
  132. package/src/components/ui/list-item/ListItem.tsx +89 -0
  133. package/src/components/ui/list-item/index.ts +14 -0
  134. package/src/components/ui/menu/Menu.stories.tsx +180 -0
  135. package/src/components/ui/menu/Menu.test.tsx +353 -0
  136. package/src/components/ui/menu/Menu.tsx +222 -0
  137. package/src/components/ui/menu/index.ts +2 -0
  138. package/src/components/ui/modal/Modal.stories.tsx +350 -0
  139. package/src/components/ui/modal/Modal.test.tsx +159 -0
  140. package/src/components/ui/modal/Modal.tsx +254 -0
  141. package/src/components/ui/modal/index.ts +19 -0
  142. package/src/components/ui/pill/Pill.stories.tsx +71 -0
  143. package/src/components/ui/pill/Pill.test.tsx +69 -0
  144. package/src/components/ui/pill/Pill.tsx +104 -0
  145. package/src/components/ui/pill/index.ts +2 -0
  146. package/src/components/ui/popover/Popover.stories.tsx +190 -0
  147. package/src/components/ui/popover/Popover.test.tsx +419 -0
  148. package/src/components/ui/popover/Popover.tsx +237 -0
  149. package/src/components/ui/popover/index.ts +2 -0
  150. package/src/components/ui/progress/Progress.stories.tsx +190 -0
  151. package/src/components/ui/progress/Progress.test.tsx +210 -0
  152. package/src/components/ui/progress/Progress.tsx +339 -0
  153. package/src/components/ui/progress/index.ts +9 -0
  154. package/src/components/ui/radio/Radio.stories.tsx +199 -0
  155. package/src/components/ui/radio/Radio.test.tsx +166 -0
  156. package/src/components/ui/radio/Radio.tsx +200 -0
  157. package/src/components/ui/radio/index.ts +2 -0
  158. package/src/components/ui/section/Section.stories.tsx +111 -0
  159. package/src/components/ui/section/Section.test.tsx +121 -0
  160. package/src/components/ui/section/Section.tsx +52 -0
  161. package/src/components/ui/section/index.ts +6 -0
  162. package/src/components/ui/select/Select.stories.tsx +286 -0
  163. package/src/components/ui/select/Select.test.tsx +200 -0
  164. package/src/components/ui/select/Select.tsx +243 -0
  165. package/src/components/ui/select/index.ts +2 -0
  166. package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
  167. package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
  168. package/src/components/ui/skeleton/Skeleton.tsx +229 -0
  169. package/src/components/ui/skeleton/index.ts +16 -0
  170. package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
  171. package/src/components/ui/spinner/Spinner.test.tsx +67 -0
  172. package/src/components/ui/spinner/Spinner.tsx +72 -0
  173. package/src/components/ui/spinner/index.ts +2 -0
  174. package/src/components/ui/stack/Stack.stories.tsx +127 -0
  175. package/src/components/ui/stack/Stack.test.tsx +184 -0
  176. package/src/components/ui/stack/Stack.tsx +76 -0
  177. package/src/components/ui/stack/index.ts +2 -0
  178. package/src/components/ui/surface/Surface.stories.tsx +91 -0
  179. package/src/components/ui/surface/Surface.test.tsx +63 -0
  180. package/src/components/ui/surface/Surface.tsx +51 -0
  181. package/src/components/ui/surface/index.ts +1 -0
  182. package/src/components/ui/switch/Switch.stories.tsx +111 -0
  183. package/src/components/ui/switch/Switch.test.tsx +112 -0
  184. package/src/components/ui/switch/Switch.tsx +103 -0
  185. package/src/components/ui/switch/index.ts +2 -0
  186. package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
  187. package/src/components/ui/tabs/Tabs.test.tsx +192 -0
  188. package/src/components/ui/tabs/Tabs.tsx +253 -0
  189. package/src/components/ui/tabs/index.ts +16 -0
  190. package/src/components/ui/toast/Toast.stories.tsx +255 -0
  191. package/src/components/ui/toast/Toast.test.tsx +207 -0
  192. package/src/components/ui/toast/Toast.tsx +327 -0
  193. package/src/components/ui/toast/index.ts +9 -0
  194. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
  195. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
  196. package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
  197. package/src/components/ui/toolbar-button/index.ts +7 -0
  198. package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
  199. package/src/components/ui/tooltip/Tooltip.test.tsx +262 -0
  200. package/src/components/ui/tooltip/Tooltip.tsx +234 -0
  201. package/src/components/ui/tooltip/index.ts +2 -0
  202. package/src/index.ts +10 -0
  203. package/src/stories/PresetShowcase.stories.tsx +462 -0
  204. package/src/stories/ThemePresets.stories.tsx +85 -0
  205. package/src/test/setup.ts +19 -0
  206. package/src/theme/Colors.stories.tsx +258 -0
  207. package/src/theme/config.ts +193 -0
  208. package/src/theme/elevation.stories.tsx +346 -0
  209. package/src/theme/elevation.test.ts +27 -0
  210. package/src/theme/elevation.ts +567 -0
  211. package/src/theme/global.css +129 -1
  212. package/src/theme/index.ts +40 -0
  213. package/src/theme/presets/apply.ts +97 -0
  214. package/src/theme/presets/audiobook.ts +93 -0
  215. package/src/theme/presets/default.ts +6 -0
  216. package/src/theme/presets/index.ts +4 -0
  217. package/src/theme/presets/presets.test.ts +51 -0
  218. package/src/theme/presets/types.ts +76 -0
  219. package/src/theme/shadows.stories.tsx +523 -0
  220. package/src/theme/shadows.ts +439 -0
  221. package/src/theme/tokens/index.ts +2 -0
  222. package/src/theme/tokens/primitives.ts +354 -0
  223. package/src/theme/tokens/semantic.ts +381 -0
  224. package/src/types/jest-axe.d.ts +16 -0
  225. package/src/types/nativewind.d.ts +54 -0
  226. package/src/utils/avatar-color.test.ts +30 -0
  227. package/src/utils/avatar-color.ts +20 -0
  228. package/src/utils/cn.ts +14 -0
  229. package/src/utils/colors.ts +140 -0
  230. package/src/utils/form.ts +188 -0
  231. package/src/utils/index.ts +29 -0
  232. package/src/utils/useTheme.ts +72 -0
  233. package/src/web-jsx/jsx-dev-runtime.ts +28 -0
  234. package/src/web-jsx/jsx-runtime.ts +41 -0
  235. package/tailwind.config.js +53 -3
  236. package/dist/chunk-MGW37MPO.mjs.map +0 -1
@@ -0,0 +1,139 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { IconBox } from './IconBox'
4
+
5
+ function StarIcon({ size = 20, className }: { size?: number; className?: string }) {
6
+ return (
7
+ <View
8
+ className={className}
9
+ style={{
10
+ width: size,
11
+ height: size,
12
+ justifyContent: 'center',
13
+ alignItems: 'center',
14
+ }}
15
+ >
16
+ <View
17
+ style={{
18
+ width: size * 0.8,
19
+ height: size * 0.8,
20
+ borderRadius: size * 0.4,
21
+ backgroundColor: 'currentColor',
22
+ }}
23
+ />
24
+ </View>
25
+ )
26
+ }
27
+
28
+ function BoltIcon({ size = 20, className }: { size?: number; className?: string }) {
29
+ return (
30
+ <View
31
+ className={className}
32
+ style={{
33
+ width: size,
34
+ height: size,
35
+ justifyContent: 'center',
36
+ alignItems: 'center',
37
+ }}
38
+ >
39
+ <View
40
+ style={{
41
+ width: 0,
42
+ height: 0,
43
+ borderLeftWidth: size * 0.3,
44
+ borderRightWidth: size * 0.3,
45
+ borderBottomWidth: size * 0.6,
46
+ borderLeftColor: 'transparent',
47
+ borderRightColor: 'transparent',
48
+ borderBottomColor: 'currentColor',
49
+ }}
50
+ />
51
+ </View>
52
+ )
53
+ }
54
+
55
+ const meta: Meta<typeof IconBox> = {
56
+ title: 'Components/IconBox',
57
+ component: IconBox,
58
+ tags: ['autodocs'],
59
+ argTypes: {
60
+ color: {
61
+ control: 'select',
62
+ options: ['primary', 'secondary', 'success', 'error', 'warning', 'info', 'neutral'],
63
+ description: 'Color variant for the icon box',
64
+ },
65
+ size: {
66
+ control: 'select',
67
+ options: ['sm', 'md', 'lg'],
68
+ description: 'Size variant',
69
+ },
70
+ },
71
+ }
72
+
73
+ export default meta
74
+ type Story = StoryObj<typeof IconBox>
75
+
76
+ export const Default: Story = {
77
+ args: {
78
+ icon: StarIcon,
79
+ color: 'neutral',
80
+ size: 'md',
81
+ },
82
+ }
83
+
84
+ export const Primary: Story = {
85
+ args: {
86
+ icon: StarIcon,
87
+ color: 'primary',
88
+ size: 'md',
89
+ },
90
+ }
91
+
92
+ export const Small: Story = {
93
+ args: {
94
+ icon: StarIcon,
95
+ color: 'primary',
96
+ size: 'sm',
97
+ },
98
+ }
99
+
100
+ export const Large: Story = {
101
+ args: {
102
+ icon: StarIcon,
103
+ color: 'primary',
104
+ size: 'lg',
105
+ },
106
+ }
107
+
108
+ export const AllColors: Story = {
109
+ render: () => (
110
+ <View style={{ flexDirection: 'row', gap: 12, flexWrap: 'wrap' }}>
111
+ <IconBox icon={StarIcon} color="primary" />
112
+ <IconBox icon={StarIcon} color="secondary" />
113
+ <IconBox icon={StarIcon} color="success" />
114
+ <IconBox icon={StarIcon} color="error" />
115
+ <IconBox icon={StarIcon} color="warning" />
116
+ <IconBox icon={StarIcon} color="info" />
117
+ <IconBox icon={StarIcon} color="neutral" />
118
+ </View>
119
+ ),
120
+ }
121
+
122
+ export const AllSizes: Story = {
123
+ render: () => (
124
+ <View style={{ flexDirection: 'row', gap: 12, alignItems: 'center' }}>
125
+ <IconBox icon={StarIcon} color="primary" size="sm" />
126
+ <IconBox icon={StarIcon} color="primary" size="md" />
127
+ <IconBox icon={StarIcon} color="primary" size="lg" />
128
+ </View>
129
+ ),
130
+ }
131
+
132
+ export const DifferentIcons: Story = {
133
+ render: () => (
134
+ <View style={{ flexDirection: 'row', gap: 12 }}>
135
+ <IconBox icon={StarIcon} color="success" />
136
+ <IconBox icon={BoltIcon} color="warning" />
137
+ </View>
138
+ ),
139
+ }
@@ -0,0 +1,120 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { IconBox } from './IconBox'
5
+
6
+ function MockIcon({ size, className }: { size?: number; className?: string }) {
7
+ return (
8
+ <span data-testid="icon" data-size={size} className={className}>
9
+ icon
10
+ </span>
11
+ )
12
+ }
13
+
14
+ describe('IconBox', () => {
15
+ it('renders the icon component', () => {
16
+ render(<IconBox icon={MockIcon} />)
17
+ expect(screen.getByTestId('icon')).toBeInTheDocument()
18
+ })
19
+
20
+ it('passes correct default size to icon (md = 20)', () => {
21
+ render(<IconBox icon={MockIcon} />)
22
+ expect(screen.getByTestId('icon')).toHaveAttribute('data-size', '20')
23
+ })
24
+
25
+ it('passes correct size to icon for sm variant', () => {
26
+ render(<IconBox icon={MockIcon} size="sm" />)
27
+ expect(screen.getByTestId('icon')).toHaveAttribute('data-size', '16')
28
+ })
29
+
30
+ it('passes correct size to icon for lg variant', () => {
31
+ render(<IconBox icon={MockIcon} size="lg" />)
32
+ expect(screen.getByTestId('icon')).toHaveAttribute('data-size', '24')
33
+ })
34
+
35
+ it('renders with size sm without crashing', () => {
36
+ const { container } = render(<IconBox icon={MockIcon} size="sm" />)
37
+ expect(container.firstChild).toBeTruthy()
38
+ expect(screen.getByTestId('icon')).toBeInTheDocument()
39
+ })
40
+
41
+ it('renders with size md by default without crashing', () => {
42
+ const { container } = render(<IconBox icon={MockIcon} />)
43
+ expect(container.firstChild).toBeTruthy()
44
+ expect(screen.getByTestId('icon')).toBeInTheDocument()
45
+ })
46
+
47
+ it('renders with size lg without crashing', () => {
48
+ const { container } = render(<IconBox icon={MockIcon} size="lg" />)
49
+ expect(container.firstChild).toBeTruthy()
50
+ expect(screen.getByTestId('icon')).toBeInTheDocument()
51
+ })
52
+
53
+ it('renders with color primary without crashing', () => {
54
+ const { container } = render(<IconBox icon={MockIcon} color="primary" />)
55
+ expect(container.firstChild).toBeTruthy()
56
+ })
57
+
58
+ it('renders with neutral color by default', () => {
59
+ const { container } = render(<IconBox icon={MockIcon} />)
60
+ expect(container.firstChild).toBeTruthy()
61
+ })
62
+
63
+ it('renders with className prop without crashing', () => {
64
+ const { container } = render(<IconBox icon={MockIcon} className="mt-4" />)
65
+ expect(container.firstChild).toBeTruthy()
66
+ })
67
+
68
+ it('passes through testID prop', () => {
69
+ render(<IconBox icon={MockIcon} testID="my-icon-box" />)
70
+ expect(screen.getByTestId('my-icon-box')).toBeInTheDocument()
71
+ })
72
+
73
+ it('supports all color variants without errors', () => {
74
+ const colors = [
75
+ 'primary',
76
+ 'secondary',
77
+ 'success',
78
+ 'error',
79
+ 'warning',
80
+ 'info',
81
+ 'neutral',
82
+ ] as const
83
+ for (const color of colors) {
84
+ const { unmount } = render(<IconBox icon={MockIcon} color={color} />)
85
+ expect(screen.getByTestId('icon')).toBeInTheDocument()
86
+ unmount()
87
+ }
88
+ })
89
+
90
+ it('supports all size variants without errors', () => {
91
+ const sizes = ['sm', 'md', 'lg'] as const
92
+ for (const size of sizes) {
93
+ const { unmount } = render(<IconBox icon={MockIcon} size={size} />)
94
+ expect(screen.getByTestId('icon')).toBeInTheDocument()
95
+ unmount()
96
+ }
97
+ })
98
+
99
+ it('renders with all props combined', () => {
100
+ render(
101
+ <IconBox
102
+ icon={MockIcon}
103
+ color="success"
104
+ size="lg"
105
+ className="mt-2"
106
+ testID="full-icon-box"
107
+ />,
108
+ )
109
+ expect(screen.getByTestId('full-icon-box')).toBeInTheDocument()
110
+ expect(screen.getByTestId('icon')).toHaveAttribute('data-size', '24')
111
+ })
112
+
113
+ describe('accessibility', () => {
114
+ it('has no accessibility violations', async () => {
115
+ const { container } = render(<IconBox icon={MockIcon} color="primary" />)
116
+ const results = await axe(container)
117
+ expect(results).toHaveNoViolations()
118
+ })
119
+ })
120
+ })
@@ -0,0 +1,57 @@
1
+ import React from 'react'
2
+ import { View, type ViewProps } from 'react-native'
3
+ import { cn } from '../../../utils/cn'
4
+
5
+ type IconBoxColor =
6
+ | 'primary'
7
+ | 'secondary'
8
+ | 'success'
9
+ | 'error'
10
+ | 'warning'
11
+ | 'info'
12
+ | 'neutral'
13
+
14
+ type IconBoxSize = 'sm' | 'md' | 'lg'
15
+
16
+ export interface IconBoxProps extends ViewProps {
17
+ icon: React.ComponentType<{ size?: number; className?: string }>
18
+ color?: IconBoxColor
19
+ size?: IconBoxSize
20
+ className?: string
21
+ }
22
+
23
+ const colorClasses: Record<IconBoxColor, { bg: string; icon: string }> = {
24
+ primary: { bg: 'bg-brand-primary/10', icon: 'text-brand-primary' },
25
+ secondary: { bg: 'bg-brand-secondary/10', icon: 'text-brand-secondary' },
26
+ success: { bg: 'bg-status-success/10', icon: 'text-status-success' },
27
+ error: { bg: 'bg-status-error/10', icon: 'text-status-error' },
28
+ warning: { bg: 'bg-status-warning/10', icon: 'text-status-warning' },
29
+ info: { bg: 'bg-status-info/10', icon: 'text-status-info' },
30
+ neutral: { bg: 'bg-surface-elevated', icon: 'text-text-secondary' },
31
+ }
32
+
33
+ const sizeClasses: Record<IconBoxSize, { box: string; iconSize: number }> = {
34
+ sm: { box: 'w-8 h-8 rounded-lg', iconSize: 16 },
35
+ md: { box: 'w-10 h-10 rounded-xl', iconSize: 20 },
36
+ lg: { box: 'w-12 h-12 rounded-xl', iconSize: 24 },
37
+ }
38
+
39
+ export function IconBox({
40
+ icon: Icon,
41
+ color = 'neutral',
42
+ size = 'md',
43
+ className,
44
+ ...props
45
+ }: IconBoxProps) {
46
+ const colors = colorClasses[color]
47
+ const sizes = sizeClasses[size]
48
+
49
+ return (
50
+ <View
51
+ className={cn('items-center justify-center', colors.bg, sizes.box, className)}
52
+ {...props}
53
+ >
54
+ <Icon size={sizes.iconSize} className={colors.icon} />
55
+ </View>
56
+ )
57
+ }
@@ -0,0 +1 @@
1
+ export { IconBox, type IconBoxProps } from './IconBox'
@@ -0,0 +1,38 @@
1
+ // Core UI components
2
+ export * from './alert'
3
+ export * from './button'
4
+ export * from './input'
5
+ export * from './card'
6
+ export * from './badge'
7
+ export * from './spinner'
8
+ export * from './avatar'
9
+ export * from './divider'
10
+ export * from './checkbox'
11
+ export * from './switch'
12
+ export * from './modal'
13
+ export * from './skeleton'
14
+ export * from './tabs'
15
+ export * from './chip'
16
+ export * from './link'
17
+ export * from './tooltip'
18
+ export * from './collapse'
19
+ export * from './breadcrumbs'
20
+ export * from './popover'
21
+ export * from './menu'
22
+ export * from './select'
23
+ export * from './toolbar-button'
24
+ export * from './radio'
25
+ export * from './drawer'
26
+ export * from './progress'
27
+ export * from './toast'
28
+ export * from './autocomplete'
29
+ export * from './help-tip'
30
+ export * from './form-field'
31
+ export * from './stack'
32
+ export * from './section'
33
+ export * from './data-row'
34
+ export * from './icon-box'
35
+ export * from './surface'
36
+ export * from './list-item'
37
+ export * from './indicator'
38
+ export * from './pill'
@@ -0,0 +1,74 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { View } from 'react-native'
3
+ import { Indicator } from './Indicator'
4
+
5
+ const meta: Meta<typeof Indicator> = {
6
+ title: 'Components/Indicator',
7
+ component: Indicator,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ size: { control: 'select', options: ['xs', 'sm', 'md', 'lg'] },
11
+ color: { control: 'select', options: ['default', 'primary', 'success', 'error', 'warning', 'info'] },
12
+ glow: { control: 'boolean' },
13
+ ring: { control: 'boolean' },
14
+ },
15
+ }
16
+ export default meta
17
+ type Story = StoryObj<typeof Indicator>
18
+
19
+ export const Default: Story = { args: { color: 'primary', size: 'md' } }
20
+
21
+ export const AllSizes: Story = {
22
+ render: () => (
23
+ <View className="flex-row gap-4 items-center">
24
+ <Indicator size="xs" color="primary" />
25
+ <Indicator size="sm" color="primary" />
26
+ <Indicator size="md" color="primary" />
27
+ <Indicator size="lg" color="primary" />
28
+ </View>
29
+ ),
30
+ }
31
+
32
+ export const AllColors: Story = {
33
+ render: () => (
34
+ <View className="flex-row gap-4 items-center">
35
+ <Indicator size="md" color="default" />
36
+ <Indicator size="md" color="primary" />
37
+ <Indicator size="md" color="success" />
38
+ <Indicator size="md" color="error" />
39
+ <Indicator size="md" color="warning" />
40
+ <Indicator size="md" color="info" />
41
+ </View>
42
+ ),
43
+ }
44
+
45
+ export const WithGlow: Story = {
46
+ render: () => (
47
+ <View className="flex-row gap-6 items-center p-4 bg-surface-base">
48
+ <Indicator size="md" color="success" glow />
49
+ <Indicator size="md" color="error" glow />
50
+ <Indicator size="md" color="warning" glow />
51
+ <Indicator size="md" color="primary" glow />
52
+ </View>
53
+ ),
54
+ }
55
+
56
+ export const WithRing: Story = {
57
+ render: () => (
58
+ <View className="flex-row gap-4 items-center">
59
+ <Indicator size="md" color="success" ring />
60
+ <Indicator size="md" color="error" ring />
61
+ <Indicator size="lg" color="primary" ring />
62
+ </View>
63
+ ),
64
+ }
65
+
66
+ export const CustomColor: Story = {
67
+ render: () => (
68
+ <View className="flex-row gap-4 items-center">
69
+ <Indicator size="md" customColor="#FF6B6B" />
70
+ <Indicator size="md" customColor="#4ECDC4" />
71
+ <Indicator size="md" customColor="#45B7D1" glow />
72
+ </View>
73
+ ),
74
+ }
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { Indicator } from './Indicator'
5
+
6
+ describe('Indicator', () => {
7
+ it('renders with default props', () => {
8
+ const { container } = render(<Indicator />)
9
+ expect(container.firstChild).toBeInTheDocument()
10
+ })
11
+
12
+ it('renders with all size options', () => {
13
+ const sizes = ['xs', 'sm', 'md', 'lg'] as const
14
+ sizes.forEach((size) => {
15
+ const { unmount } = render(<Indicator size={size} />)
16
+ unmount()
17
+ })
18
+ })
19
+
20
+ it('renders with all color options', () => {
21
+ const colors = ['default', 'primary', 'success', 'error', 'warning', 'info'] as const
22
+ colors.forEach((color) => {
23
+ const { unmount } = render(<Indicator color={color} />)
24
+ unmount()
25
+ })
26
+ })
27
+
28
+ it('applies custom className', () => {
29
+ const { container } = render(<Indicator className="custom-class" />)
30
+ expect(container.firstChild).toBeInTheDocument()
31
+ })
32
+
33
+ it('supports customColor via inline style', () => {
34
+ const { container } = render(<Indicator customColor="#FF0000" />)
35
+ expect(container.firstChild).toBeInTheDocument()
36
+ })
37
+
38
+ it('supports ring prop', () => {
39
+ const { container } = render(<Indicator ring />)
40
+ expect(container.firstChild).toBeInTheDocument()
41
+ })
42
+
43
+ it('supports glow prop', () => {
44
+ const { container } = render(<Indicator glow color="success" />)
45
+ expect(container.firstChild).toBeInTheDocument()
46
+ })
47
+
48
+ describe('accessibility', () => {
49
+ it('has no accessibility violations', async () => {
50
+ const { container } = render(<Indicator color="success" />)
51
+ const results = await axe(container)
52
+ expect(results).toHaveNoViolations()
53
+ })
54
+ })
55
+ })
@@ -0,0 +1,73 @@
1
+ import React from 'react'
2
+ import { View, type ViewProps } from 'react-native'
3
+ import { cn } from '../../../utils/cn'
4
+
5
+ export type IndicatorSize = 'xs' | 'sm' | 'md' | 'lg'
6
+ export type IndicatorColor = 'default' | 'primary' | 'success' | 'error' | 'warning' | 'info'
7
+
8
+ export interface IndicatorProps extends ViewProps {
9
+ /** Dot size */
10
+ size?: IndicatorSize
11
+ /** Semantic color */
12
+ color?: IndicatorColor
13
+ /** Custom hex color (overrides color prop) */
14
+ customColor?: string
15
+ /** Add a glow shadow effect */
16
+ glow?: boolean
17
+ /** Add a ring border */
18
+ ring?: boolean
19
+ /** Additional className */
20
+ className?: string
21
+ }
22
+
23
+ const sizeStyles: Record<IndicatorSize, string> = {
24
+ xs: 'w-1 h-1',
25
+ sm: 'w-1.5 h-1.5',
26
+ md: 'w-2 h-2',
27
+ lg: 'w-2.5 h-2.5',
28
+ }
29
+
30
+ const colorStyles: Record<IndicatorColor, string> = {
31
+ default: 'bg-text-tertiary',
32
+ primary: 'bg-brand-primary',
33
+ success: 'bg-status-success',
34
+ error: 'bg-status-error',
35
+ warning: 'bg-status-warning',
36
+ info: 'bg-status-info',
37
+ }
38
+
39
+ export function Indicator({
40
+ size = 'sm',
41
+ color = 'default',
42
+ customColor,
43
+ glow = false,
44
+ ring = false,
45
+ className,
46
+ style,
47
+ ...props
48
+ }: IndicatorProps) {
49
+ const dynamicStyle = {
50
+ ...(typeof style === 'object' ? style : {}),
51
+ ...(customColor ? { backgroundColor: customColor } : {}),
52
+ ...(glow && !customColor ? {} : {}),
53
+ ...(glow && customColor ? { boxShadow: `0 0 6px ${customColor}` } : {}),
54
+ }
55
+
56
+ return (
57
+ <View
58
+ className={cn(
59
+ 'rounded-full shrink-0',
60
+ sizeStyles[size],
61
+ !customColor && colorStyles[color],
62
+ ring && 'border-2 border-background-base',
63
+ glow && !customColor && color === 'success' && 'shadow-[0_0_6px_rgba(20,184,166,0.6)]',
64
+ glow && !customColor && color === 'error' && 'shadow-[0_0_6px_rgba(239,68,68,0.6)]',
65
+ glow && !customColor && color === 'warning' && 'shadow-[0_0_6px_rgba(245,158,11,0.6)]',
66
+ glow && !customColor && color === 'primary' && 'shadow-[0_0_6px_rgba(255,121,0,0.6)]',
67
+ className
68
+ )}
69
+ style={Object.keys(dynamicStyle).length > 0 ? dynamicStyle : style}
70
+ {...props}
71
+ />
72
+ )
73
+ }
@@ -0,0 +1,2 @@
1
+ export { Indicator } from './Indicator'
2
+ export type { IndicatorProps, IndicatorSize, IndicatorColor } from './Indicator'