@stealthscale/component-a11y 0.1.0 → 0.1.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/README.md +54 -38
- package/dist/cyclonedx/bom.json +1 -1
- package/dist/index.d.ts +47 -38
- package/dist/index.js +1 -597
- package/dist/pages/pages.json +42 -0
- package/dist/pages/roving-focus/roving-focus.js +359 -0
- package/dist/pages/roving-focus/roving-focus.props.js +3 -0
- package/dist/pages/skip-nav/skip-nav.js +181 -0
- package/dist/pages/skip-nav/skip-nav.props.js +3 -0
- package/dist/pages/visually-hidden/visually-hidden.js +203 -0
- package/dist/pages/visually-hidden/visually-hidden.props.js +3 -0
- package/dist/recipes-BM8WGKWH.js +127 -0
- package/dist/shared-UZQEmdYt.js +584 -0
- package/dist/theme.d.ts +3 -4
- package/dist/theme.js +4 -5
- package/locales/en/specimen/roving-focus.json +35 -0
- package/locales/en/specimen/skip-nav.json +14 -0
- package/locales/en/specimen/visually-hidden.json +17 -0
- package/package.json +29 -25
- package/dist/recipe-BKojQp6r.js +0 -106
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { t as __exportAll } from "../../rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { t as VisuallyHidden } from "../../shared-UZQEmdYt.js";
|
|
3
|
+
import { c } from "react/compiler-runtime";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Contained, Room, Sample, specimen, useWords } from "@stealthscale/specimen";
|
|
6
|
+
import { Button } from "@stealthscale/component-actions";
|
|
7
|
+
import { Spacer, Stack } from "@stealthscale/component-layout";
|
|
8
|
+
import { Heading, Text } from "@stealthscale/component-typography";
|
|
9
|
+
import { XIcon } from "lucide-react";
|
|
10
|
+
//#region src/visually-hidden/examples/close.example.tsx
|
|
11
|
+
var close_example_exports = /* @__PURE__ */ __exportAll({
|
|
12
|
+
Close: () => Close,
|
|
13
|
+
source: () => source$1
|
|
14
|
+
});
|
|
15
|
+
function Close() {
|
|
16
|
+
const $ = c(5);
|
|
17
|
+
const { t } = useWords("visually-hidden");
|
|
18
|
+
let t0;
|
|
19
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
20
|
+
t0 = /* @__PURE__ */ jsx(XIcon, {
|
|
21
|
+
"aria-hidden": true,
|
|
22
|
+
size: "1em"
|
|
23
|
+
});
|
|
24
|
+
$[0] = t0;
|
|
25
|
+
} else t0 = $[0];
|
|
26
|
+
let t1;
|
|
27
|
+
if ($[1] !== t) {
|
|
28
|
+
t1 = t("close");
|
|
29
|
+
$[1] = t;
|
|
30
|
+
$[2] = t1;
|
|
31
|
+
} else t1 = $[2];
|
|
32
|
+
let t2;
|
|
33
|
+
if ($[3] !== t1) {
|
|
34
|
+
t2 = /* @__PURE__ */ jsxs(Button, {
|
|
35
|
+
shape: "square",
|
|
36
|
+
variant: "outline",
|
|
37
|
+
children: [t0, /* @__PURE__ */ jsx(VisuallyHidden, { children: t1 })]
|
|
38
|
+
});
|
|
39
|
+
$[3] = t1;
|
|
40
|
+
$[4] = t2;
|
|
41
|
+
} else t2 = $[4];
|
|
42
|
+
return t2;
|
|
43
|
+
}
|
|
44
|
+
const source$1 = "import { type ReactElement } from \"react\";\n\nimport { XIcon } from \"lucide-react\";\n\nimport { Button } from \"@stealthscale/component-actions\";\nimport { useWords } from \"@stealthscale/specimen\";\n\nimport { VisuallyHidden } from \"@stealthscale/component-a11y\";\n\nexport function Close(): ReactElement {\n const { t } = useWords(\"visually-hidden\");\n\n return (\n <Button shape=\"square\" variant=\"outline\">\n <XIcon aria-hidden size=\"1em\" />\n <VisuallyHidden>{t(\"close\")}</VisuallyHidden>\n </Button>\n );\n}\n";
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/visually-hidden/examples/shortcuts.example.tsx
|
|
47
|
+
var shortcuts_example_exports = /* @__PURE__ */ __exportAll({
|
|
48
|
+
Shortcuts: () => Shortcuts,
|
|
49
|
+
source: () => source
|
|
50
|
+
});
|
|
51
|
+
function Shortcuts() {
|
|
52
|
+
const $ = c(24);
|
|
53
|
+
const { t } = useWords("visually-hidden");
|
|
54
|
+
let t0;
|
|
55
|
+
if ($[0] !== t) {
|
|
56
|
+
t0 = t("shortcuts");
|
|
57
|
+
$[0] = t;
|
|
58
|
+
$[1] = t0;
|
|
59
|
+
} else t0 = $[1];
|
|
60
|
+
let t1;
|
|
61
|
+
if ($[2] !== t0) {
|
|
62
|
+
t1 = /* @__PURE__ */ jsx(VisuallyHidden, {
|
|
63
|
+
as: Button,
|
|
64
|
+
focusable: true,
|
|
65
|
+
children: t0
|
|
66
|
+
});
|
|
67
|
+
$[2] = t0;
|
|
68
|
+
$[3] = t1;
|
|
69
|
+
} else t1 = $[3];
|
|
70
|
+
let t2;
|
|
71
|
+
if ($[4] !== t) {
|
|
72
|
+
t2 = t("invoices");
|
|
73
|
+
$[4] = t;
|
|
74
|
+
$[5] = t2;
|
|
75
|
+
} else t2 = $[5];
|
|
76
|
+
let t3;
|
|
77
|
+
if ($[6] !== t2) {
|
|
78
|
+
t3 = /* @__PURE__ */ jsx(Heading, {
|
|
79
|
+
as: "h3",
|
|
80
|
+
size: "sm",
|
|
81
|
+
children: t2
|
|
82
|
+
});
|
|
83
|
+
$[6] = t2;
|
|
84
|
+
$[7] = t3;
|
|
85
|
+
} else t3 = $[7];
|
|
86
|
+
let t4;
|
|
87
|
+
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
|
88
|
+
t4 = /* @__PURE__ */ jsx(Spacer, {});
|
|
89
|
+
$[8] = t4;
|
|
90
|
+
} else t4 = $[8];
|
|
91
|
+
let t5;
|
|
92
|
+
if ($[9] !== t) {
|
|
93
|
+
t5 = t("create");
|
|
94
|
+
$[9] = t;
|
|
95
|
+
$[10] = t5;
|
|
96
|
+
} else t5 = $[10];
|
|
97
|
+
let t6;
|
|
98
|
+
if ($[11] !== t5) {
|
|
99
|
+
t6 = /* @__PURE__ */ jsx(Button, {
|
|
100
|
+
size: "sm",
|
|
101
|
+
children: t5
|
|
102
|
+
});
|
|
103
|
+
$[11] = t5;
|
|
104
|
+
$[12] = t6;
|
|
105
|
+
} else t6 = $[12];
|
|
106
|
+
let t7;
|
|
107
|
+
if ($[13] !== t1 || $[14] !== t3 || $[15] !== t6) {
|
|
108
|
+
t7 = /* @__PURE__ */ jsxs(Stack, {
|
|
109
|
+
direction: "row",
|
|
110
|
+
children: [
|
|
111
|
+
t1,
|
|
112
|
+
t3,
|
|
113
|
+
t4,
|
|
114
|
+
t6
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
$[13] = t1;
|
|
118
|
+
$[14] = t3;
|
|
119
|
+
$[15] = t6;
|
|
120
|
+
$[16] = t7;
|
|
121
|
+
} else t7 = $[16];
|
|
122
|
+
let t8;
|
|
123
|
+
if ($[17] !== t) {
|
|
124
|
+
t8 = t("summary");
|
|
125
|
+
$[17] = t;
|
|
126
|
+
$[18] = t8;
|
|
127
|
+
} else t8 = $[18];
|
|
128
|
+
let t9;
|
|
129
|
+
if ($[19] !== t8) {
|
|
130
|
+
t9 = /* @__PURE__ */ jsx(Text, { children: t8 });
|
|
131
|
+
$[19] = t8;
|
|
132
|
+
$[20] = t9;
|
|
133
|
+
} else t9 = $[20];
|
|
134
|
+
let t10;
|
|
135
|
+
if ($[21] !== t7 || $[22] !== t9) {
|
|
136
|
+
t10 = /* @__PURE__ */ jsxs(Stack, {
|
|
137
|
+
gap: "md",
|
|
138
|
+
children: [t7, t9]
|
|
139
|
+
});
|
|
140
|
+
$[21] = t7;
|
|
141
|
+
$[22] = t9;
|
|
142
|
+
$[23] = t10;
|
|
143
|
+
} else t10 = $[23];
|
|
144
|
+
return t10;
|
|
145
|
+
}
|
|
146
|
+
const source = "import { type ReactElement } from \"react\";\n\nimport { Button } from \"@stealthscale/component-actions\";\nimport { Spacer, Stack } from \"@stealthscale/component-layout\";\nimport { Heading, Text } from \"@stealthscale/component-typography\";\nimport { useWords } from \"@stealthscale/specimen\";\n\nimport { VisuallyHidden } from \"@stealthscale/component-a11y\";\n\nexport function Shortcuts(): ReactElement {\n const { t } = useWords(\"visually-hidden\");\n\n return (\n <Stack gap=\"md\">\n <Stack direction=\"row\">\n <VisuallyHidden as={Button} focusable>\n {t(\"shortcuts\")}\n </VisuallyHidden>\n <Heading as=\"h3\" size=\"sm\">\n {t(\"invoices\")}\n </Heading>\n <Spacer />\n <Button size=\"sm\">{t(\"create\")}</Button>\n </Stack>\n <Text>{t(\"summary\")}</Text>\n </Stack>\n );\n}\n";
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/visually-hidden/visually-hidden.specimen.tsx
|
|
149
|
+
/**
|
|
150
|
+
* Renders the close button at its own width in a sample.
|
|
151
|
+
*/
|
|
152
|
+
function Named() {
|
|
153
|
+
const $ = c(1);
|
|
154
|
+
let t0;
|
|
155
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
156
|
+
t0 = /* @__PURE__ */ jsx(Sample, { children: /* @__PURE__ */ jsx(Close, {}) });
|
|
157
|
+
$[0] = t0;
|
|
158
|
+
} else t0 = $[0];
|
|
159
|
+
return t0;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Renders the page section in a 512px room that contains its fixed control.
|
|
163
|
+
*/
|
|
164
|
+
function Focusable() {
|
|
165
|
+
const $ = c(1);
|
|
166
|
+
let t0;
|
|
167
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
168
|
+
t0 = /* @__PURE__ */ jsx(Room, {
|
|
169
|
+
size: "lg",
|
|
170
|
+
children: /* @__PURE__ */ jsx(Contained, { children: /* @__PURE__ */ jsx(Shortcuts, {}) })
|
|
171
|
+
});
|
|
172
|
+
$[0] = t0;
|
|
173
|
+
} else t0 = $[0];
|
|
174
|
+
return t0;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Hand-written scene for hidden text that names an icon-only button.
|
|
178
|
+
*/
|
|
179
|
+
const named = {
|
|
180
|
+
about: "visually-hidden.name.about",
|
|
181
|
+
draw: Named,
|
|
182
|
+
example: close_example_exports,
|
|
183
|
+
title: "visually-hidden.name.title"
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Hand-written scene for a hidden control that appears under keyboard focus.
|
|
187
|
+
*/
|
|
188
|
+
const focusable = {
|
|
189
|
+
about: "visually-hidden.focusable.about",
|
|
190
|
+
axes: ["focusable"],
|
|
191
|
+
draw: Focusable,
|
|
192
|
+
example: shortcuts_example_exports,
|
|
193
|
+
title: "visually-hidden.focusable.title"
|
|
194
|
+
};
|
|
195
|
+
var visually_hidden_specimen_default = specimen({
|
|
196
|
+
about: "visually-hidden.about",
|
|
197
|
+
id: "components/a11y/visually-hidden",
|
|
198
|
+
imports: "import { VisuallyHidden } from \"@stealthscale/component-a11y\";",
|
|
199
|
+
scenes: [named, focusable],
|
|
200
|
+
title: "visually-hidden.title"
|
|
201
|
+
});
|
|
202
|
+
//#endregion
|
|
203
|
+
export { visually_hidden_specimen_default as default, focusable, named };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const dropped = {"VisuallyHiddenProps": {"conditions": 284, "foreign": 1041}};
|
|
2
|
+
export const parts = {"VisuallyHiddenProps": [{"accepts": "boolean", "fallback": "", "kind": "variant", "name": "focusable", "refers": [], "required": false, "says": "Reveals the element under keyboard focus, on the `fill.surface` layer style at the window's start corner."}]};
|
|
3
|
+
export const shapes = {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { defineRecipe, defineSlotRecipe, dense } from "@stealthscale/theme/authoring";
|
|
2
|
+
//#region src/roving-focus/recipe.ts
|
|
3
|
+
/**
|
|
4
|
+
* Styles a roving focus group as a flex row or column in the direction its arrow keys move.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* One `orientation` axis sets both the layout and the arrow keys, so a column never responds to
|
|
8
|
+
* the left and right arrows. `both` wraps the row, because a group navigable on both axes runs
|
|
9
|
+
* onto more lines. The recipe has no `palette` or `effect` axis, because the group renders no
|
|
10
|
+
* box of its own.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Defaults to a horizontal group.
|
|
14
|
+
*/
|
|
15
|
+
const recipe$2 = defineSlotRecipe({
|
|
16
|
+
base: {
|
|
17
|
+
item: { minInlineSize: "0" },
|
|
18
|
+
root: { display: "flex" }
|
|
19
|
+
},
|
|
20
|
+
className: "roving-focus",
|
|
21
|
+
defaultVariants: { orientation: "horizontal" },
|
|
22
|
+
jsx: [/^RovingFocus(\.\w+)?$/u],
|
|
23
|
+
slots: ["root", "item"],
|
|
24
|
+
variants: {
|
|
25
|
+
/**
|
|
26
|
+
* Flex direction of the root, matching the arrow keys the group handles.
|
|
27
|
+
*/
|
|
28
|
+
orientation: {
|
|
29
|
+
both: { root: {
|
|
30
|
+
flexDirection: "row",
|
|
31
|
+
flexWrap: "wrap"
|
|
32
|
+
} },
|
|
33
|
+
horizontal: { root: { flexDirection: "row" } },
|
|
34
|
+
vertical: { root: { flexDirection: "column" } }
|
|
35
|
+
} }
|
|
36
|
+
});
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/skip-nav/recipe.ts
|
|
39
|
+
/**
|
|
40
|
+
* Styles a skip link that is clipped until keyboard focus, and the target it moves focus to.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* The link applies the `srOnly` utility at rest. Under `:focus-visible` it resets each property
|
|
44
|
+
* the clipping sets and fixes itself to the window's start corner on the `fill.surface` layer
|
|
45
|
+
* style. It does not use `srOnly: false`, which expands to `position: static` and `padding: 0`
|
|
46
|
+
* and conflicts with the fixed position and the padding. The target sets a scroll margin, so a
|
|
47
|
+
* sticky header does not cover it. The recipe has no axis, because the link has one look and the
|
|
48
|
+
* target renders nothing.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Clips the link at rest and offsets the target from the top of the scroll container.
|
|
52
|
+
*/
|
|
53
|
+
const recipe$1 = defineSlotRecipe({
|
|
54
|
+
base: {
|
|
55
|
+
link: {
|
|
56
|
+
_focusVisible: {
|
|
57
|
+
layerStyle: "fill.surface",
|
|
58
|
+
textStyle: "label.md",
|
|
59
|
+
blockSize: "auto",
|
|
60
|
+
borderRadius: "l2",
|
|
61
|
+
clip: "auto",
|
|
62
|
+
inlineSize: "auto",
|
|
63
|
+
insetBlockStart: dense("{spacing.inset.md}"),
|
|
64
|
+
insetInlineStart: dense("{spacing.inset.md}"),
|
|
65
|
+
margin: "0",
|
|
66
|
+
overflow: "visible",
|
|
67
|
+
paddingBlock: dense("{spacing.inset.sm}"),
|
|
68
|
+
paddingInline: dense("{spacing.inset.md}"),
|
|
69
|
+
position: "fixed",
|
|
70
|
+
zIndex: "skipNav"
|
|
71
|
+
},
|
|
72
|
+
srOnly: true
|
|
73
|
+
},
|
|
74
|
+
target: { scrollMarginBlockStart: dense("{spacing.inset.lg}") }
|
|
75
|
+
},
|
|
76
|
+
className: "skip-nav",
|
|
77
|
+
jsx: [/^SkipNav(\.\w+)?$/u],
|
|
78
|
+
slots: ["link", "target"]
|
|
79
|
+
});
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/visually-hidden/recipe.ts
|
|
82
|
+
/**
|
|
83
|
+
* Styles content that a screen reader announces and the browser does not paint.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* The base applies the `srOnly` utility, which clips the element to 1px and keeps it in the
|
|
87
|
+
* accessibility tree. `display: none` and `visibility: hidden` remove it from the tree. The
|
|
88
|
+
* `focusable` value cancels the clipping under `:focus-visible` and fixes the element to the
|
|
89
|
+
* window's start corner, so a keyboard user sees the control that holds focus. The recipe has no
|
|
90
|
+
* `palette` or `effect` axis, because the element renders nothing at rest.
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* Clips the element at rest.
|
|
94
|
+
*/
|
|
95
|
+
const recipe = defineRecipe({
|
|
96
|
+
base: { srOnly: true },
|
|
97
|
+
className: "visually-hidden",
|
|
98
|
+
jsx: [/^VisuallyHidden$/u],
|
|
99
|
+
variants: {
|
|
100
|
+
/**
|
|
101
|
+
* Reveals the element under keyboard focus, on the `fill.surface` layer style at the window's
|
|
102
|
+
* start corner.
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* The value resets each property the clipping sets. It does not use `srOnly: false`, which
|
|
106
|
+
* expands to `position: static` and `padding: 0` and conflicts with the fixed position and
|
|
107
|
+
* the padding.
|
|
108
|
+
*/
|
|
109
|
+
focusable: { true: { _focusVisible: {
|
|
110
|
+
layerStyle: "fill.surface",
|
|
111
|
+
textStyle: "label.md",
|
|
112
|
+
blockSize: "auto",
|
|
113
|
+
borderRadius: "l1",
|
|
114
|
+
clip: "auto",
|
|
115
|
+
inlineSize: "auto",
|
|
116
|
+
insetBlockStart: dense("{spacing.inset.md}"),
|
|
117
|
+
insetInlineStart: dense("{spacing.inset.md}"),
|
|
118
|
+
margin: "0",
|
|
119
|
+
overflow: "visible",
|
|
120
|
+
paddingBlock: dense("{spacing.inset.sm}"),
|
|
121
|
+
paddingInline: dense("{spacing.inset.md}"),
|
|
122
|
+
position: "fixed",
|
|
123
|
+
zIndex: "skipNav"
|
|
124
|
+
} } } }
|
|
125
|
+
});
|
|
126
|
+
//#endregion
|
|
127
|
+
export { recipe$1 as n, recipe$2 as r, recipe as t };
|