@praxiis/ui 0.0.1 → 0.0.2

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 (26) hide show
  1. package/package.json +11 -12
  2. package/src/README.md +688 -0
  3. package/src/components/CalendarStrip/CalendarStrip.a11y.ts +51 -0
  4. package/src/components/CalendarStrip/DayCard/DayCard.a11y.ts +52 -0
  5. package/src/components/EmptyState/EmptyState.a11y.ts +53 -0
  6. package/src/components/Header/Header.a11y.ts +82 -0
  7. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.a11y.ts +15 -0
  8. package/src/primitives/actions/Button/Button.a11y.ts +38 -0
  9. package/src/primitives/actions/IconButton/IconButton.a11y.ts +55 -0
  10. package/src/primitives/content/Avatar/Avatar.a11y.ts +50 -0
  11. package/src/primitives/content/Badge/Badge.a11y.ts +83 -0
  12. package/src/primitives/content/Card/Card.a11y.ts +60 -0
  13. package/src/primitives/content/Chip/Chip.a11y.ts +101 -0
  14. package/src/primitives/content/Icon/Icon.a11y.ts +43 -0
  15. package/src/primitives/feedback/ProgressBar/ProgressBar.a11y.ts +68 -0
  16. package/src/primitives/feedback/Skeleton/Skeleton.a11y.ts +46 -0
  17. package/src/primitives/feedback/Spinner/Spinner.a11y.ts +47 -0
  18. package/src/primitives/feedback/Toast/Toast.a11y.ts +75 -0
  19. package/src/primitives/inputs/Checkbox/Checkbox.a11y.ts +47 -0
  20. package/src/primitives/inputs/RadioButton/RadioButton.a11y.ts +48 -0
  21. package/src/primitives/inputs/SegmentedControl/SegmentedControl.a11y.ts +59 -0
  22. package/src/primitives/inputs/SelectSheet/SelectSheet.a11y.ts +117 -0
  23. package/src/primitives/inputs/Switch/Switch.a11y.ts +29 -0
  24. package/src/primitives/inputs/TextInput/TextInput.a11y.ts +77 -0
  25. package/src/primitives/layout/Divider/Divider.a11y.ts +55 -0
  26. package/src/primitives/overlays/Modal/Modal.a11y.ts +64 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * ProgressBar Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the ProgressBar component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Progress bars need proper role and value
