Skip to content

Commit d46b558

Browse files
authored
feat(VCST-4543): update VcNavButton a11y (#2161)
## Description ## References ### Jira-link: <!-- Put link to your task in Jira here --> https://virtocommerce.atlassian.net/browse/VCST-4543 ### Artifact URL: https://vc3prerelease.blob.core.windows.net/packages/vc-theme-b2b-vue-2.41.0-pr-2161-21bf-21bf6fb9.zip <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Updates runtime props/aria behavior of `VcNavButton` and introduces `vue-i18n` lookups, which could affect existing consumers if i18n keys/providers are missing or styling expectations change. > > **Overview** > **`VcNavButton` now generates an accessible name by default.** The button’s `aria-label` is computed from direction-based `vue-i18n` keys (with optional `label` override), and the icon is marked `aria-hidden` with a new `icon` prop to override the default chevron. > > **Disabled/focus behavior is tightened.** Adds `aria-disabled`, improves focus-visible ring styling, disables hover styling when disabled, and adjusts disabled coloring/cursor. > > **Storybook coverage is expanded.** Stories add controls for `disabled`/`label`/`icon`, wire up click actions, enable a11y rules, and include new variants (sizes, directions, disabled, custom label/icon, and combined state grids) with explicit docs source snippets. > > **Locales updated.** Adds `ui_kit.accessibility.nav_previous/nav_next/nav_up/nav_down` translations across supported languages. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 21bf6fb. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 98af7c9 commit d46b558

File tree

15 files changed

+376
-31
lines changed

15 files changed

+376
-31
lines changed

client-app/ui-kit/components/molecules/nav-button/vc-nav-button.stories.ts

Lines changed: 282 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { VcNavButton } from "..";
22
import type { Meta, StoryObj } from "@storybook/vue3-vite";
33

4-
const SIZES = ["xs", "sm", "md"];
5-
const DIRECTIONS = ["right", "left", "up", "down"];
4+
const SIZES = ["xs", "sm", "md"] as const;
5+
const DIRECTIONS = ["right", "left", "up", "down"] as const;
66

77
const meta: Meta<typeof VcNavButton> = {
88
title: "Components/Molecules/VcNavButton",
@@ -28,38 +28,316 @@ const meta: Meta<typeof VcNavButton> = {
2828
},
2929
},
3030
},
31+
disabled: {
32+
control: "boolean",
33+
type: { name: "boolean", required: false },
34+
table: {
35+
type: {
36+
summary: "boolean",
37+
},
38+
defaultValue: { summary: "false" },
39+
},
40+
},
41+
label: {
42+
control: "text",
43+
type: { name: "string", required: false },
44+
description: "Custom accessible label (overrides default direction-based label)",
45+
table: {
46+
type: {
47+
summary: "string",
48+
},
49+
},
50+
},
51+
icon: {
52+
control: "text",
53+
type: { name: "string", required: false },
54+
description: "Custom icon name (overrides default chevron icon)",
55+
table: {
56+
type: {
57+
summary: "string",
58+
},
59+
},
60+
},
61+
onClick: { action: "click" },
62+
},
63+
args: {
64+
size: "md",
65+
direction: "right",
66+
disabled: false,
3167
},
3268
render: (args) => ({
69+
components: { VcNavButton },
3370
setup: () => ({ args }),
34-
template: '<VcNavButton v-bind="args" />',
71+
template: '<VcNavButton v-bind="args" @click="args.onClick" />',
3572
}),
73+
parameters: {
74+
a11y: {
75+
config: {
76+
rules: [
77+
{
78+
// Icon-only buttons must have accessible names
79+
id: "button-name",
80+
enabled: true,
81+
},
82+
],
83+
},
84+
},
85+
},
3686
};
3787

3888
export default meta;
3989
type StoryType = StoryObj<typeof meta>;
4090

41-
export const Basic: StoryType = {};
91+
export const Basic: StoryType = {
92+
parameters: {
93+
docs: {
94+
source: {
95+
code: `<VcNavButton />`,
96+
},
97+
},
98+
},
99+
};
100+
101+
export const ExtraSmall: StoryType = {
102+
args: {
103+
size: "xs",
104+
},
105+
parameters: {
106+
docs: {
107+
source: {
108+
code: `<VcNavButton size="xs" />`,
109+
},
110+
},
111+
},
112+
};
113+
114+
export const Small: StoryType = {
115+
args: {
116+
size: "sm",
117+
},
118+
parameters: {
119+
docs: {
120+
source: {
121+
code: `<VcNavButton size="sm" />`,
122+
},
123+
},
124+
},
125+
};
126+
127+
export const Medium: StoryType = {
128+
args: {
129+
size: "md",
130+
},
131+
parameters: {
132+
docs: {
133+
source: {
134+
code: `<VcNavButton size="md" />`,
135+
},
136+
},
137+
},
138+
};
42139

43140
export const Left: StoryType = {
44141
args: {
45142
direction: "left",
46143
},
144+
parameters: {
145+
docs: {
146+
source: {
147+
code: `<VcNavButton direction="left" />`,
148+
},
149+
},
150+
},
47151
};
48152

49153
export const Right: StoryType = {
50154
args: {
51155
direction: "right",
52156
},
157+
parameters: {
158+
docs: {
159+
source: {
160+
code: `<VcNavButton direction="right" />`,
161+
},
162+
},
163+
},
53164
};
54165

