@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,51 @@
1
+ /**
2
+ * CalendarStrip Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the CalendarStrip container.
5
+ *
6
+ * WCAG References:
7
+ * - 1.3.1 Info and Relationships: Date range context must be conveyed to AT
8
+ * - 4.1.2 Name, Role, Value: Landmark regions need descriptive labels
9
+ *
10
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
11
+ */
12
+
13
+ /**
14
+ * Generate accessibility props for the CalendarStrip container.
15
+ *
16
+ * Accepts pre-translated strings so the function stays pure and i18n-agnostic.
17
+ *
18
+ * @example
19
+ * getCalendarStripA11y({
20
+ * label: t.calendar.datePicker.label,
21
+ * selectedLabel: t.calendar.datePicker.selected,
22
+ * daysShownLabel: t.calendar.datePicker.daysShown,
23
+ * selectedDate: new Date(2026, 1, 19),
24
+ * dateCount: 7,
25
+ * });
26
+ * // { accessible: true, accessibilityRole: "list", accessibilityLabel: "Date picker, Feb 19 selected, 7 days shown" }
27
+ */
28
+ export function getCalendarStripA11y({
29
+ label,
30
+ selectedLabel,
31
+ daysShownLabel,
32
+ selectedDate,
33
+ dateCount,
34
+ }: {
35
+ label: string;
36
+ selectedLabel: string;
37
+ daysShownLabel: string;
38
+ selectedDate: Date;
39
+ dateCount: number;
40
+ }) {
41
+ const formatted = selectedDate.toLocaleDateString([], {
42
+ month: "short",
43
+ day: "numeric",
44
+ });
45
+
46
+ return {
47
+ accessible: true,
48
+ accessibilityRole: "list" as const,
49
+ accessibilityLabel: `${label}, ${formatted} ${selectedLabel}, ${dateCount} ${daysShownLabel}`,
50
+ };
51
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * DayCard Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the DayCard component.
5
+ *
6
+ * WCAG References:
7
+ * - 1.3.1 Info and Relationships: Date context must be conveyed to AT
8
+ * - 4.1.2 Name, Role, Value: Interactive elements need role, label, and state
9
+ *
10
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
11
+ */
12
+
13
+ import { DayCardA11yParams, DayCardA11yProps } from "./DayCard.types";
14
+
15
+ /**
16
+ * Generate accessibility props for a DayCard.
17
+ *
18
+ * Combines day, month and selection state into a descriptive label
19
+ * so screen readers announce the full date and its status.
20
+ *
21
+ * @param params - Date context and state for the card
22
+ * @returns Props to spread onto the DayCard Pressable
23
+ *
24
+ * @example
25
+ * const a11yProps = getDayCardA11y({
26
+ * date: new Date(2026, 1, 19),
27
+ * isSelected: true,
28
+ * isToday: false,
29
+ * dayLabel: "Thu",
30
+ * monthLabel: "Feb",
31
+ * });
32
+ * // {
33
+ * // accessible: true,
34
+ * // accessibilityRole: "button",
35
+ * // accessibilityLabel: "Thu 19 Feb, selected",
36
+ * // accessibilityState: { selected: true },
37
+ * // }
38
+ */
39
+ export function getDayCardA11y(params: DayCardA11yParams): DayCardA11yProps {
40
+ const { date, isSelected, isToday, dayLabel, monthLabel, todayLabel, selectedLabel } = params;
41
+
42
+ const parts: string[] = [dayLabel, String(date.getDate()), monthLabel];
43
+ if (isToday) parts.push(todayLabel);
44
+ if (isSelected) parts.push(selectedLabel);
45
+
46
+ return {
47
+ accessible: true,
48
+ accessibilityRole: "button",
49
+ accessibilityLabel: parts.join(" "),
50
+ accessibilityState: { selected: isSelected },
51
+ };
52
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * EmptyState Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the EmptyState component.
5
+ *
6
+ * WCAG References:
7
+ * - 1.3.1 Info and Relationships: Content structure must be conveyed
8
+ * - 4.1.2 Name, Role, Value: Informational content needs appropriate labels
9
+ *
10
+ * EmptyState components communicate important status information to users
11
+ * and should be accessible to screen readers with proper labels.
12
+ *
13
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships
14
+ */
15
+
16
+ import { EmptyStateA11yParams, EmptyStateA11yProps } from "./EmptyState.types";
17
+
18
+ /**
19
+ * Generate accessibility props for EmptyState.
20
+ *
21
+ * Combines title and description into a comprehensive accessibility label
22
+ * for screen readers. The empty state is treated as an informational text
23
+ * region.
24
+ *
25
+ * @param params - Accessibility parameters
26
+ * @returns Object to spread onto the EmptyState container
27
+ *
28
+ * @example
29
+ * const a11yProps = getEmptyStateA11y({
30
+ * title: "No items found",
31
+ * description: "Try adjusting your search"
32
+ * });
33
+ * // {
34
+ * // accessible: true,
35
+ * // accessibilityRole: "text",
36
+ * // accessibilityLabel: "No items found. Try adjusting your search"
37
+ * // }
38
+ */
39
+ export function getEmptyStateA11y(params: EmptyStateA11yParams): EmptyStateA11yProps {
40
+ const { title, description, accessibilityLabel } = params;
41
+
42
+ // Use custom label if provided, otherwise combine title and description
43
+ let label = accessibilityLabel;
44
+ if (!label) {
45
+ label = description ? `${title}. ${description}` : title;
46
+ }
47
+
48
+ return {
49
+ accessible: true,
50
+ accessibilityRole: "text",
51
+ accessibilityLabel: label,
52
+ };
53
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Header Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Header component.
5
+ *
6
+ * WCAG References:
7
+ * - 1.3.1 Info and Relationships: Headers need proper role
8
+ * - 2.4.6 Headings and Labels: Descriptive labels
9
+ * - 4.1.2 Name, Role, Value: Back button needs label
10
+ *
11
+ * The header uses "header" role to indicate a page/section heading.
12
+ * The back button uses "button" role with translated labels.
13
+ *
14
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/headings-and-labels
15
+ */
16
+
17
+ import { AccessibilityRole } from "react-native";
18
+ import {
19
+ HeaderA11yParams,
20
+ HeaderA11yProps,
21
+ HeaderBackButtonA11yParams,
22
+ } from "./Header.types";
23
+
24
+ /**
25
+ * Generate accessibility props for the back button.
26
+ *
27
+ * @param params - Back button accessibility parameters
28
+ * @returns Object to spread onto the back button
29
+ *
30
+ * @warning Logs console warning in dev if accessibilityLabel missing
31
+ *
32
+ * @example
33
+ * const a11yProps = getHeaderBackButtonA11y({
34
+ * accessibilityLabel: "Go back",
35
+ * accessibilityHint: "Returns to previous screen",
36
+ * });
37
+ */
38
+ export function getHeaderBackButtonA11y(
39
+ params: HeaderBackButtonA11yParams,
40
+ ): HeaderA11yProps {
41
+ const { accessibilityLabel, accessibilityHint } = params;
42
+
43
+ if (!accessibilityLabel && process.env.NODE_ENV !== "production") {
44
+ console.warn(
45
+ "[Header] Missing accessibilityLabel for back button (WCAG 4.1.2)",
46
+ );
47
+ }
48
+
49
+ return {
50
+ accessibilityRole: "button" as AccessibilityRole,
51
+ accessibilityLabel,
52
+ accessibilityHint,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Generate accessibility props for the header container.
58
+ *
59
+ * @param params - Header accessibility parameters
60
+ * @returns Object to spread onto the header container
61
+ *
62
+ * @warning Logs console warning in dev if accessibilityLabel missing
63
+ *
64
+ * @example
65
+ * const a11yProps = getHeaderA11y({
66
+ * accessibilityLabel: "Settings page header",
67
+ * });
68
+ * // { accessibilityRole: "header", accessibilityLabel: "Settings page header" }
69
+ */
70
+ export function getHeaderA11y(params: HeaderA11yParams): HeaderA11yProps {
71
+ const { accessibilityLabel, accessibilityHint } = params;
72
+
73
+ if (!accessibilityLabel && process.env.NODE_ENV !== "production") {
74
+ console.warn("[Header] Missing accessibilityLabel (WCAG 4.1.2)");
75
+ }
76
+
77
+ return {
78
+ accessibilityRole: "header",
79
+ accessibilityLabel,
80
+ accessibilityHint,
81
+ };
82
+ }
@@ -0,0 +1,15 @@
1
+ import type {
2
+ ScheduleItemA11yParams,
3
+ ScheduleItemA11yProps,
4
+ } from "./ScheduleItem.types";
5
+
6
+ export function getScheduleItemA11y(
7
+ params: ScheduleItemA11yParams,
8
+ ): ScheduleItemA11yProps {
9
+ const { title, timeRange, onPress } = params;
10
+ return {
11
+ accessible: true,
12
+ accessibilityRole: onPress ? "button" : "text",
13
+ accessibilityLabel: `${title}, ${timeRange}`,
14
+ };
15
+ }
package/src/core/index.ts CHANGED
@@ -23,7 +23,7 @@ export {
23
23
  restyleTheme,
24
24
  ThemeProvider,
25
25
  useRestyleTheme,
26
- buildPair,
26
+ createThemePair,
27
27
  buildRestyleTheme,
28
28
  buildRestyleThemeFromThemeColors,
29
29
  restylePresetThemes,
@@ -32,7 +32,7 @@ export {
32
32
  export { useRestyleTheme } from "./useRestyleTheme";
33
33
 
34
34
  export {
35
- buildPair,
35
+ createThemePair,
36
36
  restylePresetThemes,
37
37
  type RestyleThemePair,
38
38
  } from "./restylePresetRegistry";
@@ -22,11 +22,11 @@ export type RestyleThemePair = {
22
22
  };
23
23
 
24
24
  /**
25
- * Build light + dark Restyle themes from ThemeColors overrides.
25
+ * Create light + dark Restyle themes from ThemeColors overrides.
26
26
  * The most convenient way to create a custom theme pair — only specify
27
27
  * what differs from the default semantic colors.
28
28
  */
29
- export function buildPair(
29
+ export function createThemePair(
30
30
  lightOverrides: DeepPartial<ThemeColors>,
31
31
  darkOverrides: DeepPartial<ThemeColors>,
32
32
  ): RestyleThemePair {
@@ -43,7 +43,7 @@ const horizon: RestyleThemePair = {
43
43
  };
44
44
 
45
45
  // Sage — green accents
46
- const sage = buildPair(
46
+ const sage = createThemePair(
47
47
  {
48
48
  accent: {
49
49
  primary: "#6B8F6B",
@@ -71,7 +71,7 @@ const sage = buildPair(
71
71
  );
72
72
 
73
73
  // Sunset — warm coral accents
74
- const sunset = buildPair(
74
+ const sunset = createThemePair(
75
75
  {
76
76
  accent: {
77
77
  primary: "#E8836B",
@@ -99,7 +99,7 @@ const sunset = buildPair(
99
99
  );
100
100
 
101
101
  // Ocean — teal accents
102
- const ocean = buildPair(
102
+ const ocean = createThemePair(
103
103
  {
104
104
  accent: {
105
105
  primary: "#0E9AA5",
@@ -127,7 +127,7 @@ const ocean = buildPair(
127
127
  );
128
128
 
129
129
  // Lavender — purple accents
130
- const lavender = buildPair(
130
+ const lavender = createThemePair(
131
131
  {
132
132
  accent: {
133
133
  primary: "#8B5CF6",
@@ -155,7 +155,7 @@ const lavender = buildPair(
155
155
  );
156
156
 
157
157
  // Rose — pink accents
158
- const rose = buildPair(
158
+ const rose = createThemePair(
159
159
  {
160
160
  accent: {
161
161
  primary: "#E11D6C",
package/src/index.tsx CHANGED
@@ -110,7 +110,7 @@ export {
110
110
  ThemeProvider,
111
111
  useRestyleTheme,
112
112
  // Theme building utilities
113
- buildPair,
113
+ createThemePair,
114
114
  buildRestyleTheme,
115
115
  buildRestyleThemeFromThemeColors,
116
116
  restylePresetThemes,
@@ -140,19 +140,10 @@ export { en, es } from "./i18n";
140
140
  // =============================================================================
141
141
 
142
142
  export {
143
- // Theme Factory
144
- createTheme,
145
- createThemePair,
146
- // Built-in theme presets
143
+ // Default themes
147
144
  darkTheme,
148
145
  defaultTheme,
149
- horizonTheme,
150
- lavenderTheme,
151
146
  lightTheme,
152
- oceanTheme,
153
- roseTheme,
154
- sageTheme,
155
- sunsetTheme,
156
147
  type DeepPartial,
157
148
  type ShadowStyle,
158
149
  // Types
@@ -0,0 +1,38 @@
1
+ import { ButtonA11yParams, ButtonA11yProps } from "./Button.types";
2
+
3
+ /**
4
+ * Get accessibility props for the Button component.
5
+ *
6
+ * Implements WCAG 4.1.2 (Name, Role, Value) requirements:
7
+ * - accessibilityRole: "button" for proper identification
8
+ * - accessibilityLabel: Required for screen readers (warns if missing)
9
+ * - accessibilityHint: Optional additional context
10
+ * - accessibilityState: Communicates disabled and busy states
11
+ *
12
+ * @param params - Accessibility parameters
13
+ * @returns Accessibility props for the button
14
+ */
15
+ export function getButtonA11y(params: ButtonA11yParams): ButtonA11yProps {
16
+ const { title, accessibilityLabel, accessibilityHint, disabled, isLoading } =
17
+ params;
18
+
19
+ if (!title && process.env.NODE_ENV !== "production") {
20
+ console.warn("[Button] Missing title (WCAG 4.1.2)");
21
+ }
22
+
23
+ if (!accessibilityLabel && process.env.NODE_ENV !== "production") {
24
+ console.warn(
25
+ "[Button] Missing accessibilityLabel. Using title as fallback (WCAG 4.1.2)"
26
+ );
27
+ }
28
+
29
+ return {
30
+ accessibilityRole: "button",
31
+ accessibilityLabel: accessibilityLabel || title,
32
+ accessibilityHint,
33
+ accessibilityState: {
34
+ disabled: disabled || isLoading,
35
+ busy: isLoading,
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * IconButton Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the IconButton component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Buttons need proper role and label
8
+ * - 1.1.1 Non-text Content: Icon-only buttons require text alternatives
9
+ *
10
+ * Since IconButton has no visible text, accessibilityLabel is
11
+ * REQUIRED (not optional like text buttons).
12
+ *
13
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
14
+ */
15
+
16
+ import {
17
+ IconButtonA11yParams,
18
+ IconButtonA11yProps,
19
+ } from "./IconButton.types";
20
+
21
+ /**
22
+ * Generate accessibility props for IconButton.
23
+ *
24
+ * @param params - Accessibility parameters
25
+ * @returns Object to spread onto the IconButton component
26
+ *
27
+ * @warning Logs console warning in dev if accessibilityLabel missing.
28
+ * IconButton MUST have an accessibilityLabel since there's no visible text.
29
+ *
30
+ * @example
31
+ * const a11yProps = getIconButtonA11y({
32
+ * accessibilityLabel: "Add to favorites",
33
+ * disabled: false,
34
+ * isLoading: false,
35
+ * });
36
+ */
37
+ export function getIconButtonA11y(
38
+ params: IconButtonA11yParams
39
+ ): IconButtonA11yProps {
40
+ const { accessibilityLabel, accessibilityHint, disabled, isLoading } = params;
41
+
42
+ if (!accessibilityLabel && process.env.NODE_ENV !== "production") {
43
+ console.warn("[IconButton] Missing accessibilityLabel (WCAG 4.1.2)");
44
+ }
45
+
46
+ return {
47
+ accessibilityLabel,
48
+ accessibilityHint,
49
+ accessibilityRole: "button",
50
+ accessibilityState: {
51
+ disabled: disabled || isLoading,
52
+ busy: isLoading,
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Avatar Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Avatar component.
5
+ *
6
+ * WCAG References:
7
+ * - 1.1.1 Non-text Content: Images need text alternatives
8
+ * - 4.1.2 Name, Role, Value: Provide meaningful labels
9
+ *
10
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/non-text-content
11
+ */
12
+
13
+ import { AvatarA11yParams, AvatarA11yProps } from "./Avatar.types";
14
+
15
+ /**
16
+ * Generate accessibility props for Avatar.
17
+ *
18
+ * @param params - Name, custom label, status, and pre-translated strings
19
+ * @returns Object to spread onto the Avatar component
20
+ *
21
+ * @example
22
+ * const a11yProps = getAvatarA11y({
23
+ * name: "John Doe",
24
+ * status: "online",
25
+ * fallbackLabel: "User avatar",
26
+ * statusLabels: { online: "online", offline: "offline", busy: "busy", away: "away" },
27
+ * });
28
+ * // {
29
+ * // accessible: true,
30
+ * // accessibilityRole: "image",
31
+ * // accessibilityLabel: "John Doe, online"
32
+ * // }
33
+ */
34
+ export function getAvatarA11y(params: AvatarA11yParams): AvatarA11yProps {
35
+ const { name, accessibilityLabel, status, fallbackLabel, statusLabels } = params;
36
+
37
+ // Build label: custom label > name > fallback
38
+ let label = accessibilityLabel || name || fallbackLabel;
39
+
40
+ // Append status if present
41
+ if (status !== "none") {
42
+ label = `${label}, ${statusLabels[status]}`;
43
+ }
44
+
45
+ return {
46
+ accessible: true,
47
+ accessibilityRole: "image",
48
+ accessibilityLabel: label,
49
+ };
50
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Badge Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Badge component.
5
+ *
6
+ * WCAG References:
7
+ * - 1.3.1 Info and Relationships: Convey meaning through accessible labels
8
+ * - 4.1.2 Name, Role, Value: Provide meaningful labels
9
+ *
10
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships
11
+ */
12
+
13
+ import {
14
+ BadgeA11yParams,
15
+ BadgeA11yProps,
16
+ } from "./Badge.types";
17
+
18
+ /**
19
+ * Generate accessibility props for Badge.
20
+ *
21
+ * @param params - Label, dot mode, color, custom label, and pre-translated strings
22
+ * @returns Object to spread onto the Badge component
23
+ *
24
+ * @example
25
+ * const a11yProps = getBadgeA11y({
26
+ * label: "New",
27
+ * dot: false,
28
+ * color: "primary",
29
+ * fallbackLabel: "badge",
30
+ * statusIndicatorLabel: "status indicator",
31
+ * colorLabels: { primary: "indicator", success: "success", warning: "warning", error: "error", info: "information" },
32
+ * });
33
+ * // {
34
+ * // accessible: true,
35
+ * // accessibilityRole: "text",
36
+ * // accessibilityLabel: "New"
37
+ * // }
38
+ *
39
+ * @example
40
+ * const a11yProps = getBadgeA11y({
41
+ * dot: true,
42
+ * color: "success",
43
+ * fallbackLabel: "badge",
44
+ * statusIndicatorLabel: "status indicator",
45
+ * colorLabels: { primary: "indicator", success: "success", warning: "warning", error: "error", info: "information" },
46
+ * });
47
+ * // {
48
+ * // accessible: true,
49
+ * // accessibilityRole: "text",
50
+ * // accessibilityLabel: "success status indicator"
51
+ * // }
52
+ */
53
+ export function getBadgeA11y(params: BadgeA11yParams): BadgeA11yProps {
54
+ const { label, dot, color, accessibilityLabel, fallbackLabel, statusIndicatorLabel, colorLabels } = params;
55
+
56
+ // Use custom label if provided
57
+ if (accessibilityLabel) {
58
+ return {
59
+ accessible: true,
60
+ accessibilityRole: "text",
61
+ accessibilityLabel,
62
+ };
63
+ }
64
+
65
+ // Dot mode: describe as status indicator
66
+ if (dot) {
67
+ return {
68
+ accessible: true,
69
+ accessibilityRole: "text",
70
+ accessibilityLabel: `${colorLabels[color as string] ?? String(color)} ${statusIndicatorLabel}`,
71
+ };
72
+ }
73
+
74
+ // Text/number label
75
+ const labelText =
76
+ label !== undefined && label !== null ? String(label) : fallbackLabel;
77
+
78
+ return {
79
+ accessible: true,
80
+ accessibilityRole: "text",
81
+ accessibilityLabel: labelText,
82
+ };
83
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Card Accessibility Helpers
3
+ *
4
+ * Generates accessibility props for the Card component.
5
+ *
6
+ * WCAG References:
7
+ * - 4.1.2 Name, Role, Value: Pressable cards need proper role and label
8
+ *
9
+ * Cards have conditional accessibility based on pressable state:
10
+ * - Non-pressable: role="none" (content container only)
11
+ * - Pressable: role="button" with required label
12
+ *
13
+ * @see https://www.w3.org/WAI/WCAG21/Understanding/name-role-value
14
+ */
15
+
16
+ import { CardA11yParams, CardA11yProps } from "./Card.types";
17
+
18
+ /**
19
+ * Generate accessibility props for Card.
20
+ *
21
+ * @param params - Accessibility parameters
22
+ * @returns Object to spread onto the Card component
23
+ *
24
+ * @warning Logs console warning in dev if pressable card missing accessibilityLabel
25
+ *
26
+ * @example
27
+ * // Non-pressable card
28
+ * const a11yProps = getCardA11y({ disabled: false });
29
+ * // { accessibilityRole: "none" }
30
+ *
31
+ * @example
32
+ * // Pressable card
33
+ * const a11yProps = getCardA11y({
34
+ * accessibilityLabel: "View product",
35
+ * disabled: false,
36
+ * onPress: handlePress,
37
+ * });
38
+ * // { accessibilityRole: "button", accessibilityLabel: "View product", accessibilityState: { disabled: false } }
39
+ */
40
+ export function getCardA11y(params: CardA11yParams): CardA11yProps {
41
+ const { accessibilityLabel, disabled, onPress } = params;
42
+
43
+ if (!accessibilityLabel && onPress && process.env.NODE_ENV !== "production") {
44
+ console.warn(
45
+ "[Card] Missing accessibilityLabel for pressable card (WCAG 4.1.2)"
46
+ );
47
+ }
48
+
49
+ const isPressable = !!onPress;
50
+
51
+ return {
52
+ accessibilityRole: isPressable ? "button" : "none",
53
+ accessibilityLabel: accessibilityLabel || undefined,
54
+ accessibilityState: isPressable
55
+ ? {
56
+ disabled,
57
+ }
58
+ : undefined,
59
+ };
60
+ }