8
+ * - The progressbar role requires min, max, and now values
9
+ *
10
+ * The progress bar uses "progressbar" role with explicit value range
11
+ * to provide determinate progress feedback to assistive technologies.
12
+ *
13
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
14
+ * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/progressbar_role
15
+ */
16
+
17
+ import { clampProgress, progressToPercent } from "./ProgressBar.helpers";
18
+ import {
19
+ ProgressBarA11yParams,
20
+ ProgressBarA11yProps,
21
+ } from "./ProgressBar.types";
22
+
23
+ /**
24
+ * Generate accessibility props for ProgressBar.
25
+ *
26
+ * Creates a determinate progress indicator with:
27
+ * - accessibilityRole: "progressbar"
28
+ * - accessibilityValue: { min: 0, max: 100, now: current percentage }
29
+ * - accessibilityLabel: Description of what's in progress
30
+ *
31
+ * @param params - Accessibility parameters
32
+ * @returns Object to spread onto the ProgressBar component
33
+ *
34
+ * @warning Logs console warning in dev if accessibilityLabel missing
35
+ *
36
+ * @example
37
+ * const a11yProps = getProgressBarA11y({
38
+ * progress: 0.75,
39
+ * accessibilityLabel: "File upload"
40
+ * });
41
+ * // {
42
+ * // accessibilityRole: "progressbar",
43
+ * // accessibilityLabel: "File upload",
44
+ * // accessibilityValue: { min: 0, max: 100, now: 75 }
45
+ * // }
46
+ */
47
+ export function getProgressBarA11y(
48
+ params: ProgressBarA11yParams
49
+ ): ProgressBarA11yProps {
50
+ const { progress, accessibilityLabel, fallbackLabel } = params;
51
+
52
+ if (!accessibilityLabel && process.env.NODE_ENV !== "production") {
53
+ console.warn("[ProgressBar] Missing accessibilityLabel (WCAG 4.1.2)");
54
+ }
55
+
56
+ const clampedProgress = clampProgress(progress);
57
+ const progressPercent = progressToPercent(clampedProgress);
58
+
59
+ return {
60
+ accessibilityRole: "progressbar",
61
+ accessibilityLabel: accessibilityLabel || fallbackLabel,
62
+ accessibilityValue: {
63
+ min: 0,
64
+ max: 100,
65
+ now: progressPercent,
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Skeleton Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Skeleton component.
5
+ *
6
+ * WCAG References:
7
+ * - 1.3.1 Info and Relationships: Decorative content should be hidden
8
+ * - 4.1.2 Name, Role, Value: Non-interactive elements need appropriate roles
9
+ *
10
+ * Skeletons are purely decorative loading placeholders and should be
11
+ * completely hidden from assistive technologies. The actual loading
12
+ * state should be communicated through other means (e.g., live regions,
13
+ * loading spinners with proper labels).
14
+ *
15
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships
16
+ */
17
+
18
+ import { SkeletonA11yParams, SkeletonA11yProps } from "./Skeleton.types";
19
+
20
+ /**
21
+ * Generate accessibility props for Skeleton.
22
+ *
23
+ * Skeletons are hidden from screen readers as they are decorative.
24
+ * Loading state should be communicated through semantic live regions
25
+ * or dedicated loading indicators with proper accessibility labels.
26
+ *
27
+ * @param _params - Accessibility parameters (currently unused, reserved for future)
28
+ * @returns Object to spread onto the Skeleton component
29
+ *
30
+ * @example
31
+ * const a11yProps = getSkeletonA11y({});
32
+ * // {
33
+ * // accessible: false,
34
+ * // accessibilityRole: "none",
35
+ * // accessibilityElementsHidden: true,
36
+ * // importantForAccessibility: "no-hide-descendants"
37
+ * // }
38
+ */
39
+ export function getSkeletonA11y(_params: SkeletonA11yParams = {}): SkeletonA11yProps {
40
+ return {
41
+ accessible: false,
42
+ accessibilityRole: "none",
43
+ accessibilityElementsHidden: true,
44
+ importantForAccessibility: "no-hide-descendants",
45
+ };
46
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Spinner Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Spinner component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Spinners need proper role and label
8
+ * - 2.2.2 Pause, Stop, Hide: Users should be informed of loading state
9
+ *
10
+ * The spinner uses "progressbar" role with busy state to indicate
11
+ * that content is loading and the interface may change.
12
+ *
13
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
14
+ */
15
+
16
+ import { SpinnerA11yParams, SpinnerA11yProps } from "./Spinner.types";
17
+
18
+ /**
19
+ * Generate accessibility props for Spinner.
20
+ *
21
+ * Spinners are announced as indeterminate progress indicators.
22
+ * The busy state informs assistive technology that the UI is updating.
23
+ *
24
+ * @param params - Accessibility parameters
25
+ * @returns Object to spread onto the Spinner component
26
+ *
27
+ * @warning Logs console warning in dev if accessibilityLabel missing
28
+ *
29
+ * @example
30
+ * const a11yProps = getSpinnerA11y({ accessibilityLabel: "Loading messages" });
31
+ * // { accessibilityRole: "progressbar", accessibilityLabel: "Loading messages", accessibilityState: { busy: true } }
32
+ */
33
+ export function getSpinnerA11y(
34
+ params: SpinnerA11yParams
35
+ ): SpinnerA11yProps {
36
+ const { accessibilityLabel, fallbackLabel } = params;
37
+
38
+ if (!accessibilityLabel && process.env.NODE_ENV !== "production") {
39
+ console.warn("[Spinner] Missing accessibilityLabel (WCAG 4.1.2)");
40
+ }
41
+
42
+ return {
43
+ accessibilityLabel: accessibilityLabel || fallbackLabel,
44
+ accessibilityRole: "progressbar",
45
+ accessibilityState: { busy: true },
46
+ };
47
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Toast Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Toast component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Toasts need proper role and label
8
+ * - 4.1.3 Status Messages: Use aria-live regions for toast announcements
9
+ *
10
+ * The toast uses "alert" role with appropriate live region urgency:
11
+ * - Error: assertive (immediate announcement)
12
+ * - Success/Warning/Info: polite (waits for current speech to complete)
13
+ *
14
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/status-messages
15
+ */
16
+
17
+ import { ToastA11yParams, ToastA11yProps, ToastVariant } from "./Toast.types";
18
+
19
+ /**
20
+ * Determines live region urgency based on variant.
21
+ *
22
+ * @param variant - Toast variant
23
+ * @returns "assertive" for errors, "polite" for others
24
+ * @internal
25
+ */
26
+ function getLiveRegionUrgency(
27
+ variant: ToastVariant
28
+ ): "polite" | "assertive" {
29
+ // Errors should interrupt immediately for critical feedback
30
+ return variant === "error" ? "assertive" : "polite";
31
+ }
32
+
33
+ /**
34
+ * Generate accessibility props for Toast.
35
+ *
36
+ * Toasts are announced as alerts with appropriate urgency.
37
+ * Error toasts interrupt immediately; others wait politely.
38
+ *
39
+ * @param params - Accessibility parameters
40
+ * @returns Object to spread onto the Toast component
41
+ *
42
+ * @example
43
+ * const a11yProps = getToastA11y({
44
+ * variant: "success",
45
+ * message: "Changes saved",
46
+ * });
47
+ * // {
48
+ * // accessibilityRole: "alert",
49
+ * // accessibilityLabel: "Success: Changes saved",
50
+ * // accessibilityLiveRegion: "polite"
51
+ * // }
52
+ *
53
+ * @example
54
+ * const a11yProps = getToastA11y({
55
+ * variant: "error",
56
+ * message: "Failed to save",
57
+ * });
58
+ * // {
59
+ * // accessibilityRole: "alert",
60
+ * // accessibilityLabel: "Error: Failed to save",
61
+ * // accessibilityLiveRegion: "assertive"
62
+ * // }
63
+ */
64
+ export function getToastA11y(params: ToastA11yParams): ToastA11yProps {
65
+ const { variant, accessibilityLabel, message, variantPrefixes } = params;
66
+
67
+ // Build default label with translated variant prefix for context
68
+ const defaultLabel = `${variantPrefixes[variant]}: ${message}`;
69
+
70
+ return {
71
+ accessibilityLabel: accessibilityLabel ?? defaultLabel,
72
+ accessibilityRole: "alert",
73
+ accessibilityLiveRegion: getLiveRegionUrgency(variant),
74
+ };
75
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Checkbox Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Checkbox component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Checkboxes need proper role and label
8
+ * - 1.3.1 Info and Relationships: State must be programmatically determinable
9
+ *
10
+ * The checkbox uses "checkbox" role with checked state to indicate
11
+ * the current selection.
12
+ *
13
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
14
+ */
15
+
16
+ import { CheckboxA11yParams, CheckboxA11yProps } from "./Checkbox.types";
17
+
18
+ /**
19
+ * Generate accessibility props for Checkbox.
20
+ *
21
+ * @param params - Accessibility parameters
22
+ * @returns Object to spread onto the Checkbox component
23
+ *
24
+ * @warning Logs console warning in dev if both label and accessibilityLabel missing
25
+ *
26
+ * @example
27
+ * const a11yProps = getCheckboxA11y({
28
+ * label: "Accept terms",
29
+ * checked: true,
30
+ * disabled: false,
31
+ * });
32
+ * // { accessibilityRole: "checkbox", accessibilityLabel: "Accept terms", accessibilityState: { checked: true, disabled: false } }
33
+ */
34
+ export function getCheckboxA11y(params: CheckboxA11yParams): CheckboxA11yProps {
35
+ const { label, accessibilityLabel, accessibilityHint, checked, disabled } = params;
36
+
37
+ if (!accessibilityLabel && !label && process.env.NODE_ENV !== "production") {
38
+ console.warn("[Checkbox] Missing label or accessibilityLabel (WCAG 4.1.2)");
39
+ }
40
+
41
+ return {
42
+ accessibilityLabel: accessibilityLabel || label || "Checkbox",
43
+ accessibilityHint,
44
+ accessibilityRole: "checkbox",
45
+ accessibilityState: { checked, disabled },
46
+ };
47
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * RadioButton Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the RadioButton component.
5
+ * Follows WCAG 2.1 guidelines for radio button accessibility.
6
+ */
7
+
8
+ import { RadioButtonA11yParams, RadioButtonA11yProps } from "./RadioButton.types";
9
+
10
+ /**
11
+ * Generate accessibility props for RadioButton.
12
+ *
13
+ * Provides proper semantic information for screen readers:
14
+ * - Role: "radio" for proper radio button semantics
15
+ * - State: checked and disabled states
16
+ * - Label: Uses provided label or accessibility label
17
+ *
18
+ * @param params - Accessibility configuration
19
+ * @returns Accessibility props object
20
+ *
21
+ * @example
22
+ * const a11yProps = getRadioButtonA11y({
23
+ * label: "Option A",
24
+ * checked: true,
25
+ * disabled: false,
26
+ * });
27
+ * // Returns: { accessibilityRole: "radio", accessibilityState: { checked: true, disabled: false }, ... }
28
+ */
29
+ export function getRadioButtonA11y(params: RadioButtonA11yParams): RadioButtonA11yProps {
30
+ const {
31
+ label,
32
+ accessibilityLabel,
33
+ accessibilityHint,
34
+ checked,
35
+ disabled,
36
+ fallbackLabel,
37
+ } = params;
38
+
39
+ return {
40
+ accessibilityLabel: accessibilityLabel || label || fallbackLabel,
41
+ accessibilityHint,
42
+ accessibilityRole: "radio",
43
+ accessibilityState: {
44
+ checked,
45
+ disabled,
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,59 @@
1
+ import type { SegmentOption } from "./SegmentedControl.types";
2
+
3
+ /**
4
+ * Accessibility props for the SegmentedControl container
5
+ */
6
+ export function getSegmentedControlContainerA11y(
7
+ optionsCount: number,
8
+ i18n: { containerLabel: string; optionsLabel: string },
9
+ ): {
10
+ accessible: boolean;
11
+ accessibilityRole: "radiogroup";
12
+ accessibilityLabel: string;
13
+ } {
14
+ return {
15
+ accessible: true,
16
+ accessibilityRole: "radiogroup",
17
+ accessibilityLabel: `${i18n.containerLabel} ${optionsCount} ${i18n.optionsLabel}`,
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Accessibility props for individual segments
23
+ */
24
+ export function getSegmentA11y(
25
+ option: SegmentOption,
26
+ index: number,
27
+ totalOptions: number,
28
+ isSelected: boolean,
29
+ i18n: {
30
+ selectedLabel: string;
31
+ currentlySelectedHint: string;
32
+ doubleTapToSelectHint: string;
33
+ ofLabel: string;
34
+ },
35
+ ): {
36
+ accessible: boolean;
37
+ accessibilityRole: "radio";
38
+ accessibilityState: {
39
+ selected: boolean;
40
+ disabled: boolean;
41
+ };
42
+ accessibilityLabel: string;
43
+ accessibilityHint: string;
44
+ } {
45
+ const positionText = `${index + 1} ${i18n.ofLabel} ${totalOptions}`;
46
+
47
+ return {
48
+ accessible: true,
49
+ accessibilityRole: "radio",
50
+ accessibilityState: {
51
+ selected: isSelected,
52
+ disabled: option.disabled ?? false,
53
+ },
54
+ accessibilityLabel: `${option.label}, ${positionText}${isSelected ? `, ${i18n.selectedLabel}` : ""}`,
55
+ accessibilityHint: isSelected
56
+ ? i18n.currentlySelectedHint
57
+ : `${i18n.doubleTapToSelectHint} ${option.label}`,
58
+ };
59
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * SelectSheet Accessibility Helpers
3
+ *
4
+ * Accessibility utilities for the SelectSheet component.
5
+ */
6
+
7
+ import { AccessibilityProps } from "react-native";
8
+
9
+ import { SelectSheetMode, SelectSheetPosition } from "./SelectSheet.types";
10
+
11
+ /**
12
+ * Get accessibility props for the sheet container
13
+ */
14
+ export function getSheetA11yProps(params: {
15
+ title?: string;
16
+ position: SelectSheetPosition;
17
+ accessibilityLabel?: string;
18
+ positionLabels: { bottom: string; top: string; center: string };
19
+ fallbackTitle: string;
20
+ }): AccessibilityProps {
21
+ const { title, position, accessibilityLabel, positionLabels, fallbackTitle } = params;
22
+
23
+ const positionLabel = positionLabels[position];
24
+
25
+ return {
26
+ accessible: true,
27
+ accessibilityRole: "none",
28
+ accessibilityLabel:
29
+ accessibilityLabel || `${title || fallbackTitle} ${positionLabel}`,
30
+ accessibilityViewIsModal: true,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Get accessibility props for an option row
36
+ */
37
+ export function getOptionA11yProps(params: {
38
+ label: string;
39
+ isSelected: boolean;
40
+ isDisabled: boolean;
41
+ isMultiple: boolean;
42
+ index: number;
43
+ total: number;
44
+ selectedLabel: string;
45
+ disabledLabel: string;
46
+ ofLabel: string;
47
+ }): AccessibilityProps {
48
+ const { label, isSelected, isDisabled, isMultiple, index, total, selectedLabel, disabledLabel, ofLabel } = params;
49
+
50
+ const role = isMultiple ? "checkbox" : "radio";
51
+ const selectedText = isSelected ? `, ${selectedLabel}` : "";
52
+ const disabledText = isDisabled ? `, ${disabledLabel}` : "";
53
+ const positionText = `${index + 1} ${ofLabel} ${total}`;
54
+
55
+ return {
56
+ accessible: true,
57
+ accessibilityRole: role,
58
+ accessibilityLabel: `${label}${selectedText}${disabledText}, ${positionText}`,
59
+ accessibilityState: {
60
+ checked: isSelected,
61
+ disabled: isDisabled,
62
+ },
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Get accessibility props for the done button
68
+ */
69
+ export function getDoneButtonA11yProps(params: {
70
+ selectedCount: number;
71
+ mode: SelectSheetMode;
72
+ doneLabel: string;
73
+ itemsSelectedLabel: string;
74
+ doneHint: string;
75
+ }): AccessibilityProps {
76
+ const { selectedCount, mode, doneLabel, itemsSelectedLabel, doneHint } = params;
77
+
78
+ const countText =
79
+ mode === "multiple" ? ` with ${selectedCount} ${itemsSelectedLabel}` : "";
80
+
81
+ return {
82
+ accessible: true,
83
+ accessibilityRole: "button",
84
+ accessibilityLabel: `${doneLabel}${countText}`,
85
+ accessibilityHint: doneHint,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Get accessibility props for the close button
91
+ */
92
+ export function getCloseButtonA11yProps(params: {
93
+ closeLabel: string;
94
+ closeHint: string;
95
+ }): AccessibilityProps {
96
+ return {
97
+ accessible: true,
98
+ accessibilityRole: "button",
99
+ accessibilityLabel: params.closeLabel,
100
+ accessibilityHint: params.closeHint,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Get announcement for selection change
106
+ */
107
+ export function getSelectionAnnouncement(
108
+ label: string,
109
+ isSelected: boolean,
110
+ isMultiple: boolean,
111
+ i18n: { selectedLabel: string; deselectedLabel: string },
112
+ ): string {
113
+ if (isMultiple) {
114
+ return isSelected ? `${label} ${i18n.selectedLabel}` : `${label} ${i18n.deselectedLabel}`;
115
+ }
116
+ return `${label} ${i18n.selectedLabel}`;
117
+ }
@@ -0,0 +1,29 @@
1
+ import { SwitchA11yParams, SwitchA11yProps } from "./Switch.types";
2
+
3
+ /**
4
+ * Get accessibility props for the Switch component.
5
+ *
6
+ * Implements WCAG 4.1.2 (Name, Role, Value) requirements:
7
+ * - accessibilityRole: "switch" for proper identification
8
+ * - accessibilityLabel: Required for screen readers (warns if missing)
9
+ * - accessibilityHint: Optional additional context
10
+ * - accessibilityState: Communicates checked and disabled states
11
+ *
12
+ * @param params - Accessibility parameters
13
+ * @returns Accessibility props for the switch
14
+ */
15
+ export function getSwitchA11y(params: SwitchA11yParams): SwitchA11yProps {
16
+ const { label, accessibilityLabel, accessibilityHint, value, disabled } =
17
+ params;
18
+
19
+ if (!accessibilityLabel && !label && process.env.NODE_ENV !== "production") {
20
+ console.warn("[Switch] Missing label or accessibilityLabel (WCAG 4.1.2)");
21
+ }
22
+
23
+ return {
24
+ accessibilityLabel: accessibilityLabel || label || "Switch",
25
+ accessibilityHint,
26
+ accessibilityRole: "switch",
27
+ accessibilityState: { checked: value, disabled },
28
+ };
29
+ }
@@ -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
+ }