srcdev-nuxt-components 9.6.5 → 9.6.6
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/.claude/rules/consumer/a11y-no-focus-steal.md +14 -0
- package/.claude/skills/component-compliance-checklist.md +5 -1
- package/.claude/skills/components/display-toast.md +6 -3
- package/.claude/skills/components/social-icons-list.md +32 -5
- package/.claude/skills/css-dynamic-border-radius.md +114 -0
- package/.claude/skills/index.md +2 -1
- package/.claude/skills/storybook-add-story.md +6 -0
- package/.vscode/srcdev-component-display-toast.code-snippets +6 -8
- package/.vscode/srcdev-component-social-icons-list.code-snippets +14 -0
- package/.vscode/srcdev-utility-dynamic-border-radius.code-snippets +31 -0
- package/app/assets/styles/setup/06.utility-classes/_dynamic-border-radius.css +27 -0
- package/app/assets/styles/setup/06.utility-classes/index.css +1 -0
- package/app/components/02.molecules/social-icons-list/CONSUMER-STYLING.md +97 -8
- package/app/components/02.molecules/social-icons-list/SocialIconsList.vue +80 -14
- package/app/components/02.molecules/social-icons-list/stories/SocialIconsList.stories.ts +230 -2
- package/app/components/02.molecules/social-icons-list/stories/social-icons-list-recipes.css +108 -0
- package/app/components/02.molecules/social-icons-list/tests/SocialIconsList.spec.ts +123 -52
- package/app/components/02.molecules/social-icons-list/tests/__snapshots__/SocialIconsList.spec.ts.snap +10 -4
- package/app/components/02.molecules/toast/CONSUMER-STYLING.md +9 -3
- package/app/components/02.molecules/toast/DisplayToast.vue +10 -10
- package/app/components/02.molecules/toast/DisplayToastProvider.vue +12 -4
- package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +200 -2
- package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +33 -12
- package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +20 -0
- package/app/types/components/social-icons-list.d.ts +9 -0
- package/package.json +1 -1
|
@@ -198,21 +198,35 @@ describe("DisplayToast", () => {
|
|
|
198
198
|
|
|
199
199
|
// ─── Dismiss via Escape key ────────────────────────────────────────────────
|
|
200
200
|
|
|
201
|
-
it("transitions to hide class when Escape is pressed", async () => {
|
|
201
|
+
it("transitions to hide class when Escape is pressed on the toast", async () => {
|
|
202
202
|
await mountAndShow({ config: { behavior: { autoDismiss: false } } });
|
|
203
|
-
vi.advanceTimersByTime(
|
|
204
|
-
await nextTick();
|
|
203
|
+
vi.advanceTimersByTime(1);
|
|
205
204
|
toast()!.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
|
|
206
205
|
await nextTick();
|
|
207
206
|
expect(toast()!.getAttribute("data-state")).toBe("hide");
|
|
208
207
|
});
|
|
209
208
|
|
|
209
|
+
it("dismisses on Escape pressed anywhere in the document, since it never takes focus", async () => {
|
|
210
|
+
await mountAndShow({ config: { behavior: { autoDismiss: false } } });
|
|
211
|
+
document.body.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
|
|
212
|
+
await nextTick();
|
|
213
|
+
expect(toast()!.getAttribute("data-state")).toBe("hide");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("ignores an Escape another handler has already claimed", async () => {
|
|
217
|
+
await mountAndShow({ config: { behavior: { autoDismiss: false } } });
|
|
218
|
+
const event = new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true });
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
document.body.dispatchEvent(event);
|
|
221
|
+
await nextTick();
|
|
222
|
+
expect(toast()!.getAttribute("data-state")).toBe("show");
|
|
223
|
+
});
|
|
224
|
+
|
|
210
225
|
// ─── Dismiss via close button ─────────────────────────────────────────────
|
|
211
226
|
|
|
212
227
|
it("transitions to hide class when the close button is clicked", async () => {
|
|
213
228
|
await mountAndShow({ config: { behavior: { autoDismiss: false } } });
|
|
214
|
-
vi.advanceTimersByTime(
|
|
215
|
-
await nextTick();
|
|
229
|
+
vi.advanceTimersByTime(1);
|
|
216
230
|
(document.querySelector("[data-test-id='alert-dismiss']") as HTMLElement).click();
|
|
217
231
|
await nextTick();
|
|
218
232
|
expect(toast()!.getAttribute("data-state")).toBe("hide");
|
|
@@ -282,22 +296,29 @@ describe("DisplayToast", () => {
|
|
|
282
296
|
expect(toast()!.getAttribute("data-state")).toBe("hide");
|
|
283
297
|
});
|
|
284
298
|
|
|
285
|
-
it("does not
|
|
299
|
+
it("does not move focus to itself when shown", async () => {
|
|
300
|
+
await mountAndShow({ config: autoConfig });
|
|
301
|
+
vi.advanceTimersByTime(500);
|
|
302
|
+
await nextTick();
|
|
303
|
+
expect(document.activeElement).not.toBe(toast());
|
|
304
|
+
expect(toast()!.hasAttribute("data-paused")).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("does not pause when focused after mouse input", async () => {
|
|
286
308
|
setFocusVisible(false);
|
|
287
309
|
await mountAndShow({ config: autoConfig });
|
|
288
|
-
|
|
310
|
+
(toast() as HTMLElement).focus();
|
|
289
311
|
await nextTick();
|
|
290
|
-
expect(document.activeElement).toBe(toast());
|
|
291
312
|
expect(toast()!.hasAttribute("data-paused")).toBe(false);
|
|
292
|
-
vi.advanceTimersByTime(
|
|
313
|
+
vi.advanceTimersByTime(1000);
|
|
293
314
|
await nextTick();
|
|
294
315
|
expect(toast()!.getAttribute("data-state")).toBe("hide");
|
|
295
316
|
});
|
|
296
317
|
|
|
297
|
-
it("pauses when
|
|
318
|
+
it("pauses when a keyboard user focuses the toast, until focus leaves", async () => {
|
|
298
319
|
setFocusVisible(true);
|
|
299
320
|
await mountAndShow({ config: autoConfig });
|
|
300
|
-
|
|
321
|
+
(toast() as HTMLElement).focus();
|
|
301
322
|
await nextTick();
|
|
302
323
|
expect(toast()!.hasAttribute("data-paused")).toBe(true);
|
|
303
324
|
vi.advanceTimersByTime(5000);
|
|
@@ -305,7 +326,7 @@ describe("DisplayToast", () => {
|
|
|
305
326
|
expect(toast()!.getAttribute("data-state")).toBe("show");
|
|
306
327
|
(toast() as HTMLElement).blur();
|
|
307
328
|
await nextTick();
|
|
308
|
-
vi.advanceTimersByTime(
|
|
329
|
+
vi.advanceTimersByTime(1000);
|
|
309
330
|
await nextTick();
|
|
310
331
|
expect(toast()!.getAttribute("data-state")).toBe("hide");
|
|
311
332
|
});
|
|
@@ -208,6 +208,26 @@ describe("DisplayToastProvider", () => {
|
|
|
208
208
|
expect(items().length).toBe(0);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
+
it("does not move focus to a toast when it appears", async () => {
|
|
212
|
+
await mountProvider(DisplayToastProvider);
|
|
213
|
+
show({ behavior: { autoDismiss: false } });
|
|
214
|
+
await nextTick();
|
|
215
|
+
await nextTick();
|
|
216
|
+
expect(document.activeElement).not.toBe(item());
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("dismisses only the newest toast when Escape is pressed outside the toasts", async () => {
|
|
220
|
+
await mountProvider(DisplayToastProvider, { props: { maxVisible: 2 } });
|
|
221
|
+
show({ content: { text: "First" }, behavior: { autoDismiss: false } });
|
|
222
|
+
show({ content: { text: "Second" }, behavior: { autoDismiss: false } });
|
|
223
|
+
await nextTick();
|
|
224
|
+
expect(items().length).toBe(2);
|
|
225
|
+
document.body.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
|
|
226
|
+
await nextTick();
|
|
227
|
+
expect(items().length).toBe(1);
|
|
228
|
+
expect(item()!.textContent).toContain("First");
|
|
229
|
+
});
|
|
230
|
+
|
|
211
231
|
// ─── Auto-dismiss timer ───────────────────────────────────────────────────
|
|
212
232
|
|
|
213
233
|
it("auto-dismisses after duration", async () => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { VNode } from "vue";
|
|
2
|
+
|
|
1
3
|
export interface ISocialIcon {
|
|
2
4
|
networkName: string;
|
|
3
5
|
iconName: string;
|
|
@@ -5,4 +7,11 @@ export interface ISocialIcon {
|
|
|
5
7
|
profileId: string;
|
|
6
8
|
/** Accessible name for the link; overrides the list's linkLabelTemplate. */
|
|
7
9
|
label?: string;
|
|
10
|
+
/** Brand colour, exposed as --social-icons-list-brand-colour on this item for recipes to use. */
|
|
11
|
+
brandColour?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SocialIconsListSlots {
|
|
15
|
+
icon?(props: { item: ISocialIcon }): VNode[];
|
|
16
|
+
label?(props: { item: ISocialIcon }): VNode[];
|
|
8
17
|
}
|