@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,188 @@
1
+ /**
2
+ * Form utility functions for consistent form field handling
3
+ */
4
+
5
+ import React from 'react'
6
+
7
+ export interface FieldState {
8
+ /** Whether the field has been touched */
9
+ touched: boolean
10
+ /** Whether the field has an error */
11
+ hasError: boolean
12
+ /** Error message if any */
13
+ errorMessage?: string
14
+ /** Whether the field is required */
15
+ isRequired?: boolean
16
+ }
17
+
18
+ export interface FieldWrapperProps {
19
+ /** Field label */
20
+ label?: string
21
+ /** Helper text */
22
+ helperText?: string
23
+ /** Error message */
24
+ errorMessage?: string
25
+ /** Whether the field is required */
26
+ isRequired?: boolean
27
+ /** Whether the field is disabled */
28
+ isDisabled?: boolean
29
+ /** Whether to show error state */
30
+ isInvalid?: boolean
31
+ }
32
+
33
+ /**
34
+ * Get validation state props from field state
35
+ */
36
+ export function getFieldValidationProps(state: FieldState): {
37
+ isInvalid: boolean
38
+ errorMessage?: string
39
+ } {
40
+ return {
41
+ isInvalid: state.touched && state.hasError,
42
+ errorMessage: state.touched && state.hasError ? state.errorMessage : undefined,
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Create a field ID from label text
48
+ */
49
+ export function createFieldId(label: string): string {
50
+ return label
51
+ .toLowerCase()
52
+ .replace(/[^a-z0-9]+/g, '-')
53
+ .replace(/(^-|-$)/g, '')
54
+ }
55
+
56
+ /**
57
+ * Get aria attributes for form field
58
+ */
59
+ export function getFieldAriaProps(props: FieldWrapperProps & { id: string }): {
60
+ 'aria-invalid'?: boolean
61
+ 'aria-required'?: boolean
62
+ 'aria-describedby'?: string
63
+ } {
64
+ const describedBy: string[] = []
65
+
66
+ if (props.helperText) {
67
+ describedBy.push(`${props.id}-helper`)
68
+ }
69
+
70
+ if (props.isInvalid && props.errorMessage) {
71
+ describedBy.push(`${props.id}-error`)
72
+ }
73
+
74
+ return {
75
+ 'aria-invalid': props.isInvalid || undefined,
76
+ 'aria-required': props.isRequired || undefined,
77
+ 'aria-describedby': describedBy.length > 0 ? describedBy.join(' ') : undefined,
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Validate email format
83
+ */
84
+ export function isValidEmail(email: string): boolean {
85
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
86
+ return emailRegex.test(email)
87
+ }
88
+
89
+ /**
90
+ * Validate minimum length
91
+ */
92
+ export function hasMinLength(value: string, minLength: number): boolean {
93
+ return value.length >= minLength
94
+ }
95
+
96
+ /**
97
+ * Validate maximum length
98
+ */
99
+ export function hasMaxLength(value: string, maxLength: number): boolean {
100
+ return value.length <= maxLength
101
+ }
102
+
103
+ /**
104
+ * Check if value is empty (null, undefined, or empty string)
105
+ */
106
+ export function isEmpty(value: unknown): boolean {
107
+ if (value === null || value === undefined) return true
108
+ if (typeof value === 'string') return value.trim() === ''
109
+ if (Array.isArray(value)) return value.length === 0
110
+ return false
111
+ }
112
+
113
+ /**
114
+ * Common validation rules
115
+ */
116
+ export const validationRules = {
117
+ required: (value: unknown) => {
118
+ if (isEmpty(value)) {
119
+ return 'This field is required'
120
+ }
121
+ return undefined
122
+ },
123
+
124
+ email: (value: string) => {
125
+ if (value && !isValidEmail(value)) {
126
+ return 'Please enter a valid email address'
127
+ }
128
+ return undefined
129
+ },
130
+
131
+ minLength: (min: number) => (value: string) => {
132
+ if (value && !hasMinLength(value, min)) {
133
+ return `Must be at least ${min} characters`
134
+ }
135
+ return undefined
136
+ },
137
+
138
+ maxLength: (max: number) => (value: string) => {
139
+ if (value && !hasMaxLength(value, max)) {
140
+ return `Must be no more than ${max} characters`
141
+ }
142
+ return undefined
143
+ },
144
+
145
+ pattern: (regex: RegExp, message: string) => (value: string) => {
146
+ if (value && !regex.test(value)) {
147
+ return message
148
+ }
149
+ return undefined
150
+ },
151
+
152
+ match: (otherValue: string, fieldName: string) => (value: string) => {
153
+ if (value !== otherValue) {
154
+ return `Must match ${fieldName}`
155
+ }
156
+ return undefined
157
+ },
158
+ }
159
+
160
+ /**
161
+ * Compose multiple validation functions
162
+ */
163
+ export function composeValidators(
164
+ ...validators: Array<(value: any) => string | undefined>
165
+ ) {
166
+ return (value: any): string | undefined => {
167
+ for (const validator of validators) {
168
+ const error = validator(value)
169
+ if (error) return error
170
+ }
171
+ return undefined
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Type for form values
177
+ */
178
+ export type FormValues = Record<string, unknown>
179
+
180
+ /**
181
+ * Type for form errors
182
+ */
183
+ export type FormErrors<T extends FormValues> = Partial<Record<keyof T, string>>
184
+
185
+ /**
186
+ * Type for form touched state
187
+ */
188
+ export type FormTouched<T extends FormValues> = Partial<Record<keyof T, boolean>>
@@ -0,0 +1,29 @@
1
+ export { cn } from './cn'
2
+ export {
3
+ getStatusColor,
4
+ getResultColor,
5
+ getContrastText,
6
+ getLuminance,
7
+ alpha,
8
+ lighten,
9
+ darken,
10
+ } from './colors'
11
+ export type { StatusType, ResultType } from './colors'
12
+ export {
13
+ getFieldValidationProps,
14
+ createFieldId,
15
+ getFieldAriaProps,
16
+ isValidEmail,
17
+ hasMinLength,
18
+ hasMaxLength,
19
+ isEmpty,
20
+ validationRules,
21
+ composeValidators,
22
+ } from './form'
23
+ export type {
24
+ FieldState,
25
+ FieldWrapperProps,
26
+ FormValues,
27
+ FormErrors,
28
+ FormTouched,
29
+ } from './form'
@@ -0,0 +1,72 @@
1
+ import { useSyncExternalStore } from 'react'
2
+
3
+ /**
4
+ * Utility function to detect the current theme.
5
+ * Checks for the 'light' class on the html element (set by Storybook's theme addon).
6
+ *
7
+ * NOTE: This function reads the theme at call time. For reactive theme detection
8
+ * that re-renders when the theme changes, use the `useTheme` hook instead.
9
+ *
10
+ * @returns 'light' if light theme is active, 'dark' otherwise
11
+ */
12
+ export function getTheme(): 'light' | 'dark' {
13
+ if (typeof document === 'undefined') return 'dark'
14
+ return document.documentElement.classList.contains('light') ? 'light' : 'dark'
15
+ }
16
+
17
+ // Subscribers for theme changes
18
+ let listeners: Array<() => void> = []
19
+
20
+ function subscribe(listener: () => void) {
21
+ listeners = [...listeners, listener]
22
+
23
+ // Set up MutationObserver to watch for class changes on html element
24
+ if (typeof document !== 'undefined' && listeners.length === 1) {
25
+ const observer = new MutationObserver((mutations) => {
26
+ for (const mutation of mutations) {
27
+ if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
28
+ // Notify all listeners when class changes
29
+ listeners.forEach(l => l())
30
+ }
31
+ }
32
+ })
33
+
34
+ observer.observe(document.documentElement, {
35
+ attributes: true,
36
+ attributeFilter: ['class'],
37
+ })
38
+
39
+ // Store observer for cleanup
40
+ ;(subscribe as any)._observer = observer
41
+ }
42
+
43
+ return () => {
44
+ listeners = listeners.filter(l => l !== listener)
45
+
46
+ // Clean up observer when no more listeners
47
+ if (listeners.length === 0 && (subscribe as any)._observer) {
48
+ ;(subscribe as any)._observer.disconnect()
49
+ ;(subscribe as any)._observer = null
50
+ }
51
+ }
52
+ }
53
+
54
+ function getSnapshot(): 'light' | 'dark' {
55
+ return getTheme()
56
+ }
57
+
58
+ function getServerSnapshot(): 'light' | 'dark' {
59
+ return 'dark'
60
+ }
61
+
62
+ /**
63
+ * React hook for reactive theme detection.
64
+ * Automatically re-renders when the theme changes (via class changes on html element).
65
+ *
66
+ * Uses useSyncExternalStore for proper React 18 concurrent mode support.
67
+ *
68
+ * @returns 'light' if light theme is active, 'dark' otherwise
69
+ */
70
+ export function useTheme(): 'light' | 'dark' {
71
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
72
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Development-mode custom JSX runtime for titan-design's web distribution.
3
+ * See jsx-runtime-web.ts for details on the $$css conversion approach.
4
+ */
5
+ import { jsxDEV as reactJsxDEV, Fragment } from 'react/jsx-dev-runtime'
6
+ import type { StyleProp } from 'react-native'
7
+
8
+ type JsxDevFn = typeof reactJsxDEV
9
+
10
+ function wrapJsxDev(fn: JsxDevFn): JsxDevFn {
11
+ return function (type, props: Record<string, any>, ...rest) {
12
+ if (props && typeof props.className === 'string' && props.className) {
13
+ const cn = props.className
14
+ const cssStyle = { $$css: true, [cn]: cn } as unknown as StyleProp<any>
15
+ const existing = props.style
16
+ props = {
17
+ ...props,
18
+ style: existing ? [cssStyle, existing] : cssStyle,
19
+ }
20
+ delete props.className
21
+ }
22
+ return fn(type, props, ...rest)
23
+ } as JsxDevFn
24
+ }
25
+
26
+ const jsxDEV = wrapJsxDev(reactJsxDEV)
27
+
28
+ export { jsxDEV, Fragment }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Custom JSX runtime for titan-design's web distribution.
3
+ *
4
+ * Converts className props to $$css style objects at build time,
5
+ * eliminating the need for NativeWind's runtime interopComponents
6
+ * Map lookup. RNW's styleq recognizes $$css objects and applies
7
+ * their values as CSS class names on DOM elements.
8
+ *
9
+ * This file is NOT used by source builds (specimen, storybook,
10
+ * native Metro). It is only inlined into the dist by tsup via
11
+ * an esbuild onResolve plugin.
12
+ */
13
+ import {
14
+ jsx as reactJsx,
15
+ jsxs as reactJsxs,
16
+ Fragment,
17
+ } from 'react/jsx-runtime'
18
+ import type { StyleProp } from 'react-native'
19
+
20
+ type JsxFn = typeof reactJsx
21
+
22
+ function wrapJsx(fn: JsxFn): JsxFn {
23
+ return function (type, props: Record<string, any>, ...rest) {
24
+ if (props && typeof props.className === 'string' && props.className) {
25
+ const cn = props.className
26
+ const cssStyle = { $$css: true, [cn]: cn } as unknown as StyleProp<any>
27
+ const existing = props.style
28
+ props = {
29
+ ...props,
30
+ style: existing ? [cssStyle, existing] : cssStyle,
31
+ }
32
+ delete props.className
33
+ }
34
+ return fn(type, props, ...rest)
35
+ } as JsxFn
36
+ }
37
+
38
+ const jsx = wrapJsx(reactJsx)
39
+ const jsxs = wrapJsx(reactJsxs)
40
+
41
+ export { jsx, jsxs, Fragment }
@@ -160,9 +160,10 @@ module.exports = {
160
160
  },
161
161
  },
162
162
  fontFamily: {
163
- sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
164
- body: ['Nunito Sans', 'sans-serif'],
165
- heading: ['Space Grotesk', 'sans-serif'],
163
+ sans: ['var(--font-family-sans)', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
164
+ body: ['var(--font-family-body)', 'sans-serif'],
165
+ heading: ['var(--font-family-heading)', 'sans-serif'],
166
+ mono: ['var(--font-family-mono)', 'monospace'],
166
167
  },
167
168
  fontSize: {
168
169
  xs: ['0.75rem', { lineHeight: '1rem' }],
@@ -181,6 +182,16 @@ module.exports = {
181
182
  md: '0px 4px 6px rgba(100, 116, 139, 0.12)',
182
183
  lg: '0px 10px 15px rgba(100, 116, 139, 0.12)',
183
184
  xl: '0px 20px 25px rgba(100, 116, 139, 0.12)',
185
+ // Glow shadows (colored radial glow for emphasis)
186
+ 'glow-primary': '0 0 20px 2px rgba(var(--color-brand-primary-rgb, 255, 121, 0), 0.4)',
187
+ 'glow-secondary': '0 0 20px 2px rgba(var(--color-brand-secondary-rgb, 64, 109, 135), 0.4)',
188
+ 'glow-success': '0 0 20px 2px rgba(var(--color-status-success-rgb, 20, 184, 166), 0.35)',
189
+ 'glow-error': '0 0 20px 2px rgba(var(--color-status-error-rgb, 209, 67, 67), 0.4)',
190
+ 'glow-warning': '0 0 20px 2px rgba(var(--color-status-warning-rgb, 255, 176, 32), 0.35)',
191
+ 'glow-info': '0 0 20px 2px rgba(var(--color-status-info-rgb, 33, 150, 243), 0.35)',
192
+ 'glow-sm': '0 0 12px 0px',
193
+ 'glow-md': '0 0 20px 2px',
194
+ 'glow-lg': '0 0 30px 4px',
184
195
  },
185
196
  borderRadius: {
186
197
  DEFAULT: '8px',
@@ -189,6 +200,45 @@ module.exports = {
189
200
  lg: '12px',
190
201
  xl: '16px',
191
202
  },
203
+ transitionTimingFunction: {
204
+ 'out': 'cubic-bezier(0.22, 1, 0.36, 1)',
205
+ 'in-out': 'cubic-bezier(0.65, 0, 0.35, 1)',
206
+ 'spring': 'cubic-bezier(0.34, 1.56, 0.64, 1)',
207
+ },
208
+ transitionDuration: {
209
+ 'fast': '150ms',
210
+ 'normal': '250ms',
211
+ 'slow': '400ms',
212
+ },
213
+ keyframes: {
214
+ 'fade-in': {
215
+ '0%': { opacity: '0', transform: 'translateY(8px)' },
216
+ '100%': { opacity: '1', transform: 'translateY(0)' },
217
+ },
218
+ 'slide-down': {
219
+ '0%': { opacity: '0', transform: 'translateY(-8px)' },
220
+ '100%': { opacity: '1', transform: 'translateY(0)' },
221
+ },
222
+ 'slide-up': {
223
+ '0%': { opacity: '0', transform: 'translateY(100%)' },
224
+ '100%': { opacity: '1', transform: 'translateY(0)' },
225
+ },
226
+ 'scale-in': {
227
+ '0%': { opacity: '0', transform: 'scale(0.95)' },
228
+ '100%': { opacity: '1', transform: 'scale(1)' },
229
+ },
230
+ shimmer: {
231
+ '0%': { backgroundPosition: '-200% 0' },
232
+ '100%': { backgroundPosition: '200% 0' },
233
+ },
234
+ },
235
+ animation: {
236
+ 'fade-in': 'fade-in 250ms cubic-bezier(0.22, 1, 0.36, 1)',
237
+ 'slide-down': 'slide-down 250ms cubic-bezier(0.22, 1, 0.36, 1)',
238
+ 'slide-up': 'slide-up 400ms cubic-bezier(0.34, 1.56, 0.64, 1)',
239
+ 'scale-in': 'scale-in 200ms cubic-bezier(0.22, 1, 0.36, 1)',
240
+ shimmer: 'shimmer 2s linear infinite',
241
+ },
192
242
  },
193
243
  },
194
244
  plugins: [],