srcdev-nuxt-components 9.6.18 → 9.6.20
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/css-relative-colour-calc.md +15 -0
- package/.claude/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
- package/.claude/skills/component-compliance-checklist.md +11 -1
- package/.claude/skills/component-dynamic-slots.md +2 -2
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/accordion-chat.md +99 -0
- package/.claude/skills/components/accordion-group.md +98 -0
- package/.claude/skills/components/action-menu.md +11 -5
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/expanding-panel-classic.md +9 -7
- package/.claude/skills/components/expanding-panel.md +9 -8
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/components/sparkle-effect.md +79 -0
- package/.claude/skills/index.md +16 -14
- package/.claude/skills/storybook-add-story.md +33 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.claude/skills/using-component-skills.md +1 -1
- package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
- package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
- package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
- package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
- package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
- package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
- package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
- package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
- package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
- package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
- package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
- package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
- package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
- package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/.claude/skills/components/accordian-core.md +0 -180
- package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import ExpandingPanelClassic from "../ExpandingPanelClassic.vue";
|
|
2
2
|
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
import CanvasSwitcher from "../../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
|
|
5
|
+
import type { MediaCanvas } from "~/types/components";
|
|
3
6
|
|
|
4
7
|
const meta: Meta<typeof ExpandingPanelClassic> = {
|
|
5
8
|
title: "Molecules/Expandable/Expanding Panel Classic",
|
|
6
9
|
component: ExpandingPanelClassic,
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component:
|
|
14
|
+
"A single expand/collapse panel on native <details>/<summary>. To render a set of these from slots, optionally exclusive-open, use " +
|
|
15
|
+
"[AccordionGroup](?path=/story/molecules-expandable-accordiongroup--exclusive-open), which renders this panel for every item when `variant=\"classic\"`. " +
|
|
16
|
+
"[AccordionChat](?path=/story/molecules-expandable-accordionchat--default) restyles it as a chat: the summary becomes a question bubble and the content an answer bubble.",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
7
20
|
argTypes: {
|
|
8
21
|
name: {
|
|
9
22
|
control: { type: "text" },
|
|
10
|
-
description: "
|
|
23
|
+
description: "The <details> name: panels sharing it form an exclusive-open group. Element ids come from useId(), not from this.",
|
|
11
24
|
},
|
|
12
25
|
animationDuration: {
|
|
13
26
|
control: { type: "number", min: 0, step: 50 },
|
|
@@ -291,3 +304,72 @@ export const WithStyleClassPassthrough: Story = {
|
|
|
291
304
|
`,
|
|
292
305
|
}),
|
|
293
306
|
};
|
|
307
|
+
|
|
308
|
+
export const StressTest: Story = {
|
|
309
|
+
name: "Stress Test (Worst-Case Data)",
|
|
310
|
+
render: (args) => ({
|
|
311
|
+
components: { ExpandingPanelClassic },
|
|
312
|
+
setup() {
|
|
313
|
+
const longUnbroken = "Donaudampfschifffahrtselektrizitätenhauptbetriebswerkbauunterbeamtengesellschaft";
|
|
314
|
+
const longUrl = "https://example.com/" + "a-very-long-path-segment-without-spaces".repeat(4);
|
|
315
|
+
const longCopy = "Unsere Behandlungen werden individuell auf Ihre Bedürfnisse abgestimmt, damit Sie sich rundum wohlfühlen. ".repeat(6);
|
|
316
|
+
return { args, longUnbroken, longUrl, longCopy };
|
|
317
|
+
},
|
|
318
|
+
template: `
|
|
319
|
+
<div style="padding: 1.6rem; display: grid; gap: 1.6rem;">
|
|
320
|
+
<ExpandingPanelClassic v-bind="args" name="stress-a">
|
|
321
|
+
<template #summary><span>{{ longUnbroken }}</span></template>
|
|
322
|
+
<template #content><p style="margin: 0; padding-block: 0.8rem;">{{ longUrl }}</p></template>
|
|
323
|
+
</ExpandingPanelClassic>
|
|
324
|
+
<ExpandingPanelClassic v-bind="args" name="stress-a">
|
|
325
|
+
<template #summary><span>{{ longCopy.slice(0, 220) }}</span></template>
|
|
326
|
+
<template #content><p style="margin: 0; padding-block: 0.8rem;">{{ longCopy }}</p></template>
|
|
327
|
+
</ExpandingPanelClassic>
|
|
328
|
+
<ExpandingPanelClassic v-bind="args">
|
|
329
|
+
<template #summary><span>🎉 Emoji first, then <b>html</b> as text</span></template>
|
|
330
|
+
<template #content><p style="margin: 0; padding-block: 0.8rem;"><script>alert(1)</script></p></template>
|
|
331
|
+
</ExpandingPanelClassic>
|
|
332
|
+
<ExpandingPanelClassic v-bind="args" dir="rtl">
|
|
333
|
+
<template #summary><span>سؤال طويل من اليمين إلى اليسار للتحقق من الاتجاه</span></template>
|
|
334
|
+
<template #content><p style="margin: 0; padding-block: 0.8rem;">إجابة قصيرة.</p></template>
|
|
335
|
+
</ExpandingPanelClassic>
|
|
336
|
+
<ExpandingPanelClassic v-bind="args">
|
|
337
|
+
<template #summary><span>A</span></template>
|
|
338
|
+
</ExpandingPanelClassic>
|
|
339
|
+
<ExpandingPanelClassic v-bind="args">
|
|
340
|
+
<template #content><p style="margin: 0; padding-block: 0.8rem;">No summary slot: only the icon is clickable.</p></template>
|
|
341
|
+
</ExpandingPanelClassic>
|
|
342
|
+
</div>
|
|
343
|
+
`,
|
|
344
|
+
}),
|
|
345
|
+
decorators: [
|
|
346
|
+
(story, context) => ({
|
|
347
|
+
components: { story, CanvasSwitcher },
|
|
348
|
+
setup() {
|
|
349
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
|
|
350
|
+
return { canvasName };
|
|
351
|
+
},
|
|
352
|
+
template: `
|
|
353
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
354
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
355
|
+
</div>
|
|
356
|
+
<div :class="canvasName" style="margin-inline: auto; outline: 1px dashed currentColor;">
|
|
357
|
+
<story />
|
|
358
|
+
</div>
|
|
359
|
+
`,
|
|
360
|
+
}),
|
|
361
|
+
],
|
|
362
|
+
parameters: {
|
|
363
|
+
initialCanvas: "mobileCanvas",
|
|
364
|
+
docs: {
|
|
365
|
+
story: { inline: false, height: "640px" },
|
|
366
|
+
description: {
|
|
367
|
+
story:
|
|
368
|
+
"Open each panel at every canvas width. The long unbroken summary wraps and the caret stays visible (until 2026-10-20 it pushed the " +
|
|
369
|
+
"caret out of the clipped summary); long URLs and copy wrap in the content; the first two panels share a name, so opening one closes " +
|
|
370
|
+
"the other, and each keeps its own ids; emoji and HTML-like text render as text; the RTL panel puts the caret on the left; the " +
|
|
371
|
+
"single-letter, no-content and no-summary panels keep their shape.",
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { nextTick } from "vue";
|
|
2
|
+
import { nextTick, defineComponent } from "vue";
|
|
3
3
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
4
4
|
import type { ComponentPublicInstance } from "vue";
|
|
5
5
|
import ExpandingPanelClassic from "../ExpandingPanelClassic.vue";
|
|
@@ -156,18 +156,11 @@ describe("ExpandingPanelClassic", () => {
|
|
|
156
156
|
|
|
157
157
|
// ─── ARIA & IDs ───────────────────────────────────────────────────────────
|
|
158
158
|
|
|
159
|
-
it("
|
|
159
|
+
it("points the summary's aria-controls at the content region", async () => {
|
|
160
160
|
const wrapper = await mountSuspended(ExpandingPanelClassic, {
|
|
161
161
|
props: { name: "aria-panel" },
|
|
162
162
|
});
|
|
163
|
-
expect(wrapper.find("summary").attributes("
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("sets aria-controls on summary to id-{name}-content", async () => {
|
|
167
|
-
const wrapper = await mountSuspended(ExpandingPanelClassic, {
|
|
168
|
-
props: { name: "aria-panel" },
|
|
169
|
-
});
|
|
170
|
-
expect(wrapper.find("summary").attributes("aria-controls")).toBe("id-aria-panel-content");
|
|
163
|
+
expect(wrapper.find("summary").attributes("aria-controls")).toBe(wrapper.find(".expanding-panel-classic-content").attributes("id"));
|
|
171
164
|
});
|
|
172
165
|
|
|
173
166
|
it("sets aria-expanded to false when closed", async () => {
|
|
@@ -185,20 +178,11 @@ describe("ExpandingPanelClassic", () => {
|
|
|
185
178
|
expect(wrapper.find("summary").attributes("aria-expanded")).toBe("true");
|
|
186
179
|
});
|
|
187
180
|
|
|
188
|
-
it("
|
|
181
|
+
it("labels the content region by its summary", async () => {
|
|
189
182
|
const wrapper = await mountSuspended(ExpandingPanelClassic, {
|
|
190
183
|
props: { name: "aria-panel" },
|
|
191
184
|
});
|
|
192
|
-
expect(wrapper.find(".expanding-panel-classic-content").attributes("
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it("sets aria-labelledby on content region to id-{name}-trigger", async () => {
|
|
196
|
-
const wrapper = await mountSuspended(ExpandingPanelClassic, {
|
|
197
|
-
props: { name: "aria-panel" },
|
|
198
|
-
});
|
|
199
|
-
expect(wrapper.find(".expanding-panel-classic-content").attributes("aria-labelledby")).toBe(
|
|
200
|
-
"id-aria-panel-trigger",
|
|
201
|
-
);
|
|
185
|
+
expect(wrapper.find(".expanding-panel-classic-content").attributes("aria-labelledby")).toBe(wrapper.find("summary").attributes("id"));
|
|
202
186
|
});
|
|
203
187
|
|
|
204
188
|
it("sets role='region' on the content div", async () => {
|
|
@@ -482,33 +466,43 @@ describe("ExpandingPanelClassic", () => {
|
|
|
482
466
|
|
|
483
467
|
// ─── name prop / useId fallback ───────────────────────────────────────────
|
|
484
468
|
|
|
485
|
-
it("
|
|
469
|
+
it("builds ids from useId, not from the name", async () => {
|
|
486
470
|
const wrapper = await mountSuspended(ExpandingPanelClassic, {
|
|
487
471
|
props: { name: "my-panel" },
|
|
488
472
|
});
|
|
489
|
-
expect(wrapper.find("summary").attributes("id")).
|
|
490
|
-
expect(wrapper.find(".expanding-panel-classic-content").attributes("id")).
|
|
473
|
+
expect(wrapper.find("summary").attributes("id")).not.toContain("my-panel");
|
|
474
|
+
expect(wrapper.find(".expanding-panel-classic-content").attributes("id")).not.toContain("my-panel");
|
|
491
475
|
});
|
|
492
476
|
|
|
493
477
|
it("generates ARIA ids when no name prop is supplied", async () => {
|
|
494
478
|
const wrapper = await mountSuspended(ExpandingPanelClassic);
|
|
495
479
|
const summaryId = wrapper.find("summary").attributes("id") ?? "";
|
|
496
480
|
const contentId = wrapper.find(".expanding-panel-classic-content").attributes("id") ?? "";
|
|
497
|
-
expect(summaryId).toMatch(
|
|
498
|
-
expect(contentId).toMatch(
|
|
481
|
+
expect(summaryId).toMatch(/^.+-trigger$/);
|
|
482
|
+
expect(contentId).toMatch(/^.+-content$/);
|
|
499
483
|
});
|
|
500
484
|
|
|
501
|
-
it("updates the details name
|
|
485
|
+
it("updates the details name when the name prop changes after mount, keeping its ids", async () => {
|
|
502
486
|
const wrapper = await mountSuspended(ExpandingPanelClassic, {
|
|
503
487
|
props: { name: "panel-a" },
|
|
504
488
|
});
|
|
489
|
+
const summaryId = wrapper.find("summary").attributes("id");
|
|
505
490
|
expect(wrapper.find("details").attributes("name")).toBe("panel-a");
|
|
506
|
-
expect(wrapper.find("summary").attributes("id")).toBe("id-panel-a-trigger");
|
|
507
491
|
|
|
508
492
|
await wrapper.setProps({ name: "panel-b" });
|
|
509
493
|
|
|
510
494
|
expect(wrapper.find("details").attributes("name")).toBe("panel-b");
|
|
511
|
-
expect(wrapper.find("summary").attributes("id")).toBe(
|
|
512
|
-
|
|
495
|
+
expect(wrapper.find("summary").attributes("id")).toBe(summaryId);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("gives panels that share a name distinct ids", async () => {
|
|
499
|
+
const Host = defineComponent({
|
|
500
|
+
components: { ExpandingPanelClassic },
|
|
501
|
+
template: `<div><ExpandingPanelClassic name="shared"></ExpandingPanelClassic><ExpandingPanelClassic name="shared"></ExpandingPanelClassic></div>`,
|
|
502
|
+
});
|
|
503
|
+
const wrapper = await mountSuspended(Host);
|
|
504
|
+
const ids = wrapper.findAll("[id]").map((el) => el.attributes("id"));
|
|
505
|
+
expect(ids).toHaveLength(4);
|
|
506
|
+
expect(new Set(ids).size).toBe(4);
|
|
513
507
|
});
|
|
514
508
|
});
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
exports[`ExpandingPanelClassic > renders correct HTML structure with all props set 1`] = `
|
|
4
4
|
"<div class="expanding-panel-classic extra-class">
|
|
5
5
|
<details class="expanding-panel-classic-details" name="snap-all" open="">
|
|
6
|
-
<summary id="
|
|
6
|
+
<summary id="v-0-1-trigger" class="expanding-panel-classic-summary" aria-controls="v-0-1-content" aria-expanded="true">
|
|
7
7
|
<div class="label-wrapper"></div>
|
|
8
8
|
<div class="icon-wrapper icon-wrapper--hidden"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
9
9
|
</summary>
|
|
10
10
|
</details>
|
|
11
|
-
<div id="
|
|
11
|
+
<div id="v-0-1-content" class="expanding-panel-classic-content" aria-labelledby="v-0-1-trigger" role="region">
|
|
12
12
|
<div class="inner"></div>
|
|
13
13
|
</div>
|
|
14
14
|
</div>"
|
|
@@ -17,12 +17,12 @@ exports[`ExpandingPanelClassic > renders correct HTML structure with all props s
|
|
|
17
17
|
exports[`ExpandingPanelClassic > renders correct HTML structure with contentIsOnTop true 1`] = `
|
|
18
18
|
"<div class="expanding-panel-classic content-is-on-top">
|
|
19
19
|
<details class="expanding-panel-classic-details" name="snap-content-on-top">
|
|
20
|
-
<summary id="
|
|
20
|
+
<summary id="v-0-1-trigger" class="expanding-panel-classic-summary" aria-controls="v-0-1-content" aria-expanded="false">
|
|
21
21
|
<div class="label-wrapper"></div>
|
|
22
22
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
23
23
|
</summary>
|
|
24
24
|
</details>
|
|
25
|
-
<div id="
|
|
25
|
+
<div id="v-0-1-content" class="expanding-panel-classic-content" aria-labelledby="v-0-1-trigger" role="region">
|
|
26
26
|
<div class="inner"></div>
|
|
27
27
|
</div>
|
|
28
28
|
</div>"
|
|
@@ -31,12 +31,12 @@ exports[`ExpandingPanelClassic > renders correct HTML structure with contentIsOn
|
|
|
31
31
|
exports[`ExpandingPanelClassic > renders correct HTML structure with default props 1`] = `
|
|
32
32
|
"<div class="expanding-panel-classic">
|
|
33
33
|
<details class="expanding-panel-classic-details" name="snap-default">
|
|
34
|
-
<summary id="
|
|
34
|
+
<summary id="v-0-1-trigger" class="expanding-panel-classic-summary" aria-controls="v-0-1-content" aria-expanded="false">
|
|
35
35
|
<div class="label-wrapper"></div>
|
|
36
36
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
|
|
37
37
|
</summary>
|
|
38
38
|
</details>
|
|
39
|
-
<div id="
|
|
39
|
+
<div id="v-0-1-content" class="expanding-panel-classic-content" aria-labelledby="v-0-1-trigger" role="region">
|
|
40
40
|
<div class="inner"></div>
|
|
41
41
|
</div>
|
|
42
42
|
</div>"
|
|
@@ -45,12 +45,12 @@ exports[`ExpandingPanelClassic > renders correct HTML structure with default pro
|
|
|
45
45
|
exports[`ExpandingPanelClassic > renders correct HTML structure with populated slots 1`] = `
|
|
46
46
|
"<div class="expanding-panel-classic">
|
|
47
47
|
<details class="expanding-panel-classic-details" name="snap-slots">
|
|
48
|
-
<summary id="
|
|
48
|
+
<summary id="v-0-1-trigger" class="expanding-panel-classic-summary" aria-controls="v-0-1-content" aria-expanded="false">
|
|
49
49
|
<div class="label-wrapper"><span>Summary text</span></div>
|
|
50
50
|
<div class="icon-wrapper"><span>▸</span></div>
|
|
51
51
|
</summary>
|
|
52
52
|
</details>
|
|
53
|
-
<div id="
|
|
53
|
+
<div id="v-0-1-content" class="expanding-panel-classic-content" aria-labelledby="v-0-1-trigger" role="region">
|
|
54
54
|
<div class="inner">
|
|
55
55
|
<p>Content text</p>
|
|
56
56
|
</div>
|
|
@@ -2,65 +2,76 @@
|
|
|
2
2
|
|
|
3
3
|
## Public token API
|
|
4
4
|
|
|
5
|
+
> Changed 2026-10-21: colour defaults now come from `--theme-*` tokens (were `--blue-10`, `white`,
|
|
6
|
+
> `black`, `--slate-10`), and the structural classes are prefixed (see State hooks).
|
|
7
|
+
|
|
8
|
+
### Top-level row
|
|
9
|
+
|
|
5
10
|
| Token | Default | Controls |
|
|
6
11
|
|---|---|---|
|
|
7
|
-
| `--deep-expanding-menu-gap` | `2.4rem` | Gap between top-level items |
|
|
8
|
-
| `--deep-expanding-menu-link-border-width` | `0.2rem` | Bottom border width on links
|
|
12
|
+
| `--deep-expanding-menu-gap` | `2.4rem` | Gap between top-level items (row and column, the row wraps) |
|
|
13
|
+
| `--deep-expanding-menu-link-border-width` | `0.2rem` | Bottom border width on links, group toggles and child links |
|
|
9
14
|
| `--deep-expanding-menu-link-padding-block` | `0.8rem` | Vertical padding on links and group toggles |
|
|
10
|
-
| `--deep-expanding-menu-link-border-colour-hover` | `var(--
|
|
15
|
+
| `--deep-expanding-menu-link-border-colour-hover` | `var(--theme-border)` | Bottom border colour on hover/focus |
|
|
16
|
+
| `--deep-expanding-menu-focus-outline-width` | `0.2rem` | Keyboard focus ring width on links and toggles |
|
|
17
|
+
| `--deep-expanding-menu-focus-outline-colour` | `var(--theme-ring, currentcolor)` | Keyboard focus ring colour |
|
|
11
18
|
| `--deep-expanding-menu-icon-size` | `1.2rem` | Caret icon size |
|
|
12
|
-
| `--deep-expanding-menu-panel-width` | `min(100%, 50vw)` | Popover panel width |
|
|
13
|
-
| `--deep-expanding-menu-panel-background-colour` | `white` | Popover panel background |
|
|
14
|
-
| `--deep-expanding-menu-panel-border-width` | `0.1rem` | Popover panel border width |
|
|
15
|
-
| `--deep-expanding-menu-panel-border-colour` | `black` | Popover panel border colour |
|
|
16
|
-
| `--deep-expanding-menu-panel-border-radius` | `1.2rem` | Popover panel corner radius |
|
|
17
|
-
| `--deep-expanding-menu-panel-shadow` | `0 0 1rem rgba(0, 0, 0, 0.1)` | Popover panel box-shadow |
|
|
18
|
-
| `--deep-expanding-menu-panel-padding` | `1.2rem` | Popover panel padding |
|
|
19
|
-
| `--deep-expanding-menu-panel-heading-colour` | `var(--slate-10)` | `childLinksTitle` heading colour inside the panel |
|
|
20
|
-
| `--deep-expanding-menu-panel-list-gap` | `1.2rem` | Gap between child link grid items |
|
|
21
|
-
| `--deep-expanding-menu-group-link-colour` | `var(--slate-10)` | Child link text colour |
|
|
22
|
-
| `--deep-expanding-menu-group-link-border-colour-hover` | `var(--slate-10)` | Child link bottom border colour on hover/focus |
|
|
23
|
-
| `--deep-expanding-menu-panel-z-index` | `999999` | Panel stacking order in browsers without CSS anchor positioning. Ignored where the panel renders in the top layer |
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
.my-page {
|
|
27
|
-
--deep-expanding-menu-panel-background-colour: #1a1a1a;
|
|
28
|
-
--deep-expanding-menu-panel-border-colour: transparent;
|
|
29
|
-
--deep-expanding-menu-group-link-colour: white;
|
|
30
|
-
}
|
|
31
|
-
```
|
|
20
|
+
### Panel
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
22
|
+
| Token | Default | Controls |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `--deep-expanding-menu-panel-width` | `min(100%, 50vw)` | Panel width |
|
|
25
|
+
| `--deep-expanding-menu-panel-max-height` | `70dvh` | Panel max height; longer lists scroll inside it |
|
|
26
|
+
| `--deep-expanding-menu-panel-text-colour` | `var(--theme-text)` | Panel text colour |
|
|
27
|
+
| `--deep-expanding-menu-panel-background-colour` | `var(--theme-surface-subtle)` | Panel background |
|
|
28
|
+
| `--deep-expanding-menu-panel-border-width` | `0.1rem` | Panel border width |
|
|
29
|
+
| `--deep-expanding-menu-panel-border-colour` | `var(--theme-border)` | Panel border colour |
|
|
30
|
+
| `--deep-expanding-menu-panel-border-radius` | `1.2rem` | Panel corner radius |
|
|
31
|
+
| `--deep-expanding-menu-panel-shadow` | `0 0 1rem rgba(0, 0, 0, 0.1)` | Panel box-shadow |
|
|
32
|
+
| `--deep-expanding-menu-panel-padding` | `1.2rem` | Panel padding |
|
|
33
|
+
| `--deep-expanding-menu-panel-heading-colour` | `var(--theme-text)` | `childLinksTitle` heading colour |
|
|
34
|
+
| `--deep-expanding-menu-panel-heading-line-clamp` | `none` | Lines of `childLinksTitle` before an ellipsis. `1` is single-line ellipsis, `none` shows everything |
|
|
35
|
+
| `--deep-expanding-menu-panel-list-gap` | `1.2rem` | Gap between child link grid items |
|
|
36
|
+
| `--deep-expanding-menu-group-link-colour` | `var(--theme-text)` | Child link text colour |
|
|
37
|
+
| `--deep-expanding-menu-group-link-border-colour-hover` | `var(--theme-text)` | Child link bottom border colour on hover/focus |
|
|
38
|
+
| `--deep-expanding-menu-panel-z-index` | `999999` | Panel stacking order in browsers without CSS anchor positioning. Ignored where the panel renders in the top layer |
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
40
|
+
Private tokens (not public API): `--_anchor-name` (per-group anchor, set inline),
|
|
41
|
+
`--_icon-transform` (caret flip), and `--_anchor-bottom`, `--_anchor-left`,
|
|
42
|
+
`--_anchor-top-inverse` (written by `useAnchoredPopover` in the fallback).
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
Not tokenised: the `1rem` gap between a toggle and its panel, and the `12rem` minimum child link
|
|
45
|
+
column. Anchor names and popover target ids come from `useId()`.
|
|
48
46
|
|
|
49
|
-
##
|
|
47
|
+
## State hooks
|
|
50
48
|
|
|
51
49
|
| Hook | Meaning |
|
|
52
50
|
|---|---|
|
|
53
|
-
| `.
|
|
54
|
-
| `.
|
|
51
|
+
| `.deep-expanding-menu-group-toggle[aria-expanded="true"]` | Group open, every browser (flips the caret) |
|
|
52
|
+
| `.deep-expanding-menu-group-panel:popover-open` | Panel open, Popover API browsers |
|
|
55
53
|
| `.deep-expanding-menu-panel-open` | Panel open, browsers without the Popover API (Safari 16 and older). Style it in a **separate rule** from `:popover-open`: a selector list containing `:popover-open` is dropped whole where it's unsupported |
|
|
56
|
-
| `.
|
|
54
|
+
| `.deep-expanding-menu-group-panel[data-placement="top"]` | Flipped above the toggle, browsers without CSS anchor positioning only |
|
|
55
|
+
|
|
56
|
+
Inner elements: `.deep-expanding-menu-inner` (the flex row), `.deep-expanding-menu-link` (direct
|
|
57
|
+
link), `.deep-expanding-menu-group`, `.deep-expanding-menu-group-toggle`,
|
|
58
|
+
`.deep-expanding-menu-group-label`, `.deep-expanding-menu-group-icon`,
|
|
59
|
+
`.deep-expanding-menu-group-panel`, `.deep-expanding-menu-group-heading`,
|
|
60
|
+
`.deep-expanding-menu-group-list`, `.deep-expanding-menu-group-item`,
|
|
61
|
+
`.deep-expanding-menu-group-link`.
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
with `transform`, `filter` or `contain` becomes its containing block and can misplace it.
|
|
63
|
+
> Changed 2026-10-21: these were `.inner`, `.navigation-link`, `.navigation-group`,
|
|
64
|
+
> `.navigation-group-toggle`, `.icon`, `.navigation-group-panel`, `.navigation-group-list`,
|
|
65
|
+
> `.navigation-group-item` and `.navigation-group-link`. The heading had no class of its own.
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
## Browser support and placement
|
|
68
|
+
|
|
69
|
+
Where CSS anchor positioning is supported, a panel that would overflow the right edge flips to
|
|
70
|
+
align with its toggle's right edge (`position-try-fallbacks: flip-inline`). Without anchor
|
|
71
|
+
positioning (e.g. Safari 17–18) the panel is placed with `position: fixed` from the toggle's
|
|
72
|
+
measured position. Without the Popover API (Safari 16) the panel also opens and closes in JS and
|
|
73
|
+
isn't in the top layer, so `--deep-expanding-menu-panel-z-index` applies, and an ancestor with
|
|
74
|
+
`transform`, `filter` or `contain` becomes its containing block and can misplace it.
|
|
64
75
|
|
|
65
76
|
## Local overrides
|
|
66
77
|
|
|
@@ -69,3 +80,11 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
69
80
|
`@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
|
|
70
81
|
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
|
|
71
82
|
`:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
83
|
+
|
|
84
|
+
**Caveat:** in browsers with the Popover API the panel renders in the top layer but stays a DOM
|
|
85
|
+
descendant of the menu, so tokens set on an ancestor still reach it.
|
|
86
|
+
|
|
87
|
+
## Class passthrough
|
|
88
|
+
|
|
89
|
+
`style-class-passthrough` adds classes to the root element (the `<nav>` by default). It's the
|
|
90
|
+
usual place to scope token overrides to one instance.
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<component :is="tag" ref="rootRef" class="deep-expanding-menu" :class="[elementClasses]">
|
|
3
|
-
<div class="inner">
|
|
2
|
+
<component :is="tag" ref="rootRef" class="deep-expanding-menu" :class="[elementClasses]" :aria-label="ariaLabel">
|
|
3
|
+
<div class="deep-expanding-menu-inner">
|
|
4
4
|
<template v-for="(link, key) in navLinks" :key="key">
|
|
5
|
-
<NuxtLink v-if="link.path" :to="link.path" class="
|
|
5
|
+
<NuxtLink v-if="link.path" :to="link.path" class="deep-expanding-menu-link">{{ link.name }}</NuxtLink>
|
|
6
6
|
|
|
7
|
-
<div v-else class="
|
|
7
|
+
<div v-else class="deep-expanding-menu-group" :style="`--_anchor-name: --anchor-${uid}-${key};`">
|
|
8
8
|
<button
|
|
9
9
|
:ref="(el) => setElement(triggerEls, key, el)"
|
|
10
10
|
type="button"
|
|
11
11
|
:popovertarget="`popovertarget-${uid}-${key}`"
|
|
12
|
-
class="
|
|
12
|
+
class="deep-expanding-menu-group-toggle"
|
|
13
13
|
:aria-expanded="isOpen && activeKey === key"
|
|
14
14
|
@click="handleGroupClick(key)"
|
|
15
15
|
>
|
|
16
|
-
<span>{{ link.name }}</span>
|
|
17
|
-
<Icon name="bi:caret-down-fill" class="icon" />
|
|
16
|
+
<span class="deep-expanding-menu-group-label">{{ link.name }}</span>
|
|
17
|
+
<Icon name="bi:caret-down-fill" class="deep-expanding-menu-group-icon" aria-hidden="true" />
|
|
18
18
|
</button>
|
|
19
19
|
|
|
20
20
|
<div
|
|
21
21
|
:id="`popovertarget-${uid}-${key}`"
|
|
22
22
|
:ref="(el) => setElement(panelEls, key, el)"
|
|
23
|
-
class="
|
|
23
|
+
class="deep-expanding-menu-group-panel"
|
|
24
24
|
:class="{ 'deep-expanding-menu-panel-open': usesFallbackPopover && isOpen && activeKey === key }"
|
|
25
25
|
:style="activeKey === key ? positionStyle : undefined"
|
|
26
26
|
:data-placement="activeKey === key ? popoverPlacement : undefined"
|
|
@@ -28,10 +28,16 @@
|
|
|
28
28
|
@beforetoggle="handlePanelBeforeToggle(key, $event)"
|
|
29
29
|
@toggle="handlePanelToggle(key, $event)"
|
|
30
30
|
>
|
|
31
|
-
<h4 class="page-heading-4 mb-6">
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
<h4 v-if="link.childLinksTitle" class="deep-expanding-menu-group-heading page-heading-4 mb-6">
|
|
32
|
+
{{ link.childLinksTitle }}
|
|
33
|
+
</h4>
|
|
34
|
+
<ul v-if="link.childLinks?.length" class="deep-expanding-menu-group-list" role="list">
|
|
35
|
+
<li
|
|
36
|
+
v-for="(childLink, childIndex) in link.childLinks"
|
|
37
|
+
:key="`${childIndex}-${childLink.name}`"
|
|
38
|
+
class="deep-expanding-menu-group-item"
|
|
39
|
+
>
|
|
40
|
+
<NuxtLink :to="childLink.path" class="deep-expanding-menu-group-link">{{ childLink.name }}</NuxtLink>
|
|
35
41
|
</li>
|
|
36
42
|
</ul>
|
|
37
43
|
</div>
|
|
@@ -41,19 +47,22 @@
|
|
|
41
47
|
</component>
|
|
42
48
|
</template>
|
|
43
49
|
|
|
50
|
+
|
|
44
51
|
<script setup lang="ts">
|
|
45
52
|
import type { ComponentPublicInstance } from "vue";
|
|
46
53
|
import type { ResponsiveHeaderNavItem } from "~/types/components";
|
|
47
54
|
|
|
48
55
|
interface Props {
|
|
49
|
-
tag?: "div" | "section" | "nav"
|
|
56
|
+
tag?: "div" | "section" | "nav";
|
|
50
57
|
navLinks?: ResponsiveHeaderNavItem[];
|
|
58
|
+
ariaLabel?: string;
|
|
51
59
|
styleClassPassthrough?: string | string[];
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
const props = withDefaults(defineProps<Props>(), {
|
|
55
63
|
tag: "nav",
|
|
56
64
|
navLinks: () => [],
|
|
65
|
+
ariaLabel: undefined,
|
|
57
66
|
styleClassPassthrough: () => [],
|
|
58
67
|
});
|
|
59
68
|
|
|
@@ -126,15 +135,19 @@ watch(
|
|
|
126
135
|
display: grid;
|
|
127
136
|
grid-template-areas: "element-stack";
|
|
128
137
|
|
|
129
|
-
.inner {
|
|
138
|
+
.deep-expanding-menu-inner {
|
|
130
139
|
grid-area: element-stack;
|
|
131
140
|
display: flex;
|
|
141
|
+
flex-wrap: wrap;
|
|
132
142
|
gap: var(--deep-expanding-menu-gap, 2.4rem);
|
|
133
143
|
align-items: center;
|
|
144
|
+
min-inline-size: 0;
|
|
134
145
|
|
|
135
|
-
.
|
|
136
|
-
.
|
|
146
|
+
.deep-expanding-menu-link,
|
|
147
|
+
.deep-expanding-menu-group-toggle {
|
|
137
148
|
all: unset;
|
|
149
|
+
min-inline-size: 0;
|
|
150
|
+
overflow-wrap: anywhere;
|
|
138
151
|
border-bottom: var(--deep-expanding-menu-link-border-width, 0.2rem) solid transparent;
|
|
139
152
|
padding-block: var(--deep-expanding-menu-link-padding-block, 0.8rem);
|
|
140
153
|
|
|
@@ -144,26 +157,35 @@ watch(
|
|
|
144
157
|
&:focus,
|
|
145
158
|
&:focus-visible {
|
|
146
159
|
cursor: pointer;
|
|
147
|
-
border-color: var(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
160
|
+
border-color: var(--deep-expanding-menu-link-border-colour-hover, var(--theme-border));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&:focus-visible {
|
|
164
|
+
outline: var(--deep-expanding-menu-focus-outline-width, 0.2rem) solid
|
|
165
|
+
var(--deep-expanding-menu-focus-outline-colour, var(--theme-ring, currentcolor));
|
|
166
|
+
outline-offset: 0.2rem;
|
|
151
167
|
}
|
|
152
168
|
}
|
|
153
169
|
|
|
154
|
-
.
|
|
170
|
+
.deep-expanding-menu-group {
|
|
155
171
|
--_icon-transform: scaleY(1);
|
|
156
172
|
position: relative;
|
|
173
|
+
min-inline-size: 0;
|
|
157
174
|
|
|
158
|
-
.
|
|
175
|
+
.deep-expanding-menu-group-toggle {
|
|
159
176
|
anchor-name: var(--_anchor-name);
|
|
160
177
|
|
|
161
178
|
display: flex;
|
|
162
179
|
align-items: center;
|
|
163
180
|
gap: 1.2rem;
|
|
164
181
|
|
|
165
|
-
.
|
|
182
|
+
.deep-expanding-menu-group-label {
|
|
183
|
+
min-inline-size: 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.deep-expanding-menu-group-icon {
|
|
166
187
|
display: block;
|
|
188
|
+
flex-shrink: 0;
|
|
167
189
|
font-size: var(--deep-expanding-menu-icon-size, 1.2rem);
|
|
168
190
|
|
|
169
191
|
transform: var(--_icon-transform);
|
|
@@ -171,10 +193,11 @@ watch(
|
|
|
171
193
|
}
|
|
172
194
|
}
|
|
173
195
|
|
|
174
|
-
.
|
|
196
|
+
.deep-expanding-menu-group-panel {
|
|
175
197
|
display: none;
|
|
176
198
|
position: absolute;
|
|
177
199
|
position-anchor: var(--_anchor-name);
|
|
200
|
+
position-try-fallbacks: flip-inline;
|
|
178
201
|
margin: 0;
|
|
179
202
|
top: calc(anchor(bottom) + 1rem);
|
|
180
203
|
left: calc(anchor(left) + 0rem);
|
|
@@ -187,14 +210,17 @@ watch(
|
|
|
187
210
|
transition-behavior: allow-discrete;
|
|
188
211
|
|
|
189
212
|
width: var(--deep-expanding-menu-panel-width, min(100%, 50vw));
|
|
213
|
+
max-block-size: var(--deep-expanding-menu-panel-max-height, 70dvh);
|
|
190
214
|
|
|
191
|
-
|
|
215
|
+
color: var(--deep-expanding-menu-panel-text-colour, var(--theme-text));
|
|
216
|
+
background-color: var(--deep-expanding-menu-panel-background-colour, var(--theme-surface-subtle));
|
|
192
217
|
border: var(--deep-expanding-menu-panel-border-width, 0.1rem) solid
|
|
193
|
-
var(--deep-expanding-menu-panel-border-colour,
|
|
218
|
+
var(--deep-expanding-menu-panel-border-colour, var(--theme-border));
|
|
194
219
|
border-radius: var(--deep-expanding-menu-panel-border-radius, 1.2rem);
|
|
195
220
|
box-shadow: var(--deep-expanding-menu-panel-shadow, 0 0 1rem rgba(0, 0, 0, 0.1));
|
|
196
221
|
padding: var(--deep-expanding-menu-panel-padding, 1.2rem);
|
|
197
|
-
overflow: clip;
|
|
222
|
+
overflow-x: clip;
|
|
223
|
+
overflow-y: auto;
|
|
198
224
|
|
|
199
225
|
&:popover-open {
|
|
200
226
|
display: block;
|
|
@@ -224,23 +250,33 @@ watch(
|
|
|
224
250
|
}
|
|
225
251
|
}
|
|
226
252
|
|
|
227
|
-
|
|
228
|
-
color: var(--deep-expanding-menu-panel-heading-colour, var(--
|
|
253
|
+
.deep-expanding-menu-group-heading {
|
|
254
|
+
color: var(--deep-expanding-menu-panel-heading-colour, var(--theme-text));
|
|
255
|
+
overflow-wrap: anywhere;
|
|
256
|
+
display: -webkit-box;
|
|
257
|
+
-webkit-box-orient: vertical;
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
-webkit-line-clamp: var(--deep-expanding-menu-panel-heading-line-clamp, none);
|
|
260
|
+
line-clamp: var(--deep-expanding-menu-panel-heading-line-clamp, none);
|
|
229
261
|
}
|
|
230
262
|
|
|
231
|
-
.
|
|
263
|
+
.deep-expanding-menu-group-list {
|
|
232
264
|
display: grid;
|
|
233
|
-
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
|
265
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 12rem), 1fr));
|
|
234
266
|
gap: var(--deep-expanding-menu-panel-list-gap, 1.2rem);
|
|
267
|
+
list-style: none;
|
|
235
268
|
padding-inline-start: 0;
|
|
236
|
-
margin-block
|
|
269
|
+
margin-block: 0 0.8rem;
|
|
237
270
|
|
|
238
|
-
.
|
|
271
|
+
.deep-expanding-menu-group-item {
|
|
239
272
|
display: block;
|
|
273
|
+
min-inline-size: 0;
|
|
240
274
|
|
|
241
|
-
a.
|
|
275
|
+
a.deep-expanding-menu-group-link {
|
|
242
276
|
display: inline-block;
|
|
243
|
-
|
|
277
|
+
max-inline-size: 100%;
|
|
278
|
+
overflow-wrap: anywhere;
|
|
279
|
+
color: var(--deep-expanding-menu-group-link-colour, var(--theme-text));
|
|
244
280
|
text-decoration: none;
|
|
245
281
|
padding-block: 0.8rem;
|
|
246
282
|
|
|
@@ -251,14 +287,14 @@ watch(
|
|
|
251
287
|
&:hover,
|
|
252
288
|
&:focus-visible {
|
|
253
289
|
cursor: pointer;
|
|
254
|
-
border-color: var(--deep-expanding-menu-group-link-border-colour-hover, var(--
|
|
290
|
+
border-color: var(--deep-expanding-menu-group-link-border-colour-hover, var(--theme-text));
|
|
255
291
|
}
|
|
256
292
|
}
|
|
257
293
|
}
|
|
258
294
|
}
|
|
259
295
|
}
|
|
260
296
|
|
|
261
|
-
&:has(> .
|
|
297
|
+
&:has(> .deep-expanding-menu-group-toggle[aria-expanded="true"]) {
|
|
262
298
|
--_icon-transform: scaleY(-1);
|
|
263
299
|
}
|
|
264
300
|
}
|
|
@@ -267,3 +303,4 @@ watch(
|
|
|
267
303
|
}
|
|
268
304
|
}
|
|
269
305
|
</style>
|
|
306
|
+
|