@praxiis/ui 0.0.1 → 0.0.3

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 (37) hide show
  1. package/dist/index.d.mts +3 -111
  2. package/dist/index.d.ts +3 -111
  3. package/dist/index.js +6 -200
  4. package/dist/index.mjs +6 -192
  5. package/package.json +11 -12
  6. package/src/README.md +688 -0
  7. package/src/components/CalendarStrip/CalendarStrip.a11y.ts +51 -0
  8. package/src/components/CalendarStrip/DayCard/DayCard.a11y.ts +52 -0
  9. package/src/components/EmptyState/EmptyState.a11y.ts +53 -0
  10. package/src/components/Header/Header.a11y.ts +82 -0
  11. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.a11y.ts +15 -0
  12. package/src/core/index.ts +1 -1
  13. package/src/core/restyle/index.ts +1 -1
  14. package/src/core/restyle/restylePresetRegistry.ts +7 -7
  15. package/src/index.tsx +2 -11
  16. package/src/primitives/actions/Button/Button.a11y.ts +38 -0
  17. package/src/primitives/actions/IconButton/IconButton.a11y.ts +55 -0
  18. package/src/primitives/content/Avatar/Avatar.a11y.ts +50 -0
  19. package/src/primitives/content/Badge/Badge.a11y.ts +83 -0
  20. package/src/primitives/content/Card/Card.a11y.ts +60 -0
  21. package/src/primitives/content/Chip/Chip.a11y.ts +101 -0
  22. package/src/primitives/content/Icon/Icon.a11y.ts +43 -0
  23. package/src/primitives/feedback/ProgressBar/ProgressBar.a11y.ts +68 -0
  24. package/src/primitives/feedback/Skeleton/Skeleton.a11y.ts +46 -0
  25. package/src/primitives/feedback/Spinner/Spinner.a11y.ts +47 -0
  26. package/src/primitives/feedback/Toast/Toast.a11y.ts +75 -0
  27. package/src/primitives/inputs/Checkbox/Checkbox.a11y.ts +47 -0
  28. package/src/primitives/inputs/RadioButton/RadioButton.a11y.ts +48 -0
  29. package/src/primitives/inputs/SegmentedControl/SegmentedControl.a11y.ts +59 -0
  30. package/src/primitives/inputs/SelectSheet/SelectSheet.a11y.ts +117 -0
  31. package/src/primitives/inputs/Switch/Switch.a11y.ts +29 -0
  32. package/src/primitives/inputs/TextInput/TextInput.a11y.ts +77 -0
  33. package/src/primitives/layout/Divider/Divider.a11y.ts +55 -0
  34. package/src/primitives/overlays/Modal/Modal.a11y.ts +64 -0
  35. package/src/providers/ThemeProvider/index.ts +0 -12
  36. package/src/providers/index.ts +0 -8
  37. package/src/providers/ThemeProvider/createTheme.ts +0 -304
