@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,101 @@
1
+ /**
2
+ * Chip Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Chip component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Provide semantic role and state
8
+ * - 1.3.1 Info and Relationships: Convey selected state
9
+ *
10
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
11
+ */
12
+
13
+ import { ChipA11yParams, ChipA11yProps } from "./Chip.types";
14
+
15
+ /**
16
+ * Get accessibility props for the Chip component.
17
+ *
18
+ * Implements WCAG 4.1.2 (Name, Role, Value) requirements:
19
+ * - accessibilityRole: "checkbox" for selectable, "button" for action/dismissible
20
+ * - accessibilityLabel: Required for screen readers
21
+ * - accessibilityHint: Optional additional context
22
+ * - accessibilityState: Communicates disabled and selected/checked states
23
+ *
24
+ * @param params - Accessibility parameters
25
+ * @returns Accessibility props for the chip
26
+ *
27
+ * @example
28
+ * // Selectable chip
29
+ * const a11yProps = getChipA11y({
30
+ * label: "Option A",
31
+ * selected: true,
32
+ * isSelectable: true,
33
+ * isDismissible: false,
34
+ * disabled: false,
35
+ * });
36
+ * // {
37
+ * // accessibilityRole: "checkbox",
38
+ * // accessibilityLabel: "Option A",
39
+ * // accessibilityState: { disabled: false, checked: true }
40
+ * // }
41
+ *
42
+ * @example
43
+ * // Dismissible chip
44
+ * const a11yProps = getChipA11y({
45
+ * label: "Filter",
46
+ * selected: false,
47
+ * isSelectable: false,
48
+ * isDismissible: true,
49
+ * disabled: false,
50
+ * });
51
+ * // {
52
+ * // accessibilityRole: "button",
53
+ * // accessibilityLabel: "Filter",
54
+ * // accessibilityHint: "Double tap to remove",
55
+ * // accessibilityState: { disabled: false }
56
+ * // }
57
+ */
58
+ export function getChipA11y(params: ChipA11yParams): ChipA11yProps {
59
+ const {
60
+ label,
61
+ accessibilityLabel,
62
+ accessibilityHint,
63
+ disabled,
64
+ selected,
65
+ isSelectable,
66
+ isDismissible,
67
+ toggleHint,
68
+ removeHint,
69
+ } = params;
70
+
71
+ if (!label && process.env.NODE_ENV !== "production") {
72
+ console.warn("[Chip] Missing label (WCAG 4.1.2)");
73
+ }
74
+
75
+ const finalLabel = accessibilityLabel || label;
76
+
77
+ // Selectable chips use checkbox role
78
+ if (isSelectable) {
79
+ return {
80
+ accessibilityRole: "checkbox",
81
+ accessibilityLabel: finalLabel,
82
+ accessibilityHint: accessibilityHint || toggleHint,
83
+ accessibilityState: {
84
+ disabled,
85
+ checked: selected,
86
+ },
87
+ };
88
+ }
89
+
90
+ // Dismissible and action chips use button role
91
+ return {
92
+ accessibilityRole: "button",
93
+ accessibilityLabel: finalLabel,
94
+ accessibilityHint:
95
+ accessibilityHint || (isDismissible ? removeHint : undefined),
96
+ accessibilityState: {
97
+ disabled,
98
+ selected: isSelectable ? selected : undefined,
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Icon Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Icon component.
5
+ *
6
+ * WCAG References:
7
+ * - 1.1.1 Non-text Content: Icons need text alternatives
8
+ * - 4.1.2 Name, Role, Value: Proper role and label required
9
+ *
10
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/non-text-content
11
+ */
12
+
13
+ import { IconA11yParams, IconA11yProps } from "./Icon.types";
14
+
15
+ /**
16
+ * Generate accessibility props for Icon.
17
+ *
18
+ * Icons are treated as images for accessibility purposes.
19
+ * Decorative icons (without accessibilityLabel) will still
20
+ * be announced but with no descriptive text.
21
+ *
22
+ * @param params - Accessibility parameters
23
+ * @returns Object to spread onto the Icon component
24
+ *
25
+ * @warning Logs console warning in dev if accessibilityLabel missing
26
+ *
27
+ * @example
28
+ * const a11yProps = getIconA11y({ accessibilityLabel: "Settings" });
29
+ * // { accessibilityRole: "image", accessibilityLabel: "Settings" }
30
+ */
31
+ export function getIconA11y(params: IconA11yParams): IconA11yProps {
32
+ const { accessibilityLabel, accessibilityHint } = params;
33
+
34
+ if (!accessibilityLabel && process.env.NODE_ENV !== "production") {
35
+ console.warn("[Icon] Missing accessibilityLabel (WCAG 4.1.2)");
36
+ }
37
+
38
+ return {
39
+ accessibilityRole: "image",
40
+ accessibilityLabel,
41
+ accessibilityHint,
42
+ };
43
+ }
@@ -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
+ }