@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.
- package/dist/index.d.mts +3 -111
- package/dist/index.d.ts +3 -111
- package/dist/index.js +6 -200
- package/dist/index.mjs +6 -192
- 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/core/index.ts +1 -1
- package/src/core/restyle/index.ts +1 -1
- package/src/core/restyle/restylePresetRegistry.ts +7 -7
- package/src/index.tsx +2 -11
- 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
- package/src/providers/ThemeProvider/index.ts +0 -12
- package/src/providers/index.ts +0 -8
- 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
|
@@ -22,11 +22,11 @@ export type RestyleThemePair = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
+
}
|