@titan-design/react-ui 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/package.json +10 -5
  2. package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
  3. package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
  4. package/src/components/custom/DateTime/DateTime.tsx +235 -0
  5. package/src/components/custom/DateTime/index.ts +2 -0
  6. package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
  7. package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
  8. package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
  9. package/src/components/custom/EmptyState/index.ts +2 -0
  10. package/src/components/custom/Metric/Metric.stories.tsx +133 -0
  11. package/src/components/custom/Metric/Metric.test.tsx +120 -0
  12. package/src/components/custom/Metric/Metric.tsx +92 -0
  13. package/src/components/custom/Metric/index.ts +2 -0
  14. package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
  15. package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
  16. package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
  17. package/src/components/custom/Sidebar/index.ts +18 -0
  18. package/src/components/custom/Table/Table.stories.tsx +481 -0
  19. package/src/components/custom/Table/Table.test.tsx +531 -0
  20. package/src/components/custom/Table/Table.tsx +696 -0
  21. package/src/components/custom/Table/index.ts +22 -0
  22. package/src/components/custom/Typography/Typography.stories.tsx +153 -0
  23. package/src/components/custom/Typography/Typography.test.tsx +107 -0
  24. package/src/components/custom/Typography/Typography.tsx +218 -0
  25. package/src/components/custom/Typography/index.ts +19 -0
  26. package/src/components/custom/index.ts +8 -0
  27. package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
  28. package/src/components/custom/stepper/Stepper.test.tsx +230 -0
  29. package/src/components/custom/stepper/Stepper.tsx +250 -0
  30. package/src/components/custom/stepper/index.ts +10 -0
  31. package/src/components/index.ts +5 -0
  32. package/src/components/ui/alert/Alert.stories.tsx +154 -0
  33. package/src/components/ui/alert/Alert.test.tsx +202 -0
  34. package/src/components/ui/alert/Alert.tsx +170 -0
  35. package/src/components/ui/alert/index.ts +8 -0
  36. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
  37. package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
  38. package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
  39. package/src/components/ui/autocomplete/index.ts +2 -0
  40. package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
  41. package/src/components/ui/avatar/Avatar.test.tsx +135 -0
  42. package/src/components/ui/avatar/Avatar.tsx +168 -0
  43. package/src/components/ui/avatar/index.ts +2 -0
  44. package/src/components/ui/badge/Badge.stories.tsx +100 -0
  45. package/src/components/ui/badge/Badge.test.tsx +86 -0
  46. package/src/components/ui/badge/Badge.tsx +105 -0
  47. package/src/components/ui/badge/index.ts +2 -0
  48. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
  49. package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
  50. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
  51. package/src/components/ui/breadcrumbs/index.ts +2 -0
  52. package/src/components/ui/button/Button.stories.tsx +216 -0
  53. package/src/components/ui/button/Button.test.tsx +132 -0
  54. package/src/components/ui/button/Button.tsx +242 -0
  55. package/src/components/ui/button/index.ts +10 -0
  56. package/src/components/ui/card/Card.stories.tsx +443 -0
  57. package/src/components/ui/card/Card.test.tsx +265 -0
  58. package/src/components/ui/card/Card.tsx +380 -0
  59. package/src/components/ui/card/index.ts +20 -0
  60. package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
  61. package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
  62. package/src/components/ui/checkbox/Checkbox.tsx +163 -0
  63. package/src/components/ui/checkbox/index.ts +2 -0
  64. package/src/components/ui/chip/Chip.stories.tsx +112 -0
  65. package/src/components/ui/chip/Chip.test.tsx +119 -0
  66. package/src/components/ui/chip/Chip.tsx +151 -0
  67. package/src/components/ui/chip/index.ts +2 -0
  68. package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
  69. package/src/components/ui/collapse/Collapse.test.tsx +298 -0
  70. package/src/components/ui/collapse/Collapse.tsx +275 -0
  71. package/src/components/ui/collapse/index.ts +18 -0
  72. package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
  73. package/src/components/ui/data-row/DataRow.test.tsx +78 -0
  74. package/src/components/ui/data-row/DataRow.tsx +29 -0
  75. package/src/components/ui/data-row/index.ts +2 -0
  76. package/src/components/ui/divider/Divider.stories.tsx +71 -0
  77. package/src/components/ui/divider/Divider.test.tsx +55 -0
  78. package/src/components/ui/divider/Divider.tsx +37 -0
  79. package/src/components/ui/divider/index.ts +2 -0
  80. package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
  81. package/src/components/ui/drawer/Drawer.test.tsx +142 -0
  82. package/src/components/ui/drawer/Drawer.tsx +228 -0
  83. package/src/components/ui/drawer/index.ts +9 -0
  84. package/src/components/ui/form-field/FormField.stories.tsx +260 -0
  85. package/src/components/ui/form-field/FormField.test.tsx +280 -0
  86. package/src/components/ui/form-field/FormField.tsx +308 -0
  87. package/src/components/ui/form-field/index.ts +2 -0
  88. package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
  89. package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
  90. package/src/components/ui/help-tip/HelpTip.tsx +229 -0
  91. package/src/components/ui/help-tip/index.ts +2 -0
  92. package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
  93. package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
  94. package/src/components/ui/icon-box/IconBox.tsx +57 -0
  95. package/src/components/ui/icon-box/index.ts +1 -0
  96. package/src/components/ui/index.ts +36 -0
  97. package/src/components/ui/input/Input.stories.tsx +192 -0
  98. package/src/components/ui/input/Input.test.tsx +86 -0
  99. package/src/components/ui/input/Input.tsx +273 -0
  100. package/src/components/ui/input/index.ts +2 -0
  101. package/src/components/ui/link/Link.stories.tsx +78 -0
  102. package/src/components/ui/link/Link.test.tsx +94 -0
  103. package/src/components/ui/link/Link.tsx +86 -0
  104. package/src/components/ui/link/index.ts +2 -0
  105. package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
  106. package/src/components/ui/list-item/ListItem.test.tsx +128 -0
  107. package/src/components/ui/list-item/ListItem.tsx +89 -0
  108. package/src/components/ui/list-item/index.ts +14 -0
  109. package/src/components/ui/menu/Menu.stories.tsx +180 -0
  110. package/src/components/ui/menu/Menu.test.tsx +353 -0
  111. package/src/components/ui/menu/Menu.tsx +222 -0
  112. package/src/components/ui/menu/index.ts +2 -0
  113. package/src/components/ui/modal/Modal.stories.tsx +350 -0
  114. package/src/components/ui/modal/Modal.test.tsx +159 -0
  115. package/src/components/ui/modal/Modal.tsx +254 -0
  116. package/src/components/ui/modal/index.ts +19 -0
  117. package/src/components/ui/popover/Popover.stories.tsx +190 -0
  118. package/src/components/ui/popover/Popover.test.tsx +277 -0
  119. package/src/components/ui/popover/Popover.tsx +162 -0
  120. package/src/components/ui/popover/index.ts +2 -0
  121. package/src/components/ui/progress/Progress.stories.tsx +190 -0
  122. package/src/components/ui/progress/Progress.test.tsx +181 -0
  123. package/src/components/ui/progress/Progress.tsx +320 -0
  124. package/src/components/ui/progress/index.ts +9 -0
  125. package/src/components/ui/radio/Radio.stories.tsx +199 -0
  126. package/src/components/ui/radio/Radio.test.tsx +166 -0
  127. package/src/components/ui/radio/Radio.tsx +200 -0
  128. package/src/components/ui/radio/index.ts +2 -0
  129. package/src/components/ui/section/Section.stories.tsx +111 -0
  130. package/src/components/ui/section/Section.test.tsx +121 -0
  131. package/src/components/ui/section/Section.tsx +52 -0
  132. package/src/components/ui/section/index.ts +6 -0
  133. package/src/components/ui/select/Select.stories.tsx +286 -0
  134. package/src/components/ui/select/Select.test.tsx +180 -0
  135. package/src/components/ui/select/Select.tsx +236 -0
  136. package/src/components/ui/select/index.ts +2 -0
  137. package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
  138. package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
  139. package/src/components/ui/skeleton/Skeleton.tsx +229 -0
  140. package/src/components/ui/skeleton/index.ts +16 -0
  141. package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
  142. package/src/components/ui/spinner/Spinner.test.tsx +67 -0
  143. package/src/components/ui/spinner/Spinner.tsx +72 -0
  144. package/src/components/ui/spinner/index.ts +2 -0
  145. package/src/components/ui/stack/Stack.stories.tsx +127 -0
  146. package/src/components/ui/stack/Stack.test.tsx +184 -0
  147. package/src/components/ui/stack/Stack.tsx +76 -0
  148. package/src/components/ui/stack/index.ts +2 -0
  149. package/src/components/ui/surface/Surface.stories.tsx +91 -0
  150. package/src/components/ui/surface/Surface.test.tsx +63 -0
  151. package/src/components/ui/surface/Surface.tsx +51 -0
  152. package/src/components/ui/surface/index.ts +1 -0
  153. package/src/components/ui/switch/Switch.stories.tsx +111 -0
  154. package/src/components/ui/switch/Switch.test.tsx +112 -0
  155. package/src/components/ui/switch/Switch.tsx +103 -0
  156. package/src/components/ui/switch/index.ts +2 -0
  157. package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
  158. package/src/components/ui/tabs/Tabs.test.tsx +192 -0
  159. package/src/components/ui/tabs/Tabs.tsx +253 -0
  160. package/src/components/ui/tabs/index.ts +16 -0
  161. package/src/components/ui/toast/Toast.stories.tsx +255 -0
  162. package/src/components/ui/toast/Toast.test.tsx +207 -0
  163. package/src/components/ui/toast/Toast.tsx +327 -0
  164. package/src/components/ui/toast/index.ts +8 -0
  165. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
  166. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
  167. package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
  168. package/src/components/ui/toolbar-button/index.ts +7 -0
  169. package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
  170. package/src/components/ui/tooltip/Tooltip.test.tsx +179 -0
  171. package/src/components/ui/tooltip/Tooltip.tsx +143 -0
  172. package/src/components/ui/tooltip/index.ts +2 -0
  173. package/src/index.ts +10 -0
  174. package/src/test/setup.ts +19 -0
  175. package/src/theme/Colors.stories.tsx +258 -0
  176. package/src/theme/config.ts +193 -0
  177. package/src/theme/elevation.stories.tsx +346 -0
  178. package/src/theme/elevation.test.ts +27 -0
  179. package/src/theme/elevation.ts +567 -0
  180. package/src/theme/index.ts +39 -0
  181. package/src/theme/shadows.stories.tsx +523 -0
  182. package/src/theme/shadows.ts +439 -0
  183. package/src/theme/tokens/index.ts +2 -0
  184. package/src/theme/tokens/primitives.ts +354 -0
  185. package/src/theme/tokens/semantic.ts +381 -0
  186. package/src/types/jest-axe.d.ts +16 -0
  187. package/src/types/nativewind.d.ts +54 -0
  188. package/src/utils/cn.ts +14 -0
  189. package/src/utils/colors.ts +140 -0
  190. package/src/utils/form.ts +188 -0
  191. package/src/utils/index.ts +29 -0
  192. package/src/utils/useTheme.ts +72 -0
@@ -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
+ }