@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,297 @@
1
+ import React, { forwardRef } from 'react'
2
+ import { Pressable, Text, View, ActivityIndicator, type PressableProps } from 'react-native'
3
+ import { cn } from '../../../utils/cn'
4
+ import { semanticColorsDark } from '../../../theme/tokens/semantic'
5
+
6
+ export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'link'
7
+ export type ButtonSize = 'sm' | 'md' | 'lg'
8
+ export type ButtonColor = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info'
9
+
10
+ /** Inline color map for RNW where Tailwind text classes get dropped */
11
+ const textColorMap: Record<ButtonVariant, Record<ButtonColor, string>> = {
12
+ solid: {
13
+ primary: '#FFFFFF',
14
+ secondary: '#FFFFFF',
15
+ success: '#FFFFFF',
16
+ error: '#FFFFFF',
17
+ warning: '#FFFFFF',
18
+ info: '#FFFFFF',
19
+ },
20
+ outline: {
21
+ primary: semanticColorsDark['brand-primary'],
22
+ secondary: semanticColorsDark['brand-secondary'],
23
+ success: semanticColorsDark['status-success'],
24
+ error: semanticColorsDark['status-error'],
25
+ warning: semanticColorsDark['status-warning'],
26
+ info: semanticColorsDark['status-info'],
27
+ },
28
+ ghost: {
29
+ primary: semanticColorsDark['brand-primary'],
30
+ secondary: semanticColorsDark['brand-secondary'],
31
+ success: semanticColorsDark['status-success'],
32
+ error: semanticColorsDark['status-error'],
33
+ warning: semanticColorsDark['status-warning'],
34
+ info: semanticColorsDark['status-info'],
35
+ },
36
+ link: {
37
+ primary: semanticColorsDark['brand-primary'],
38
+ secondary: semanticColorsDark['brand-secondary'],
39
+ success: semanticColorsDark['status-success'],
40
+ error: semanticColorsDark['status-error'],
41
+ warning: semanticColorsDark['status-warning'],
42
+ info: semanticColorsDark['status-info'],
43
+ },
44
+ }
45
+
46
+ /** Inline border color map for outline variant */
47
+ const borderColorMap: Record<ButtonColor, string> = {
48
+ primary: semanticColorsDark['brand-primary'],
49
+ secondary: semanticColorsDark['brand-secondary'],
50
+ success: semanticColorsDark['status-success'],
51
+ error: semanticColorsDark['status-error'],
52
+ warning: semanticColorsDark['status-warning'],
53
+ info: semanticColorsDark['status-info'],
54
+ }
55
+
56
+ export interface ButtonProps extends Omit<PressableProps, 'children'> {
57
+ /** Visual style variant */
58
+ variant?: ButtonVariant
59
+ /** Size of the button */
60
+ size?: ButtonSize
61
+ /** Color scheme */
62
+ color?: ButtonColor
63
+ /** Whether this is an icon-only button (square aspect ratio) */
64
+ isIconButton?: boolean
65
+ /** Whether the button is disabled */
66
+ isDisabled?: boolean
67
+ /** Whether the button is in a loading state */
68
+ isLoading?: boolean
69
+ /** Loading text to display */
70
+ loadingText?: string
71
+ /** Whether the button should take full width */
72
+ fullWidth?: boolean
73
+ /** Additional className for styling */
74
+ className?: string
75
+ /** Button content */
76
+ children?: React.ReactNode
77
+ }
78
+
79
+ const variantStyles: Record<ButtonVariant, Record<ButtonColor, string>> = {
80
+ solid: {
81
+ primary: 'bg-brand-primary active:bg-brand-primary-dark active:scale-[0.98] web:hover:bg-brand-primary-dark web:active:scale-[0.98]',
82
+ secondary: 'bg-brand-secondary active:bg-brand-secondary-dark active:scale-[0.98] web:hover:bg-brand-secondary-dark web:active:scale-[0.98]',
83
+ success: 'bg-status-success active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
84
+ error: 'bg-status-error active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
85
+ warning: 'bg-status-warning active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
86
+ info: 'bg-status-info active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
87
+ },
88
+ outline: {
89
+ primary: 'border-2 border-brand-primary bg-transparent active:bg-brand-primary-subtle active:scale-[0.98] web:hover:bg-brand-primary-subtle web:active:scale-[0.98]',
90
+ secondary: 'border-2 border-brand-secondary bg-transparent active:bg-brand-secondary-subtle active:scale-[0.98] web:hover:bg-brand-secondary-subtle web:active:scale-[0.98]',
91
+ success: 'border-2 border-status-success bg-transparent active:bg-status-success-subtle active:scale-[0.98] web:hover:bg-status-success-subtle web:active:scale-[0.98]',
92
+ error: 'border-2 border-status-error bg-transparent active:bg-status-error-subtle active:scale-[0.98] web:hover:bg-status-error-subtle web:active:scale-[0.98]',
93
+ warning: 'border-2 border-status-warning bg-transparent active:bg-status-warning-subtle active:scale-[0.98] web:hover:bg-status-warning-subtle web:active:scale-[0.98]',
94
+ info: 'border-2 border-status-info bg-transparent active:bg-status-info-subtle active:scale-[0.98] web:hover:bg-status-info-subtle web:active:scale-[0.98]',
95
+ },
96
+ ghost: {
97
+ primary: 'bg-transparent active:bg-brand-primary-subtle active:scale-[0.98] web:hover:bg-brand-primary-subtle web:active:scale-[0.98]',
98
+ secondary: 'bg-transparent active:bg-brand-secondary-subtle active:scale-[0.98] web:hover:bg-brand-secondary-subtle web:active:scale-[0.98]',
99
+ success: 'bg-transparent active:bg-status-success-subtle active:scale-[0.98] web:hover:bg-status-success-subtle web:active:scale-[0.98]',
100
+ error: 'bg-transparent active:bg-status-error-subtle active:scale-[0.98] web:hover:bg-status-error-subtle web:active:scale-[0.98]',
101
+ warning: 'bg-transparent active:bg-status-warning-subtle active:scale-[0.98] web:hover:bg-status-warning-subtle web:active:scale-[0.98]',
102
+ info: 'bg-transparent active:bg-status-info-subtle active:scale-[0.98] web:hover:bg-status-info-subtle web:active:scale-[0.98]',
103
+ },
104
+ link: {
105
+ primary: 'bg-transparent',
106
+ secondary: 'bg-transparent',
107
+ success: 'bg-transparent',
108
+ error: 'bg-transparent',
109
+ warning: 'bg-transparent',
110
+ info: 'bg-transparent',
111
+ },
112
+ }
113
+
114
+ const textStyles: Record<ButtonVariant, Record<ButtonColor, string>> = {
115
+ solid: {
116
+ primary: 'text-white',
117
+ secondary: 'text-white',
118
+ success: 'text-white',
119
+ error: 'text-white',
120
+ warning: 'text-white',
121
+ info: 'text-white',
122
+ },
123
+ outline: {
124
+ primary: 'text-brand-primary',
125
+ secondary: 'text-brand-secondary',
126
+ success: 'text-status-success',
127
+ error: 'text-status-error',
128
+ warning: 'text-status-warning',
129
+ info: 'text-status-info',
130
+ },
131
+ ghost: {
132
+ primary: 'text-brand-primary',
133
+ secondary: 'text-brand-secondary',
134
+ success: 'text-status-success',
135
+ error: 'text-status-error',
136
+ warning: 'text-status-warning',
137
+ info: 'text-status-info',
138
+ },
139
+ link: {
140
+ primary: 'text-brand-primary web:hover:underline',
141
+ secondary: 'text-brand-secondary web:hover:underline',
142
+ success: 'text-status-success web:hover:underline',
143
+ error: 'text-status-error web:hover:underline',
144
+ warning: 'text-status-warning web:hover:underline',
145
+ info: 'text-status-info web:hover:underline',
146
+ },
147
+ }
148
+
149
+ const sizeStyles: Record<ButtonSize, string> = {
150
+ sm: 'px-4 py-1.5 min-h-[32px]',
151
+ md: 'px-5 py-2 min-h-[40px]',
152
+ lg: 'px-6 py-2.5 min-h-[48px]',
153
+ }
154
+
155
+ // Icon button sizes (square aspect ratio)
156
+ const iconButtonSizeStyles: Record<ButtonSize, string> = {
157
+ sm: 'w-8 h-8 p-0',
158
+ md: 'w-10 h-10 p-0',
159
+ lg: 'w-12 h-12 p-0',
160
+ }
161
+
162
+ const textSizeStyles: Record<ButtonSize, string> = {
163
+ sm: 'text-sm',
164
+ md: 'text-sm',
165
+ lg: 'text-base',
166
+ }
167
+
168
+ /**
169
+ * Button component following compound component pattern.
170
+ *
171
+ * @example
172
+ * <Button variant="solid" color="primary">
173
+ * <ButtonText>Click me</ButtonText>
174
+ * </Button>
175
+ *
176
+ * // Or with icon
177
+ * <Button variant="outline" color="secondary">
178
+ * <ButtonIcon as={PlusIcon} />
179
+ * <ButtonText>Add item</ButtonText>
180
+ * </Button>
181
+ */
182
+ export const Button = forwardRef<View, ButtonProps>(function Button(
183
+ {
184
+ variant = 'solid',
185
+ size = 'md',
186
+ color = 'primary',
187
+ isIconButton = false,
188
+ isDisabled = false,
189
+ isLoading = false,
190
+ loadingText,
191
+ fullWidth = false,
192
+ className,
193
+ children,
194
+ ...props
195
+ },
196
+ ref
197
+ ) {
198
+ const disabled = isDisabled || isLoading
199
+
200
+ const inlineStyle: Record<string, string> = {
201
+ color: textColorMap[variant][color],
202
+ }
203
+ if (variant === 'outline') {
204
+ inlineStyle.borderColor = borderColorMap[color]
205
+ }
206
+
207
+ return (
208
+ <Pressable
209
+ ref={ref}
210
+ disabled={disabled}
211
+ accessibilityRole="button"
212
+ accessibilityState={{ disabled }}
213
+ className={cn(
214
+ // Base styles
215
+ 'flex-row items-center justify-center rounded-md transition-all duration-150',
216
+ // Variant + color styles
217
+ variantStyles[variant][color],
218
+ // Size styles (icon button vs regular)
219
+ isIconButton ? iconButtonSizeStyles[size] : sizeStyles[size],
220
+ // Full width
221
+ fullWidth && 'w-full',
222
+ // Disabled styles - more refined
223
+ disabled && 'opacity-40 cursor-not-allowed active:scale-100 web:active:scale-100',
224
+ // Link variant has no padding
225
+ variant === 'link' && 'px-0 py-0 min-h-0',
226
+ className
227
+ )}
228
+ style={inlineStyle}
229
+ {...props}
230
+ >
231
+ {isLoading && (
232
+ <ActivityIndicator
233
+ size="small"
234
+ color={variant === 'solid' ? semanticColorsDark['on-brand-primary'] : undefined}
235
+ className="mr-2"
236
+ />
237
+ )}
238
+ {isLoading && loadingText ? (
239
+ <Text
240
+ className={cn(
241
+ 'font-semibold',
242
+ textSizeStyles[size],
243
+ textStyles[variant][color]
244
+ )}
245
+ style={{ color: textColorMap[variant][color] }}
246
+ >
247
+ {loadingText}
248
+ </Text>
249
+ ) : (
250
+ children
251
+ )}
252
+ </Pressable>
253
+ )
254
+ })
255
+
256
+ export interface ButtonTextProps {
257
+ children: React.ReactNode
258
+ className?: string
259
+ }
260
+
261
+ /**
262
+ * Text component for Button. Use inside Button for consistent styling.
263
+ */
264
+ export function ButtonText({ children, className }: ButtonTextProps) {
265
+ return (
266
+ <Text className={cn('font-semibold text-inherit', className)}>
267
+ {children}
268
+ </Text>
269
+ )
270
+ }
271
+
272
+ export interface ButtonIconProps {
273
+ /** Icon component to render */
274
+ as: React.ComponentType<{ size?: number; color?: string; className?: string }>
275
+ /** Icon size */
276
+ size?: number
277
+ /** Additional className */
278
+ className?: string
279
+ }
280
+
281
+ /**
282
+ * Icon component for Button. Use inside Button for icons.
283
+ */
284
+ export function ButtonIcon({ as: Icon, size = 16, className }: ButtonIconProps) {
285
+ return <Icon size={size} className={cn('text-inherit', className)} />
286
+ }
287
+
288
+ export interface ButtonSpinnerProps {
289
+ className?: string
290
+ }
291
+
292
+ /**
293
+ * Spinner component for Button loading state.
294
+ */
295
+ export function ButtonSpinner({ className }: ButtonSpinnerProps) {
296
+ return <ActivityIndicator size="small" className={cn('text-inherit', className)} />
297
+ }
@@ -0,0 +1,10 @@
1
+ export { Button, ButtonText, ButtonIcon, ButtonSpinner } from './Button'
2
+ export type {
3
+ ButtonProps,
4
+ ButtonTextProps,
5
+ ButtonIconProps,
6
+ ButtonSpinnerProps,
7
+ ButtonVariant,
8
+ ButtonSize,
9
+ ButtonColor,
10
+ } from './Button'