@@ -0,0 +1,77 @@
1
+ /**
2
+ * TextInput Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the TextInput component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Inputs need proper labels
8
+ * - 3.3.1 Error Identification: Errors announced via hint
9
+ * - 3.3.2 Labels or Instructions: Required fields indicated
10
+ *
11
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions
12
+ */
13
+
14
+ import { TextInputA11yParams, TextInputA11yProps } from "./TextInput.types";
15
+
16
+ /**
17
+ * Generate accessibility props for TextInput.
18
+ *
19
+ * Implements WCAG requirements:
20
+ * - accessibilityLabel: Uses label prop as fallback, adds "required" suffix
21
+ * - accessibilityHint: Error message takes precedence over custom hint
22
+ * - accessibilityState: Communicates disabled state
23
+ *
24
+ * @param params - Accessibility parameters
25
+ * @returns Accessibility props for the text input
26
+ *
27
+ * @warning Logs console warning in dev if both label and accessibilityLabel missing
28
+ *
29
+ * @example
30
+ * const a11yProps = getTextInputA11y({
31
+ * label: "Email",
32
+ * required: true,
33
+ * disabled: false,
34
+ * error: "Invalid email",
35
+ * });
36
+ * // { accessibilityLabel: "Email, required", accessibilityHint: "Error: Invalid email", accessibilityState: { disabled: false } }
37
+ */
38
+ export function getTextInputA11y(
39
+ params: TextInputA11yParams
40
+ ): TextInputA11yProps {
41
+ const {
42
+ label,
43
+ accessibilityLabel,
44
+ accessibilityHint,
45
+ disabled,
46
+ error,
47
+ required,
48
+ fallbackLabel,
49
+ requiredLabel,
50
+ errorPrefixLabel,
51
+ } = params;
52
+
53
+ if (!accessibilityLabel && !label && process.env.NODE_ENV !== "production") {
54
+ console.warn(
55
+ "[TextInput] Missing label or accessibilityLabel (WCAG 4.1.2)"
56
+ );
57
+ }
58
+
59
+ const baseLabel = accessibilityLabel || label || fallbackLabel;
60
+ const fullLabel = required ? `${baseLabel}, ${requiredLabel}` : baseLabel;
61
+
62
+ // Error takes precedence, then custom hint
63
+ let finalHint: string | undefined;
64
+ if (error) {
65
+ finalHint = `${errorPrefixLabel}: ${error}`;
66
+ } else if (accessibilityHint) {
67
+ finalHint = accessibilityHint;
68
+ }
69
+
70
+ return {
71
+ accessibilityLabel: fullLabel,
72
+ accessibilityHint: finalHint,
73
+ accessibilityState: {
74
+ disabled,
75
+ },
76
+ };
77
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Divider Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Divider component.
5
+ *
6
+ * WCAG References:
7
+ * - Decorative elements should be hidden from assistive technologies
8
+ * - When a divider has semantic meaning (with label), it should be announced
9
+ *
10
+ * The divider uses "none" role by default as it's purely decorative.
11
+ * When a label is present, it's announced via accessibilityLabel.
12
+ *
13
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
14
+ */
15
+
16
+ import { DividerA11yParams, DividerA11yProps } from "./Divider.types";
17
+
18
+ /**
19
+ * Generate accessibility props for Divider.
20
+ *
21
+ * Dividers are typically decorative, so they use role="none" and
22
+ * are not accessible by default. When a label is present, the
23
+ * divider becomes accessible with the label as its description.
24
+ *
25
+ * @param params - Accessibility parameters
26
+ * @returns Object to spread onto the Divider component
27
+ *
28
+ * @example
29
+ * // Decorative divider (hidden from AT)
30
+ * const a11yProps = getDividerA11y({ orientation: "horizontal" });
31
+ * // { accessibilityRole: "none", accessible: false }
32
+ *
33
+ * @example
34
+ * // Divider with label (announced by AT)
35
+ * const a11yProps = getDividerA11y({
36
+ * orientation: "horizontal",
37
+ * label: "OR",
38
+ * accessibilityLabel: "Or"
39
+ * });
40
+ * // { accessibilityRole: "none", accessible: true, accessibilityLabel: "Or" }
41
+ */
42
+ export function getDividerA11y(params: DividerA11yParams): DividerA11yProps {
43
+ const { label, accessibilityLabel } = params;
44
+
45
+ // If there's a label or explicit a11y label, make it accessible
46
+ const hasSemanticMeaning = Boolean(label || accessibilityLabel);
47
+
48
+ return {
49
+ accessibilityRole: "none",
50
+ accessible: hasSemanticMeaning,
51
+ ...(hasSemanticMeaning && {
52
+ accessibilityLabel: accessibilityLabel || label,
53
+ }),
54
+ };
55
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Modal Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Modal component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Modal needs proper label and modal indication
8
+ * - 1.3.1 Info and Relationships: State must be programmatically determinable
9
+ * - 2.4.3 Focus Order: Modal must trap focus when visible
10
+ *
11
+ * The modal uses accessibilityViewIsModal to indicate to assistive
12
+ * technologies that content behind the modal is not accessible.
13
+ *
14
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
15
+ */
16
+
17
+ import type { ModalA11yParams, ModalA11yProps, ModalCloseButtonA11yParams } from "./Modal.types";
18
+
19
+ /**
20
+ * Generate accessibility props for Modal container.
21
+ *
22
+ * @param params - Accessibility parameters
23
+ * @returns Object to spread onto the Modal container
24
+ *
25
+ * @warning Logs console warning in dev if title and accessibilityLabel are both missing
26
+ *
27
+ * @example
28
+ * const a11yProps = getModalA11y({
29
+ * title: "Confirm Delete",
30
+ * position: "center",
31
+ * });
32
+ * // { accessible: true, accessibilityRole: "none", accessibilityLabel: "Confirm Delete dialog", accessibilityViewIsModal: true }
33
+ */
34
+ export function getModalA11y(params: ModalA11yParams): ModalA11yProps {
35
+ const { title, position, accessibilityLabel, fallbackTitle, positionLabels } = params;
36
+
37
+ if (!accessibilityLabel && !title && process.env.NODE_ENV !== "production") {
38
+ console.warn(
39
+ "[Modal] Missing title or accessibilityLabel (WCAG 4.1.2)"
40
+ );
41
+ }
42
+
43
+ const positionLabel = positionLabels[position];
44
+
45
+ return {
46
+ accessible: true,
47
+ accessibilityRole: "none",
48
+ accessibilityLabel:
49
+ accessibilityLabel || `${title || fallbackTitle} ${positionLabel}`,
50
+ accessibilityViewIsModal: true,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Get accessibility props for the modal close button.
56
+ */
57
+ export function getModalCloseButtonA11yProps(params: ModalCloseButtonA11yParams) {
58
+ return {
59
+ accessible: true as const,
60
+ accessibilityRole: "button" as const,
61
+ accessibilityLabel: params.closeLabel,
62
+ accessibilityHint: params.closeHint,
63
+ };
64
+ }
@@ -20,15 +20,3 @@ export type {
20
20
 
21
21
  // Default themes
22
22
  export { lightTheme, darkTheme, defaultTheme } from './defaultTheme';
23
-
24
- // Theme factory
25
- export {
26
- createTheme,
27
- createThemePair,
28
- horizonTheme,
29
- sageTheme,
30
- sunsetTheme,
31
- oceanTheme,
32
- lavenderTheme,
33
- roseTheme,
34
- } from './createTheme';
@@ -5,14 +5,6 @@
5
5
  */
6
6
 
7
7
  export {
8
- createTheme,
9
- createThemePair,
10
- horizonTheme,
11
- sageTheme,
12
- sunsetTheme,
13
- oceanTheme,
14
- lavenderTheme,
15
- roseTheme,
16
8
  lightTheme,
17
9
  darkTheme,
18
10
  defaultTheme,
@@ -1,304 +0,0 @@
1
- /**
2
- * El Sendero Design System - Theme Factory
3
- *
4
- * Create custom themes by extending the base theme with overrides.
5
- *
6
- * @example
7
- * const myTheme = createTheme(defaultTheme, {
8
- * colors: {
9
- * accent: {
10
- * primary: '#8B5CF6', // Purple accent
11
- * }
12
- * }
13
- * });
14
- */
15
-
16
- import { Theme, ThemeOverrides, DeepPartial } from './types';
17
- import { lightTheme, darkTheme } from './defaultTheme';
18
-
19
- // =============================================================================
20
- // DEEP MERGE UTILITY
21
- // =============================================================================
22
-
23
- /**
24
- * Deep merge two objects, with source values overriding target
25
- */
26
- function deepMerge<T extends object>(
27
- target: T,
28
- source: DeepPartial<T>
29
- ): T {
30
- const result = { ...target };
31
-
32
- for (const key in source) {
33
- if (Object.prototype.hasOwnProperty.call(source, key)) {
34
- const sourceValue = source[key as keyof typeof source];
35
- const targetValue = (target as Record<string, unknown>)[key];
36
-
37
- if (
38
- sourceValue !== null &&
39
- typeof sourceValue === 'object' &&
40
- !Array.isArray(sourceValue) &&
41
- targetValue !== null &&
42
- typeof targetValue === 'object' &&
43
- !Array.isArray(targetValue)
44
- ) {
45
- result[key as keyof T] = deepMerge(
46
- targetValue as Record<string, unknown>,
47
- sourceValue as DeepPartial<Record<string, unknown>>
48
- ) as T[keyof T];
49
- } else if (sourceValue !== undefined) {
50
- result[key as keyof T] = sourceValue as T[keyof T];
51
- }
52
- }
53
- }
54
-
55
- return result;
56
- }
57
-
58
- // =============================================================================
59
- // CREATE THEME
60
- // =============================================================================
61
-
62
- /**
63
- * Create a custom theme by extending a base theme with overrides.
64
- *
65
- * @param baseTheme - The theme to extend (usually defaultTheme)
66
- * @param overrides - Partial theme object with your customizations
67
- * @returns A complete Theme object with your overrides applied
68
- *
69
- * @example
70
- * // Create a sage-accented theme
71
- * const sageTheme = createTheme(defaultTheme, {
72
- * name: 'Sage',
73
- * colors: {
74
- * accent: {
75
- * primary: '#6B8F6B',
76
- * primaryHover: '#557255',
77
- * primaryPressed: '#445944',
78
- * }
79
- * }
80
- * });
81
- *
82
- * @example
83
- * // Create a more rounded theme
84
- * const softTheme = createTheme(defaultTheme, {
85
- * radii: {
86
- * md: 16,
87
- * lg: 24,
88
- * xl: 32,
89
- * }
90
- * });
91
- */
92
- export function createTheme(
93
- baseTheme: Theme,
94
- overrides: ThemeOverrides
95
- ): Theme {
96
- return deepMerge(baseTheme, overrides);
97
- }
98
-
99
- // =============================================================================
100
- // CREATE THEME PAIR
101
- // =============================================================================
102
-
103
- /**
104
- * Create both light and dark versions of a custom theme.
105
- *
106
- * @param overrides - Overrides to apply to both light and dark themes
107
- * @param lightOverrides - Additional overrides for light theme only
108
- * @param darkOverrides - Additional overrides for dark theme only
109
- * @returns Object with light and dark theme variants
110
- *
111
- * @example
112
- * const { light, dark } = createThemePair(
113
- * { name: 'MyBrand' },
114
- * { colors: { accent: { primary: '#A68B6A' } } }, // Light-specific
115
- * { colors: { accent: { primary: '#C4A285' } } } // Dark-specific
116
- * );
117
- */
118
- export function createThemePair(
119
- overrides: ThemeOverrides = {},
120
- lightOverrides: ThemeOverrides = {},
121
- darkOverrides: ThemeOverrides = {}
122
- ): { light: Theme; dark: Theme } {
123
- return {
124
- light: createTheme(
125
- createTheme(lightTheme, overrides),
126
- lightOverrides
127
- ),
128
- dark: createTheme(
129
- createTheme(darkTheme, overrides),
130
- darkOverrides
131
- ),
132
- };
133
- }
134
-
135
- // =============================================================================
136
- // PRESET THEMES
137
- // =============================================================================
138
-
139
- /**
140
- * Horizon Default - Blue with a calming feel
141
- */
142
- export const horizonTheme = createThemePair({
143
- name: 'Horizon',
144
- });
145
-
146
- /**
147
- * Sage Theme - Green-focused for hope and growth
148
- */
149
- export const sageTheme = createThemePair(
150
- { name: 'Sage' },
151
- {
152
- colors: {
153
- accent: {
154
- primary: '#6B8F6B',
155
- primaryHover: '#557255',
156
- primaryPressed: '#445944',
157
- },
158
- text: {
159
- link: '#6B8F6B',
160
- },
161
- border: {
162
- focus: '#6B8F6B',
163
- },
164
- },
165
- },
166
- {
167
- colors: {
168
- accent: {
169
- primary: '#8FAF8F',
170
- primaryHover: '#B3C7B3',
171
- primaryPressed: '#6B8F6B',
172
- },
173
- text: {
174
- link: '#8FAF8F',
175
- },
176
- border: {
177
- focus: '#8FAF8F',
178
- },
179
- },
180
- }
181
- );
182
-
183
- /**
184
- * Sunset Theme - Warm coral/peach for energy and comfort
185
- */
186
- export const sunsetTheme = createThemePair(
187
- { name: 'Sunset' },
188
- {
189
- colors: {
190
- accent: {
191
- primary: '#E8836B',
192
- primaryHover: '#CC6B55',
193
- primaryPressed: '#A85545',
194
- },
195
- text: {
196
- link: '#E8836B',
197
- },
198
- border: {
199
- focus: '#E8836B',
200
- },
201
- },
202
- },
203
- {
204
- colors: {
205
- accent: {
206
- primary: '#FF9B85',
207
- primaryHover: '#FFBAA8',
208
- primaryPressed: '#E8836B',
209
- },
210
- text: {
211
- link: '#FF9B85',
212
- },
213
- border: {
214
- focus: '#FF9B85',
215
- },
216
- },
217
- }
218
- );
219
-
220
- /**
221
- * Ocean Theme - Teal/cyan accents, calming water vibes
222
- */
223
- export const oceanTheme = createThemePair(
224
- {
225
- colors: {
226
- accent: {
227
- primary: '#0E9AA5',
228
- primaryHover: '#0B7E87',
229
- primaryPressed: '#086269',
230
- },
231
- text: { link: '#0E9AA5' },
232
- border: { focus: '#0E9AA5' },
233
- },
234
- },
235
- {
236
- colors: {
237
- accent: {
238
- primary: '#2DD4BF',
239
- primaryHover: '#5EEAD4',
240
- primaryPressed: '#0E9AA5',
241
- },
242
- text: { link: '#2DD4BF' },
243
- border: { focus: '#2DD4BF' },
244
- },
245
- }
246
- );
247
-
248
- /**
249
- * Lavender Theme - Soft purple accents, relaxing and spiritual
250
- */
251
- export const lavenderTheme = createThemePair(
252
- { name: 'Lavender' },
253
- {
254
- colors: {
255
- accent: {
256
- primary: '#8B5CF6',
257
- primaryHover: '#7C3AED',
258
- primaryPressed: '#6D28D9',
259
- },
260
- text: { link: '#8B5CF6' },
261
- border: { focus: '#8B5CF6' },
262
- },
263
- },
264
- {
265
- colors: {
266
- accent: {
267
- primary: '#A78BFA',
268
- primaryHover: '#C4B5FD',
269
- primaryPressed: '#8B5CF6',
270
- },
271
- text: { link: '#A78BFA' },
272
- border: { focus: '#A78BFA' },
273
- },
274
- }
275
- );
276
-
277
- /**
278
- * Rose Theme - Warm pink/rose accents, gentle and nurturing
279
- */
280
- export const roseTheme = createThemePair(
281
- { name: 'Rose' },
282
- {
283
- colors: {
284
- accent: {
285
- primary: '#E11D6C',
286
- primaryHover: '#BE185D',
287
- primaryPressed: '#9D174D',
288
- },
289
- text: { link: '#E11D6C' },
290
- border: { focus: '#E11D6C' },
291
- },
292
- },
293
- {
294
- colors: {
295
- accent: {
296
- primary: '#F472B6',
297
- primaryHover: '#F9A8D4',
298
- primaryPressed: '#E11D6C',
299
- },
300
- text: { link: '#F472B6' },
301
- border: { focus: '#F472B6' },
302
- },
303
- }
304
- );