@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.
- package/package.json +11 -12
- package/src/README.md +688 -0
- package/src/components/CalendarStrip/CalendarStrip.a11y.ts +51 -0
- package/src/components/CalendarStrip/DayCard/DayCard.a11y.ts +52 -0
- package/src/components/EmptyState/EmptyState.a11y.ts +53 -0
- package/src/components/Header/Header.a11y.ts +82 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.a11y.ts +15 -0
- package/src/primitives/actions/Button/Button.a11y.ts +38 -0
- package/src/primitives/actions/IconButton/IconButton.a11y.ts +55 -0
- package/src/primitives/content/Avatar/Avatar.a11y.ts +50 -0
- package/src/primitives/content/Badge/Badge.a11y.ts +83 -0
- package/src/primitives/content/Card/Card.a11y.ts +60 -0
- package/src/primitives/content/Chip/Chip.a11y.ts +101 -0
- package/src/primitives/content/Icon/Icon.a11y.ts +43 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.a11y.ts +68 -0
- package/src/primitives/feedback/Skeleton/Skeleton.a11y.ts +46 -0
- package/src/primitives/feedback/Spinner/Spinner.a11y.ts +47 -0
- package/src/primitives/feedback/Toast/Toast.a11y.ts +75 -0
- package/src/primitives/inputs/Checkbox/Checkbox.a11y.ts +47 -0
- package/src/primitives/inputs/RadioButton/RadioButton.a11y.ts +48 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.a11y.ts +59 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.a11y.ts +117 -0
- package/src/primitives/inputs/Switch/Switch.a11y.ts +29 -0
- package/src/primitives/inputs/TextInput/TextInput.a11y.ts +77 -0
- package/src/primitives/layout/Divider/Divider.a11y.ts +55 -0
- package/src/primitives/overlays/Modal/Modal.a11y.ts +64 -0
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|