@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,339 @@
1
+ import React from 'react'
2
+ import { View, Text, type ViewProps } from 'react-native'
3
+ import { cn } from '../../../utils/cn'
4
+
5
+ export type ProgressSize = 'sm' | 'md' | 'lg'
6
+ export type ProgressColor = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info'
7
+ export type ProgressVariant = 'linear' | 'circular'
8
+
9
+ /** Maps the color prop to a CSS variable reference for inline styles */
10
+ const colorVarMap: Record<ProgressColor, string> = {
11
+ primary: 'var(--color-brand-primary)',
12
+ secondary: 'var(--color-brand-secondary)',
13
+ success: 'var(--color-status-success)',
14
+ error: 'var(--color-status-error)',
15
+ warning: 'var(--color-status-warning)',
16
+ info: 'var(--color-status-info)',
17
+ }
18
+
19
+ export interface ProgressProps extends ViewProps {
20
+ /** Current value (0-100) */
21
+ value?: number
22
+ /** Maximum value */
23
+ max?: number
24
+ /** Whether the progress is indeterminate */
25
+ isIndeterminate?: boolean
26
+ /** Size of the progress bar */
27
+ size?: ProgressSize
28
+ /** Color of the progress */
29
+ color?: ProgressColor
30
+ /** Whether to show the value label */
31
+ showValue?: boolean
32
+ /** Custom value format function */
33
+ formatValue?: (value: number, max: number) => string
34
+ /** Label text */
35
+ label?: string
36
+ /** Fixed track width in pixels (default: full width) */
37
+ trackWidth?: number
38
+ /** Custom hex color override for the fill bar */
39
+ customColor?: string
40
+ /** Additional className */
41
+ className?: string
42
+ }
43
+
44
+ const sizeStyles: Record<ProgressSize, string> = {
45
+ sm: 'h-1',
46
+ md: 'h-2',
47
+ lg: 'h-3',
48
+ }
49
+
50
+ const colorStyles: Record<ProgressColor, string> = {
51
+ primary: 'bg-brand-primary',
52
+ secondary: 'bg-brand-secondary',
53
+ success: 'bg-status-success',
54
+ error: 'bg-status-error',
55
+ warning: 'bg-status-warning',
56
+ info: 'bg-status-info',
57
+ }
58
+
59
+ const trackColorStyles: Record<ProgressColor, string> = {
60
+ primary: 'bg-brand-primary/20',
61
+ secondary: 'bg-brand-secondary/20',
62
+ success: 'bg-status-success/20',
63
+ error: 'bg-status-error/20',
64
+ warning: 'bg-status-warning/20',
65
+ info: 'bg-status-info/20',
66
+ }
67
+
68
+ /**
69
+ * Linear progress bar component.
70
+ *
71
+ * @example
72
+ * // Determinate progress
73
+ * <Progress value={75} />
74
+ *
75
+ * @example
76
+ * // With label and value
77
+ * <Progress value={45} label="Uploading..." showValue />
78
+ *
79
+ * @example
80
+ * // Indeterminate progress
81
+ * <Progress isIndeterminate />
82
+ */
83
+ export function Progress({
84
+ value = 0,
85
+ max = 100,
86
+ isIndeterminate = false,
87
+ size = 'md',
88
+ color = 'primary',
89
+ showValue = false,
90
+ formatValue = (v, m) => `${Math.round((v / m) * 100)}%`,
91
+ label,
92
+ trackWidth,
93
+ customColor,
94
+ className,
95
+ ...props
96
+ }: ProgressProps) {
97
+ const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
98
+
99
+ return (
100
+ <View className={cn('w-full', className)} {...props}>
101
+ {/* Label and Value Row */}
102
+ {(label || showValue) && (
103
+ <View className="flex-row justify-between items-center mb-1">
104
+ {label && (
105
+ <Text className="text-sm text-text-secondary">{label}</Text>
106
+ )}
107
+ {showValue && !isIndeterminate && (
108
+ <Text className="text-sm font-medium text-text-primary">
109
+ {formatValue(value, max)}
110
+ </Text>
111
+ )}
112
+ </View>
113
+ )}
114
+
115
+ {/* Progress Track */}
116
+ <View
117
+ className={cn(
118
+ 'rounded-full overflow-hidden',
119
+ trackWidth ? undefined : 'w-full',
120
+ trackColorStyles[color],
121
+ sizeStyles[size]
122
+ )}
123
+ style={trackWidth ? { width: trackWidth } : undefined}
124
+ accessibilityRole="progressbar"
125
+ accessibilityValue={{
126
+ min: 0,
127
+ max: max,
128
+ now: isIndeterminate ? undefined : value,
129
+ }}
130
+ >
131
+ {/* Progress Fill */}
132
+ <View
133
+ className={cn(
134
+ 'h-full rounded-full',
135
+ !customColor && colorStyles[color],
136
+ isIndeterminate && 'animate-pulse w-1/3'
137
+ )}
138
+ style={{
139
+ ...(isIndeterminate ? {} : { width: `${percentage}%` }),
140
+ ...(customColor ? { backgroundColor: customColor } : {}),
141
+ }}
142
+ />
143
+ </View>
144
+ </View>
145
+ )
146
+ }
147
+
148
+ export interface CircularProgressProps extends ViewProps {
149
+ /** Current value (0-100) */
150
+ value?: number
151
+ /** Maximum value */
152
+ max?: number
153
+ /** Whether the progress is indeterminate */
154
+ isIndeterminate?: boolean
155
+ /** Size of the progress circle (diameter) */
156
+ size?: number
157
+ /** Stroke width */
158
+ strokeWidth?: number
159
+ /** Color of the progress */
160
+ color?: ProgressColor
161
+ /** Whether to show the value in center */
162
+ showValue?: boolean
163
+ /** Custom value format function */
164
+ formatValue?: (value: number, max: number) => string
165
+ /** Additional className */
166
+ className?: string
167
+ }
168
+
169
+ /**
170
+ * Circular progress indicator component.
171
+ *
172
+ * @example
173
+ * <CircularProgress value={75} showValue />
174
+ */
175
+ export function CircularProgress({
176
+ value = 0,
177
+ max = 100,
178
+ isIndeterminate = false,
179
+ size = 48,
180
+ strokeWidth = 4,
181
+ color = 'primary',
182
+ showValue = false,
183
+ formatValue = (v, m) => `${Math.round((v / m) * 100)}%`,
184
+ className,
185
+ ...props
186
+ }: CircularProgressProps) {
187
+ const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
188
+ const radius = (size - strokeWidth) / 2
189
+ const circumference = 2 * Math.PI * radius
190
+ const strokeDashoffset = circumference - (percentage / 100) * circumference
191
+ const center = size / 2
192
+
193
+ return (
194
+ <View
195
+ className={cn('items-center justify-center', className)}
196
+ style={{ width: size, height: size }}
197
+ accessibilityRole="progressbar"
198
+ accessibilityValue={{
199
+ min: 0,
200
+ max: max,
201
+ now: isIndeterminate ? undefined : value,
202
+ }}
203
+ {...props}
204
+ >
205
+ {/* SVG implementation for web */}
206
+ <View
207
+ className={cn(isIndeterminate && 'animate-spin')}
208
+ style={{ width: size, height: size }}
209
+ >
210
+ <svg
211
+ width={size}
212
+ height={size}
213
+ viewBox={`0 0 ${size} ${size}`}
214
+ style={{ position: 'absolute' }}
215
+ >
216
+ {/* Background track */}
217
+ <circle
218
+ cx={center}
219
+ cy={center}
220
+ r={radius}
221
+ fill="none"
222
+ stroke="var(--color-border-default)"
223
+ strokeWidth={strokeWidth}
224
+ />
225
+ {/* Progress arc */}
226
+ <circle
227
+ cx={center}
228
+ cy={center}
229
+ r={radius}
230
+ fill="none"
231
+ stroke={colorVarMap[color]}
232
+ strokeWidth={strokeWidth}
233
+ strokeLinecap="round"
234
+ strokeDasharray={circumference}
235
+ strokeDashoffset={isIndeterminate ? circumference * 0.75 : strokeDashoffset}
236
+ transform={`rotate(-90 ${center} ${center})`}
237
+ style={{ transition: 'stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)' }}
238
+ />
239
+ </svg>
240
+ </View>
241
+
242
+ {/* Center value */}
243
+ {showValue && !isIndeterminate && (
244
+ <Text
245
+ className="text-xs font-semibold text-text-primary absolute"
246
+ style={{ fontSize: Math.max(size * 0.22, 10) }}
247
+ >
248
+ {formatValue(value, max)}
249
+ </Text>
250
+ )}
251
+ </View>
252
+ )
253
+ }
254
+
255
+ export interface ProgressStepsProps extends ViewProps {
256
+ /** Current step (0-indexed) */
257
+ currentStep: number
258
+ /** Total number of steps */
259
+ totalSteps: number
260
+ /** Color of completed steps */
261
+ color?: ProgressColor
262
+ /** Size of step indicators */
263
+ size?: ProgressSize
264
+ /** Labels for each step */
265
+ labels?: string[]
266
+ /** Additional className */
267
+ className?: string
268
+ }
269
+
270
+ const stepSizeStyles: Record<ProgressSize, { dot: string; connector: string }> = {
271
+ sm: { dot: 'w-2 h-2', connector: 'h-0.5' },
272
+ md: { dot: 'w-3 h-3', connector: 'h-1' },
273
+ lg: { dot: 'w-4 h-4', connector: 'h-1.5' },
274
+ }
275
+
276
+ /**
277
+ * Step-based progress indicator.
278
+ *
279
+ * @example
280
+ * <ProgressSteps currentStep={2} totalSteps={4} labels={['Cart', 'Shipping', 'Payment', 'Done']} />
281
+ */
282
+ export function ProgressSteps({
283
+ currentStep,
284
+ totalSteps,
285
+ color = 'primary',
286
+ size = 'md',
287
+ labels,
288
+ className,
289
+ ...props
290
+ }: ProgressStepsProps) {
291
+ const { dot, connector } = stepSizeStyles[size]
292
+
293
+ return (
294
+ <View className={cn('w-full', className)} {...props}>
295
+ <View className="flex-row items-center">
296
+ {Array.from({ length: totalSteps }).map((_, index) => (
297
+ <React.Fragment key={index}>
298
+ {/* Step dot */}
299
+ <View
300
+ className={cn(
301
+ 'rounded-full',
302
+ dot,
303
+ index <= currentStep ? colorStyles[color] : 'bg-border-strong'
304
+ )}
305
+ />
306
+
307
+ {/* Connector line */}
308
+ {index < totalSteps - 1 && (
309
+ <View
310
+ className={cn(
311
+ 'flex-1 mx-1',
312
+ connector,
313
+ index < currentStep ? colorStyles[color] : 'bg-border-strong'
314
+ )}
315
+ />
316
+ )}
317
+ </React.Fragment>
318
+ ))}
319
+ </View>
320
+
321
+ {/* Labels */}
322
+ {labels && labels.length > 0 && (
323
+ <View className="flex-row mt-2">
324
+ {labels.map((label, index) => (
325
+ <Text
326
+ key={index}
327
+ className={cn(
328
+ 'flex-1 text-xs text-center',
329
+ index <= currentStep ? 'text-text-primary font-medium' : 'text-text-tertiary'
330
+ )}
331
+ >
332
+ {label}
333
+ </Text>
334
+ ))}
335
+ </View>
336
+ )}
337
+ </View>
338
+ )
339
+ }
@@ -0,0 +1,9 @@
1
+ export { Progress, CircularProgress, ProgressSteps } from './Progress'
2
+ export type {
3
+ ProgressProps,
4
+ CircularProgressProps,
5
+ ProgressStepsProps,
6
+ ProgressSize,
7
+ ProgressColor,
8
+ ProgressVariant,
9
+ } from './Progress'
@@ -0,0 +1,199 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { useState } from 'react'
3
+ import { View, Text } from 'react-native'
4
+ import { Radio, RadioGroup } from './Radio'
5
+
6
+ const meta: Meta<typeof RadioGroup> = {
7
+ title: 'Components/Radio',
8
+ component: RadioGroup,
9
+ tags: ['autodocs'],
10
+ argTypes: {
11
+ size: {
12
+ control: 'select',
13
+ options: ['sm', 'md', 'lg'],
14
+ },
15
+ color: {
16
+ control: 'select',
17
+ options: ['primary', 'secondary', 'success', 'error'],
18
+ },
19
+ orientation: {
20
+ control: 'select',
21
+ options: ['horizontal', 'vertical'],
22
+ },
23
+ isDisabled: {
24
+ control: 'boolean',
25
+ },
26
+ },
27
+ }
28
+
29
+ export default meta
30
+ type Story = StoryObj<typeof RadioGroup>
31
+
32
+ export const Default: Story = {
33
+ render: function Render() {
34
+ const [value, setValue] = useState<string | null>('option1')
35
+ return (
36
+ <RadioGroup value={value} onChange={setValue}>
37
+ <Radio value="option1">Option 1</Radio>
38
+ <Radio value="option2">Option 2</Radio>
39
+ <Radio value="option3">Option 3</Radio>
40
+ </RadioGroup>
41
+ )
42
+ },
43
+ }
44
+
45
+ export const WithLabel: Story = {
46
+ render: function Render() {
47
+ const [value, setValue] = useState<string | null>('email')
48
+ return (
49
+ <RadioGroup value={value} onChange={setValue} label="Notification Preference">
50
+ <Radio value="email">Email</Radio>
51
+ <Radio value="sms">SMS</Radio>
52
+ <Radio value="push">Push Notification</Radio>
53
+ <Radio value="none">None</Radio>
54
+ </RadioGroup>
55
+ )
56
+ },
57
+ }
58
+
59
+ export const Horizontal: Story = {
60
+ render: function Render() {
61
+ const [value, setValue] = useState<string | null>('monthly')
62
+ return (
63
+ <RadioGroup
64
+ value={value}
65
+ onChange={setValue}
66
+ orientation="horizontal"
67
+ label="Billing Cycle"
68
+ >
69
+ <Radio value="monthly">Monthly</Radio>
70
+ <Radio value="quarterly">Quarterly</Radio>
71
+ <Radio value="yearly">Yearly</Radio>
72
+ </RadioGroup>
73
+ )
74
+ },
75
+ }
76
+
77
+ export const AllSizes: Story = {
78
+ render: function Render() {
79
+ const [small, setSmall] = useState<string | null>('a')
80
+ const [medium, setMedium] = useState<string | null>('a')
81
+ const [large, setLarge] = useState<string | null>('a')
82
+
83
+ return (
84
+ <View className="gap-6">
85
+ <View>
86
+ <Text className="text-text-secondary text-sm mb-2">Small</Text>
87
+ <RadioGroup value={small} onChange={setSmall} size="sm" orientation="horizontal">
88
+ <Radio value="a">Option A</Radio>
89
+ <Radio value="b">Option B</Radio>
90
+ </RadioGroup>
91
+ </View>
92
+ <View>
93
+ <Text className="text-text-secondary text-sm mb-2">Medium (default)</Text>
94
+ <RadioGroup value={medium} onChange={setMedium} size="md" orientation="horizontal">
95
+ <Radio value="a">Option A</Radio>
96
+ <Radio value="b">Option B</Radio>
97
+ </RadioGroup>
98
+ </View>
99
+ <View>
100
+ <Text className="text-text-secondary text-sm mb-2">Large</Text>
101
+ <RadioGroup value={large} onChange={setLarge} size="lg" orientation="horizontal">
102
+ <Radio value="a">Option A</Radio>
103
+ <Radio value="b">Option B</Radio>
104
+ </RadioGroup>
105
+ </View>
106
+ </View>
107
+ )
108
+ },
109
+ }
110
+
111
+ export const AllColors: Story = {
112
+ render: function Render() {
113
+ const [primary, setPrimary] = useState<string | null>('a')
114
+ const [secondary, setSecondary] = useState<string | null>('a')
115
+ const [success, setSuccess] = useState<string | null>('a')
116
+ const [error, setError] = useState<string | null>('a')
117
+
118
+ return (
119
+ <View className="gap-6">
120
+ <RadioGroup value={primary} onChange={setPrimary} color="primary" orientation="horizontal" label="Primary">
121
+ <Radio value="a">Option A</Radio>
122
+ <Radio value="b">Option B</Radio>
123
+ </RadioGroup>
124
+ <RadioGroup value={secondary} onChange={setSecondary} color="secondary" orientation="horizontal" label="Secondary">
125
+ <Radio value="a">Option A</Radio>
126
+ <Radio value="b">Option B</Radio>
127
+ </RadioGroup>
128
+ <RadioGroup value={success} onChange={setSuccess} color="success" orientation="horizontal" label="Success">
129
+ <Radio value="a">Option A</Radio>
130
+ <Radio value="b">Option B</Radio>
131
+ </RadioGroup>
132
+ <RadioGroup value={error} onChange={setError} color="error" orientation="horizontal" label="Error">
133
+ <Radio value="a">Option A</Radio>
134
+ <Radio value="b">Option B</Radio>
135
+ </RadioGroup>
136
+ </View>
137
+ )
138
+ },
139
+ }
140
+
141
+ export const Disabled: Story = {
142
+ render: function Render() {
143
+ const [value, setValue] = useState<string | null>('option1')
144
+ return (
145
+ <View className="gap-6">
146
+ <View>
147
+ <Text className="text-text-secondary text-sm mb-2">Entire group disabled</Text>
148
+ <RadioGroup value={value} onChange={setValue} isDisabled>
149
+ <Radio value="option1">Option 1</Radio>
150
+ <Radio value="option2">Option 2</Radio>
151
+ </RadioGroup>
152
+ </View>
153
+ <View>
154
+ <Text className="text-text-secondary text-sm mb-2">Individual option disabled</Text>
155
+ <RadioGroup value={value} onChange={setValue}>
156
+ <Radio value="option1">Option 1 (enabled)</Radio>
157
+ <Radio value="option2" isDisabled>Option 2 (disabled)</Radio>
158
+ <Radio value="option3">Option 3 (enabled)</Radio>
159
+ </RadioGroup>
160
+ </View>
161
+ </View>
162
+ )
163
+ },
164
+ }
165
+
166
+ export const FormExample: Story = {
167
+ render: function Render() {
168
+ const [plan, setPlan] = useState<string | null>('pro')
169
+ const [billing, setBilling] = useState<string | null>('monthly')
170
+
171
+ return (
172
+ <View className="gap-6 p-4 bg-surface-base rounded-lg max-w-md">
173
+ <Text className="text-lg font-semibold text-text-primary">Subscription Settings</Text>
174
+
175
+ <RadioGroup value={plan} onChange={setPlan} label="Select Plan">
176
+ <Radio value="free">Free - Basic features</Radio>
177
+ <Radio value="pro">Pro - Advanced features</Radio>
178
+ <Radio value="enterprise">Enterprise - Custom solutions</Radio>
179
+ </RadioGroup>
180
+
181
+ <RadioGroup
182
+ value={billing}
183
+ onChange={setBilling}
184
+ orientation="horizontal"
185
+ label="Billing Frequency"
186
+ >
187
+ <Radio value="monthly">Monthly</Radio>
188
+ <Radio value="yearly">Yearly (save 20%)</Radio>
189
+ </RadioGroup>
190
+
191
+ <View className="pt-4 border-t border-border-default">
192
+ <Text className="text-sm text-text-secondary">
193
+ Selected: {plan} plan, billed {billing}
194
+ </Text>
195
+ </View>
196
+ </View>
197
+ )
198
+ },
199
+ }