55166
export const Up: StoryType = {
56167
args: {
57168
direction: "up",
58169
},
170+
parameters: {
171+
docs: {
172+
source: {
173+
code: `<VcNavButton direction="up" />`,
174+
},
175+
},
176+
},
59177
};
60178

61179
export const Down: StoryType = {
62180
args: {
63181
direction: "down",
64182
},
183+
parameters: {
184+
docs: {
185+
source: {
186+
code: `<VcNavButton direction="down" />`,
187+
},
188+
},
189+
},
190+
};
191+
192+
export const Disabled: StoryType = {
193+
args: {
194+
disabled: true,
195+
},
196+
parameters: {
197+
docs: {
198+
source: {
199+
code: `<VcNavButton disabled />`,
200+
},
201+
},
202+
a11y: {
203+
config: {
204+
rules: [
205+
{
206+
id: "button-name",
207+
enabled: true,
208+
},
209+
{
210+
id: "aria-valid-attr-value",
211+
enabled: true,
212+
},
213+
],
214+
},
215+
},
216+
},
217+
};
218+
219+
export const CustomLabel: StoryType = {
220+
args: {
221+
label: "Go to next slide",
222+
},
223+
parameters: {
224+
docs: {
225+
source: {
226+
code: `<VcNavButton label="Go to next slide" />`,
227+
},
228+
},
229+
},
230+
};
231+
232+
export const CustomIcon: StoryType = {
233+
args: {
234+
icon: "arrow-right",
235+
label: "Next",
236+
},
237+
parameters: {
238+
docs: {
239+
source: {
240+
code: `<VcNavButton icon="arrow-right" label="Next" />`,
241+
},
242+
},
243+
},
244+
};
245+
246+
export const AllSizes: StoryType = {
247+
render: () => ({
248+
components: { VcNavButton },
249+
setup: () => ({ sizes: SIZES }),
250+
template: `
251+
<div class="flex items-center gap-4">
252+
<div v-for="size in sizes" :key="size" class="flex flex-col items-center gap-2">
253+
<VcNavButton :size="size" direction="right" />
254+
<span class="text-sm text-neutral-500">{{ size }}</span>
255+
</div>
256+
</div>
257+
`,
258+
}),
259+
parameters: {
260+
docs: {
261+
source: {
262+
code: `<VcNavButton size="xs" />
263+
<VcNavButton size="sm" />
264+
<VcNavButton size="md" />`,
265+
},
266+
},
267+
},
268+
};
269+
270+
export const AllDirections: StoryType = {
271+
render: () => ({
272+
components: { VcNavButton },
273+
setup: () => ({ directions: DIRECTIONS }),
274+
template: `
275+
<div class="flex items-center gap-4">
276+
<div v-for="direction in directions" :key="direction" class="flex flex-col items-center gap-2">
277+
<VcNavButton :direction="direction" />
278+
<span class="text-sm text-neutral-500">{{ direction }}</span>
279+
</div>
280+
</div>
281+
`,
282+
}),
283+
parameters: {
284+
docs: {
285+
source: {
286+
code: `<VcNavButton direction="right" />
287+
<VcNavButton direction="left" />
288+
<VcNavButton direction="up" />
289+
<VcNavButton direction="down" />`,
290+
},
291+
},
292+
},
293+
};
294+
295+
export const AllStates: StoryType = {
296+
render: () => ({
297+
components: { VcNavButton },
298+
setup: () => ({ sizes: SIZES, directions: DIRECTIONS }),
299+
template: `
300+
<div class="space-y-8">
301+
<div v-for="size in sizes" :key="size" class="space-y-2">
302+
<h3 class="text-lg font-semibold">Size: {{ size }}</h3>
303+
<div class="flex items-center gap-4">
304+
<div v-for="direction in directions" :key="direction" class="flex flex-col items-center gap-2">
305+
<VcNavButton :size="size" :direction="direction" />
306+
<span class="text-xs text-neutral-500">{{ direction }}</span>
307+
</div>
308+
<div class="flex flex-col items-center gap-2">
309+
<VcNavButton :size="size" direction="right" disabled />
310+
<span class="text-xs text-neutral-500">disabled</span>
311+
</div>
312+
</div>
313+
</div>
314+
</div>
315+
`,
316+
}),
317+
parameters: {
318+
docs: {
319+
source: {
320+
code: `<!-- Size: xs -->
321+
<VcNavButton size="xs" direction="right" />
322+
<VcNavButton size="xs" direction="left" />
323+
<VcNavButton size="xs" direction="up" />
324+
<VcNavButton size="xs" direction="down" />
325+
<VcNavButton size="xs" direction="right" disabled />
326+
327+
<!-- Size: sm -->
328+
<VcNavButton size="sm" direction="right" />
329+
<VcNavButton size="sm" direction="left" />
330+
<VcNavButton size="sm" direction="up" />
331+
<VcNavButton size="sm" direction="down" />
332+
<VcNavButton size="sm" direction="right" disabled />
333+
334+
<!-- Size: md -->
335+
<VcNavButton size="md" direction="right" />
336+
<VcNavButton size="md" direction="left" />
337+
<VcNavButton size="md" direction="up" />
338+
<VcNavButton size="md" direction="down" />
339+
<VcNavButton size="md" direction="right" disabled />`,
340+
},
341+
},
342+
},
65343
};

0 commit comments

Comments
 (0)