@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.
- package/README.md +47 -1
- package/dist/{chunk-MGW37MPO.mjs → chunk-UXVRPOME.mjs} +173 -3
- package/dist/chunk-UXVRPOME.mjs.map +1 -0
- package/dist/index.d.mts +101 -12
- package/dist/index.d.ts +101 -12
- package/dist/index.js +1267 -468
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +734 -112
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +86 -1
- package/dist/theme/index.d.ts +86 -1
- package/dist/theme/index.js +166 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1 -1
- package/package.json +22 -6
- package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
- package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
- package/src/components/custom/DateTime/DateTime.tsx +235 -0
- package/src/components/custom/DateTime/index.ts +2 -0
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
- package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
- package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
- package/src/components/custom/EmptyState/index.ts +2 -0
- package/src/components/custom/Metric/Metric.stories.tsx +133 -0
- package/src/components/custom/Metric/Metric.test.tsx +120 -0
- package/src/components/custom/Metric/Metric.tsx +92 -0
- package/src/components/custom/Metric/index.ts +2 -0
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
- package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
- package/src/components/custom/Sidebar/index.ts +18 -0
- package/src/components/custom/Table/Table.stories.tsx +481 -0
- package/src/components/custom/Table/Table.test.tsx +531 -0
- package/src/components/custom/Table/Table.tsx +696 -0
- package/src/components/custom/Table/index.ts +22 -0
- package/src/components/custom/Typography/Typography.stories.tsx +153 -0
- package/src/components/custom/Typography/Typography.test.tsx +107 -0
- package/src/components/custom/Typography/Typography.tsx +218 -0
- package/src/components/custom/Typography/index.ts +19 -0
- package/src/components/custom/Workout/RestTimer.stories.tsx +71 -0
- package/src/components/custom/Workout/RestTimer.test.tsx +145 -0
- package/src/components/custom/Workout/RestTimer.tsx +172 -0
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +144 -0
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +130 -0
- package/src/components/custom/Workout/SupersetWrapper.tsx +63 -0
- package/src/components/custom/Workout/index.ts +2 -0
- package/src/components/custom/index.ts +9 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
- package/src/components/custom/stepper/Stepper.test.tsx +230 -0
- package/src/components/custom/stepper/Stepper.tsx +250 -0
- package/src/components/custom/stepper/index.ts +10 -0
- package/src/components/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +154 -0
- package/src/components/ui/alert/Alert.test.tsx +202 -0
- package/src/components/ui/alert/Alert.tsx +170 -0
- package/src/components/ui/alert/index.ts +8 -0
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
- package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
- package/src/components/ui/autocomplete/index.ts +2 -0
- package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
- package/src/components/ui/avatar/Avatar.test.tsx +152 -0
- package/src/components/ui/avatar/Avatar.tsx +176 -0
- package/src/components/ui/avatar/index.ts +2 -0
- package/src/components/ui/badge/Badge.stories.tsx +100 -0
- package/src/components/ui/badge/Badge.test.tsx +103 -0
- package/src/components/ui/badge/Badge.tsx +119 -0
- package/src/components/ui/badge/index.ts +3 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
- package/src/components/ui/breadcrumbs/index.ts +2 -0
- package/src/components/ui/button/Button.stories.tsx +216 -0
- package/src/components/ui/button/Button.test.tsx +132 -0
- package/src/components/ui/button/Button.tsx +297 -0
- package/src/components/ui/button/index.ts +10 -0
- package/src/components/ui/card/Card.stories.tsx +443 -0
- package/src/components/ui/card/Card.test.tsx +294 -0
- package/src/components/ui/card/Card.tsx +401 -0
- package/src/components/ui/card/index.ts +20 -0
- package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
- package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
- package/src/components/ui/checkbox/Checkbox.tsx +163 -0
- package/src/components/ui/checkbox/index.ts +2 -0
- package/src/components/ui/chip/Chip.stories.tsx +112 -0
- package/src/components/ui/chip/Chip.test.tsx +119 -0
- package/src/components/ui/chip/Chip.tsx +151 -0
- package/src/components/ui/chip/index.ts +2 -0
- package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
- package/src/components/ui/collapse/Collapse.test.tsx +298 -0
- package/src/components/ui/collapse/Collapse.tsx +275 -0
- package/src/components/ui/collapse/index.ts +18 -0
- package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
- package/src/components/ui/data-row/DataRow.test.tsx +78 -0
- package/src/components/ui/data-row/DataRow.tsx +29 -0
- package/src/components/ui/data-row/index.ts +2 -0
- package/src/components/ui/divider/Divider.stories.tsx +71 -0
- package/src/components/ui/divider/Divider.test.tsx +55 -0
- package/src/components/ui/divider/Divider.tsx +37 -0
- package/src/components/ui/divider/index.ts +2 -0
- package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
- package/src/components/ui/drawer/Drawer.test.tsx +142 -0
- package/src/components/ui/drawer/Drawer.tsx +228 -0
- package/src/components/ui/drawer/index.ts +9 -0
- package/src/components/ui/form-field/FormField.stories.tsx +260 -0
- package/src/components/ui/form-field/FormField.test.tsx +280 -0
- package/src/components/ui/form-field/FormField.tsx +308 -0
- package/src/components/ui/form-field/index.ts +2 -0
- package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
- package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
- package/src/components/ui/help-tip/HelpTip.tsx +229 -0
- package/src/components/ui/help-tip/index.ts +2 -0
- package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
- package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
- package/src/components/ui/icon-box/IconBox.tsx +57 -0
- package/src/components/ui/icon-box/index.ts +1 -0
- package/src/components/ui/index.ts +38 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +74 -0
- package/src/components/ui/indicator/Indicator.test.tsx +55 -0
- package/src/components/ui/indicator/Indicator.tsx +73 -0
- package/src/components/ui/indicator/index.ts +2 -0
- package/src/components/ui/input/Input.stories.tsx +192 -0
- package/src/components/ui/input/Input.test.tsx +86 -0
- package/src/components/ui/input/Input.tsx +273 -0
- package/src/components/ui/input/index.ts +2 -0
- package/src/components/ui/link/Link.stories.tsx +78 -0
- package/src/components/ui/link/Link.test.tsx +94 -0
- package/src/components/ui/link/Link.tsx +86 -0
- package/src/components/ui/link/index.ts +2 -0
- package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
- package/src/components/ui/list-item/ListItem.test.tsx +128 -0
- package/src/components/ui/list-item/ListItem.tsx +89 -0
- package/src/components/ui/list-item/index.ts +14 -0
- package/src/components/ui/menu/Menu.stories.tsx +180 -0
- package/src/components/ui/menu/Menu.test.tsx +353 -0
- package/src/components/ui/menu/Menu.tsx +222 -0
- package/src/components/ui/menu/index.ts +2 -0
- package/src/components/ui/modal/Modal.stories.tsx +350 -0
- package/src/components/ui/modal/Modal.test.tsx +159 -0
- package/src/components/ui/modal/Modal.tsx +254 -0
- package/src/components/ui/modal/index.ts +19 -0
- package/src/components/ui/pill/Pill.stories.tsx +71 -0
- package/src/components/ui/pill/Pill.test.tsx +69 -0
- package/src/components/ui/pill/Pill.tsx +104 -0
- package/src/components/ui/pill/index.ts +2 -0
- package/src/components/ui/popover/Popover.stories.tsx +190 -0
- package/src/components/ui/popover/Popover.test.tsx +419 -0
- package/src/components/ui/popover/Popover.tsx +237 -0
- package/src/components/ui/popover/index.ts +2 -0
- package/src/components/ui/progress/Progress.stories.tsx +190 -0
- package/src/components/ui/progress/Progress.test.tsx +210 -0
- package/src/components/ui/progress/Progress.tsx +339 -0
- package/src/components/ui/progress/index.ts +9 -0
- package/src/components/ui/radio/Radio.stories.tsx +199 -0
- package/src/components/ui/radio/Radio.test.tsx +166 -0
- package/src/components/ui/radio/Radio.tsx +200 -0
- package/src/components/ui/radio/index.ts +2 -0
- package/src/components/ui/section/Section.stories.tsx +111 -0
- package/src/components/ui/section/Section.test.tsx +121 -0
- package/src/components/ui/section/Section.tsx +52 -0
- package/src/components/ui/section/index.ts +6 -0
- package/src/components/ui/select/Select.stories.tsx +286 -0
- package/src/components/ui/select/Select.test.tsx +200 -0
- package/src/components/ui/select/Select.tsx +243 -0
- package/src/components/ui/select/index.ts +2 -0
- package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
- package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
- package/src/components/ui/skeleton/Skeleton.tsx +229 -0
- package/src/components/ui/skeleton/index.ts +16 -0
- package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
- package/src/components/ui/spinner/Spinner.test.tsx +67 -0
- package/src/components/ui/spinner/Spinner.tsx +72 -0
- package/src/components/ui/spinner/index.ts +2 -0
- package/src/components/ui/stack/Stack.stories.tsx +127 -0
- package/src/components/ui/stack/Stack.test.tsx +184 -0
- package/src/components/ui/stack/Stack.tsx +76 -0
- package/src/components/ui/stack/index.ts +2 -0
- package/src/components/ui/surface/Surface.stories.tsx +91 -0
- package/src/components/ui/surface/Surface.test.tsx +63 -0
- package/src/components/ui/surface/Surface.tsx +51 -0
- package/src/components/ui/surface/index.ts +1 -0
- package/src/components/ui/switch/Switch.stories.tsx +111 -0
- package/src/components/ui/switch/Switch.test.tsx +112 -0
- package/src/components/ui/switch/Switch.tsx +103 -0
- package/src/components/ui/switch/index.ts +2 -0
- package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
- package/src/components/ui/tabs/Tabs.test.tsx +192 -0
- package/src/components/ui/tabs/Tabs.tsx +253 -0
- package/src/components/ui/tabs/index.ts +16 -0
- package/src/components/ui/toast/Toast.stories.tsx +255 -0
- package/src/components/ui/toast/Toast.test.tsx +207 -0
- package/src/components/ui/toast/Toast.tsx +327 -0
- package/src/components/ui/toast/index.ts +9 -0
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
- package/src/components/ui/toolbar-button/index.ts +7 -0
- package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
- package/src/components/ui/tooltip/Tooltip.test.tsx +262 -0
- package/src/components/ui/tooltip/Tooltip.tsx +234 -0
- package/src/components/ui/tooltip/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/stories/PresetShowcase.stories.tsx +462 -0
- package/src/stories/ThemePresets.stories.tsx +85 -0
- package/src/test/setup.ts +19 -0
- package/src/theme/Colors.stories.tsx +258 -0
- package/src/theme/config.ts +193 -0
- package/src/theme/elevation.stories.tsx +346 -0
- package/src/theme/elevation.test.ts +27 -0
- package/src/theme/elevation.ts +567 -0
- package/src/theme/global.css +129 -1
- package/src/theme/index.ts +40 -0
- package/src/theme/presets/apply.ts +97 -0
- package/src/theme/presets/audiobook.ts +93 -0
- package/src/theme/presets/default.ts +6 -0
- package/src/theme/presets/index.ts +4 -0
- package/src/theme/presets/presets.test.ts +51 -0
- package/src/theme/presets/types.ts +76 -0
- package/src/theme/shadows.stories.tsx +523 -0
- package/src/theme/shadows.ts +439 -0
- package/src/theme/tokens/index.ts +2 -0
- package/src/theme/tokens/primitives.ts +354 -0
- package/src/theme/tokens/semantic.ts +381 -0
- package/src/types/jest-axe.d.ts +16 -0
- package/src/types/nativewind.d.ts +54 -0
- package/src/utils/avatar-color.test.ts +30 -0
- package/src/utils/avatar-color.ts +20 -0
- package/src/utils/cn.ts +14 -0
- package/src/utils/colors.ts +140 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/useTheme.ts +72 -0
- package/src/web-jsx/jsx-dev-runtime.ts +28 -0
- package/src/web-jsx/jsx-runtime.ts +41 -0
- package/tailwind.config.js +53 -3
- 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 }
|
package/tailwind.config.js
CHANGED
|
@@ -160,9 +160,10 @@ module.exports = {
|
|
|
160
160
|
},
|
|
161
161
|
},
|
|
162
162
|
fontFamily: {
|
|
163
|
-
sans: ['
|
|
164
|
-
body: ['
|
|
165
|
-
heading: ['
|
|
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: [],
|