srcdev-nuxt-components 9.5.6 → 9.6.0

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.
Files changed (113) hide show
  1. package/.claude/skills/component-compliance-checklist.md +6 -2
  2. package/.claude/skills/components/breadcrumb.md +5 -0
  3. package/.claude/skills/components/display-pill.md +2 -0
  4. package/.claude/skills/components/input-button.md +3 -0
  5. package/.claude/skills/components/navigation-items.md +1 -0
  6. package/.claude/skills/components/opening-hours.md +12 -0
  7. package/.claude/skills/components/price-list.md +10 -1
  8. package/.claude/skills/components/responsive-header.md +74 -0
  9. package/.claude/skills/components/service-detail.md +32 -0
  10. package/.claude/skills/components/service-summary.md +8 -0
  11. package/.claude/skills/components/services-card-grid.md +5 -3
  12. package/.claude/skills/components/services-card.md +22 -12
  13. package/.claude/skills/components/site-header.md +2 -0
  14. package/.claude/skills/components/slider-gallery.md +10 -4
  15. package/.claude/skills/components/tab-navigation.md +25 -9
  16. package/.claude/skills/index.md +1 -2
  17. package/.claude/skills/storybook-add-story.md +14 -0
  18. package/.vscode/srcdev-component-responsive-header.code-snippets +13 -1
  19. package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
  20. package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
  21. package/.vscode/srcdev-component-services-card.code-snippets +38 -36
  22. package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
  23. package/.vscode/srcdev-component-site-header.code-snippets +3 -1
  24. package/.vscode/srcdev-component-tab-navigation.code-snippets +1 -1
  25. package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +3 -0
  26. package/app/components/01.atoms/display-pill/DisplayPill.vue +5 -2
  27. package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
  28. package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
  29. package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
  30. package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
  31. package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +52 -11
  32. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +55 -23
  33. package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +176 -257
  34. package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +42 -2
  35. package/app/components/02.molecules/opening-hours/CONSUMER-STYLING.md +11 -1
  36. package/app/components/02.molecules/opening-hours/OpeningHours.vue +71 -43
  37. package/app/components/02.molecules/opening-hours/stories/OpeningHours.stories.ts +138 -6
  38. package/app/components/02.molecules/opening-hours/tests/OpeningHours.spec.ts +53 -0
  39. package/app/components/02.molecules/opening-hours/tests/__snapshots__/OpeningHours.spec.ts.snap +1 -1
  40. package/app/components/02.molecules/opening-hours/tests/buildOpeningHoursSpecification.spec.ts +37 -1
  41. package/app/components/02.molecules/price-list/CONSUMER-STYLING.md +17 -5
  42. package/app/components/02.molecules/price-list/PriceList.vue +32 -9
  43. package/app/components/02.molecules/price-list/stories/PriceList.stories.ts +125 -29
  44. package/app/components/02.molecules/price-list/tests/PriceList.spec.ts +20 -0
  45. package/app/components/03.organisms/google-reviews/stories/GoogleReviews.stories.ts +9 -4
  46. package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
  47. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
  48. package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
  49. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
  50. package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
  51. package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +18 -0
  52. package/app/components/03.organisms/responsive-header/NavigationItems.vue +56 -18
  53. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +106 -31
  54. package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +201 -18
  55. package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +21 -0
  56. package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +66 -0
  57. package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +30 -0
  58. package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +1 -0
  59. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +35 -0
  60. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +23 -0
  61. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +1 -0
  62. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +71 -0
  63. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +386 -0
  64. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +11 -0
  65. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +79 -0
  66. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +17 -0
  67. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +64 -0
  68. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +29 -0
  69. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +32 -0
  70. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +10 -0
  71. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
  72. package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +36 -0
  73. package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +131 -6
  74. package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +9 -9
  75. package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +1 -0
  76. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
  77. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
  78. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
  79. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
  80. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -3
  81. package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
  82. package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
  83. package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
  84. package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
  85. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +1 -1
  86. package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
  87. package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
  88. package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
  89. package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
  90. package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
  91. package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
  92. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
  93. package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
  94. package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
  95. package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
  96. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +3 -3
  97. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
  98. package/app/components/03.organisms/site-header/SiteHeader.vue +8 -0
  99. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +1 -1
  100. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
  101. package/app/components/05.forms/input-button/InputButton.vue +3 -1
  102. package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
  103. package/app/composables/useAnchorScroll.ts +1 -1
  104. package/app/utils/opening-hours.ts +23 -2
  105. package/nuxt.config.ts +1 -1
  106. package/package.json +1 -1
  107. package/.claude/skills/components/site-navigation.md +0 -145
  108. package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
  109. package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
  110. package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
  111. package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
  112. package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
  113. package/app/components/02.molecules/navigation/site-navigation/tests/__snapshots__/SiteNavigation.spec.ts.snap +0 -30
@@ -4,7 +4,7 @@ exports[`ServiceSummaryGrid > renders correct HTML structure 1`] = `
4
4
  "<div class="service-summary-grid">
5
5
  <div class="service-summary">
6
6
  <div class="service-summary__grid">
7
- <div class="service-summary__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-installation.jpg 1x, /_ipx/_/images/locs-installation.jpg 2x" alt="Locs Installation" loading="eager" fetchpriority="high" class="service-summary__image" src="/_ipx/_/images/locs-installation.jpg"></div>
7
+ <div class="service-summary__image-wrapper"><img width="1080" height="1080" data-nuxt-img="" srcset="/_ipx/s_1080x1080/images/locs-installation.jpg 1x, /_ipx/s_2160x2160/images/locs-installation.jpg 2x" alt="Locs Installation" loading="eager" fetchpriority="high" class="service-summary__image" src="/_ipx/s_1080x1080/images/locs-installation.jpg"></div>
8
8
  <div class="service-summary__info-wrapper service-summary__info-wrapper--align-center">
9
9
  <div class="eyebrow-text large">Subtitle for Locs Installation</div>
10
10
  <h2 id="v-0-0-0" class="hero-text mb-20 title axis-horizontal">
@@ -17,7 +17,7 @@ exports[`ServiceSummaryGrid > renders correct HTML structure 1`] = `
17
17
  </div>
18
18
  <div class="service-summary">
19
19
  <div class="service-summary__grid">
20
- <div class="service-summary__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-retwist.jpg 1x, /_ipx/_/images/locs-retwist.jpg 2x" alt="Locs Retwist" loading="eager" fetchpriority="auto" class="service-summary__image" src="/_ipx/_/images/locs-retwist.jpg"></div>
20
+ <div class="service-summary__image-wrapper"><img width="1080" height="1080" data-nuxt-img="" srcset="/_ipx/s_1080x1080/images/locs-retwist.jpg 1x, /_ipx/s_2160x2160/images/locs-retwist.jpg 2x" alt="Locs Retwist" loading="eager" fetchpriority="auto" class="service-summary__image" src="/_ipx/s_1080x1080/images/locs-retwist.jpg"></div>
21
21
  <div class="service-summary__info-wrapper service-summary__info-wrapper--align-center">
22
22
  <div class="eyebrow-text large">Subtitle for Locs Retwist</div>
23
23
  <h2 id="v-0-0-1" class="hero-text mb-20 title axis-horizontal">
@@ -30,7 +30,7 @@ exports[`ServiceSummaryGrid > renders correct HTML structure 1`] = `
30
30
  </div>
31
31
  <div class="service-summary">
32
32
  <div class="service-summary__grid">
33
- <div class="service-summary__image-wrapper"><img data-nuxt-img="" srcset="/_ipx/_/images/colour-treatment.jpg 1x, /_ipx/_/images/colour-treatment.jpg 2x" alt="Colour Treatment" loading="lazy" fetchpriority="auto" class="service-summary__image" src="/_ipx/_/images/colour-treatment.jpg"></div>
33
+ <div class="service-summary__image-wrapper"><img width="1080" height="1080" data-nuxt-img="" srcset="/_ipx/s_1080x1080/images/colour-treatment.jpg 1x, /_ipx/s_2160x2160/images/colour-treatment.jpg 2x" alt="Colour Treatment" loading="lazy" fetchpriority="auto" class="service-summary__image" src="/_ipx/s_1080x1080/images/colour-treatment.jpg"></div>
34
34
  <div class="service-summary__info-wrapper service-summary__info-wrapper--align-center">
35
35
  <div class="eyebrow-text large">Subtitle for Colour Treatment</div>
36
36
  <h2 id="v-0-0-2" class="hero-text mb-20 title axis-horizontal">
@@ -3,20 +3,20 @@
3
3
  exports[`ServicesCardGrid > renders correct HTML structure 1`] = `
4
4
  "<div class="services-card-grid">
5
5
  <div class="services-card">
6
- <div class="grid-stack image-wrapper">
7
- <div class="grid-stack__layer"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-installation.jpg 1x, /_ipx/_/images/locs-installation.jpg 2x" alt="Locs Installation" loading="lazy" class="image" src="/_ipx/_/images/locs-installation.jpg"></div>
6
+ <div class="grid-stack services-card-image-wrapper">
7
+ <div class="grid-stack__layer"><img width="640" height="853" data-nuxt-img="" srcset="/_ipx/s_640x853/images/locs-installation.jpg 1x, /_ipx/s_1280x1706/images/locs-installation.jpg 2x" alt="Locs Installation" loading="lazy" class="services-card-image" src="/_ipx/s_640x853/images/locs-installation.jpg"></div>
8
8
  <div class="grid-stack__layer"></div>
9
9
  </div>
10
- <div class="details-wrapper">
10
+ <div class="services-card-details">
11
11
  <div class="eyebrow-text large">Subtitle for Locs Installation</div>
12
12
  <h2 class="hero-text heading axis-horizontal">
13
13
  <!--v-if--><span class="text-block-0 normal">Locs Installation</span>
14
14
  </h2>
15
- <div class="description">Short description for Locs Installation.</div>
16
- <div class="footer">
17
- <div class="meta">
18
- <div class="meta-duration">60 mins</div>
19
- <div class="meta-price">£50</div>
15
+ <div class="services-card-description">Short description for Locs Installation.</div>
16
+ <div class="services-card-footer">
17
+ <div class="services-card-meta">
18
+ <div class="services-card-meta-duration">60 mins</div>
19
+ <div class="services-card-meta-price">£50</div>
20
20
  </div><a href="/services/locs-installation" data-testid="input-button" data-theme="default" class="input-button secondary mbs-24 is-link">
21
21
  <!--v-if-->
22
22
  <!--v-if--><span class="button-text">Enquire about Locs Installation</span><span class="btn-icon right"><span class="iconify i-mdi:arrow-right icon" aria-hidden="true"></span></span>
@@ -26,20 +26,20 @@ exports[`ServicesCardGrid > renders correct HTML structure 1`] = `
26
26
  </div>
27
27
  </div>
28
28
  <div class="services-card">
29
- <div class="grid-stack image-wrapper">
30
- <div class="grid-stack__layer"><img data-nuxt-img="" srcset="/_ipx/_/images/locs-retwist.jpg 1x, /_ipx/_/images/locs-retwist.jpg 2x" alt="Locs Retwist" loading="lazy" class="image" src="/_ipx/_/images/locs-retwist.jpg"></div>
29
+ <div class="grid-stack services-card-image-wrapper">
30
+ <div class="grid-stack__layer"><img width="640" height="853" data-nuxt-img="" srcset="/_ipx/s_640x853/images/locs-retwist.jpg 1x, /_ipx/s_1280x1706/images/locs-retwist.jpg 2x" alt="Locs Retwist" loading="lazy" class="services-card-image" src="/_ipx/s_640x853/images/locs-retwist.jpg"></div>
31
31
  <div class="grid-stack__layer"></div>
32
32
  </div>
33
- <div class="details-wrapper">
33
+ <div class="services-card-details">
34
34
  <div class="eyebrow-text large">Subtitle for Locs Retwist</div>
35
35
  <h2 class="hero-text heading axis-horizontal">
36
36
  <!--v-if--><span class="text-block-0 normal">Locs Retwist</span>
37
37
  </h2>
38
- <div class="description">Short description for Locs Retwist.</div>
39
- <div class="footer">
40
- <div class="meta">
41
- <div class="meta-duration">60 mins</div>
42
- <div class="meta-price">£50</div>
38
+ <div class="services-card-description">Short description for Locs Retwist.</div>
39
+ <div class="services-card-footer">
40
+ <div class="services-card-meta">
41
+ <div class="services-card-meta-duration">60 mins</div>
42
+ <div class="services-card-meta-price">£50</div>
43
43
  </div><a href="/services/locs-retwist" data-testid="input-button" data-theme="default" class="input-button secondary mbs-24 is-link">
44
44
  <!--v-if-->
45
45
  <!--v-if--><span class="button-text">Enquire about Locs Retwist</span><span class="btn-icon right"><span class="iconify i-mdi:arrow-right icon" aria-hidden="true"></span></span>
@@ -49,20 +49,20 @@ exports[`ServicesCardGrid > renders correct HTML structure 1`] = `
49
49
  </div>
50
50
  </div>
51
51
  <div class="services-card">
52
- <div class="grid-stack image-wrapper">
53
- <div class="grid-stack__layer"><img data-nuxt-img="" srcset="/_ipx/_/images/colour-treatment.jpg 1x, /_ipx/_/images/colour-treatment.jpg 2x" alt="Colour Treatment" loading="lazy" class="image" src="/_ipx/_/images/colour-treatment.jpg"></div>
52
+ <div class="grid-stack services-card-image-wrapper">
53
+ <div class="grid-stack__layer"><img width="640" height="853" data-nuxt-img="" srcset="/_ipx/s_640x853/images/colour-treatment.jpg 1x, /_ipx/s_1280x1706/images/colour-treatment.jpg 2x" alt="Colour Treatment" loading="lazy" class="services-card-image" src="/_ipx/s_640x853/images/colour-treatment.jpg"></div>
54
54
  <div class="grid-stack__layer"></div>
55
55
  </div>
56
- <div class="details-wrapper">
56
+ <div class="services-card-details">
57
57
  <div class="eyebrow-text large">Subtitle for Colour Treatment</div>
58
58
  <h2 class="hero-text heading axis-horizontal">
59
59
  <!--v-if--><span class="text-block-0 normal">Colour Treatment</span>
60
60
  </h2>
61
- <div class="description">Short description for Colour Treatment.</div>
62
- <div class="footer">
63
- <div class="meta">
64
- <div class="meta-duration">60 mins</div>
65
- <div class="meta-price">£50</div>
61
+ <div class="services-card-description">Short description for Colour Treatment.</div>
62
+ <div class="services-card-footer">
63
+ <div class="services-card-meta">
64
+ <div class="services-card-meta-duration">60 mins</div>
65
+ <div class="services-card-meta-price">£50</div>
66
66
  </div><a href="/services/colour-treatment" data-testid="input-button" data-theme="default" class="input-button secondary mbs-24 is-link">
67
67
  <!--v-if-->
68
68
  <!--v-if--><span class="button-text">Enquire about Colour Treatment</span><span class="btn-icon right"><span class="iconify i-mdi:arrow-right icon" aria-hidden="true"></span></span>
@@ -20,6 +20,8 @@
20
20
  :main-nav-aria-label="mainNavAriaLabel"
21
21
  :secondary-nav-aria-label="secondaryNavAriaLabel"
22
22
  :overflow-menu-aria-label="overflowMenuAriaLabel"
23
+ :overflow-button-label="overflowButtonLabel"
24
+ :submenu-aria-label="submenuAriaLabel"
23
25
  >
24
26
  <template v-if="slots.secondaryNavigation" #secondaryNavigation>
25
27
  <slot name="secondaryNavigation"></slot>
@@ -54,6 +56,10 @@ interface Props {
54
56
  secondaryNavAriaLabel?: string;
55
57
  /** Forwarded to ResponsiveHeader's overflowMenuAriaLabel — override for localisation. */
56
58
  overflowMenuAriaLabel?: string;
59
+ /** Forwarded to ResponsiveHeader's overflowButtonLabel, the burger button's accessible name. */
60
+ overflowButtonLabel?: string;
61
+ /** Forwarded to ResponsiveHeader's submenuAriaLabel; {title} is replaced with the item's title. */
62
+ submenuAriaLabel?: string;
57
63
  }
58
64
 
59
65
  withDefaults(defineProps<Props>(), {
@@ -73,6 +79,8 @@ withDefaults(defineProps<Props>(), {
73
79
  mainNavAriaLabel: "Main navigation",
74
80
  secondaryNavAriaLabel: "Secondary navigation",
75
81
  overflowMenuAriaLabel: "Overflow navigation menu",
82
+ overflowButtonLabel: "More navigation",
83
+ submenuAriaLabel: "{title} submenu",
76
84
  });
77
85
 
78
86
  const slots = useSlots();
@@ -9,7 +9,7 @@ exports[`SiteHeader > renders correct HTML structure with default props 1`] = `
9
9
  <nav class="skip-links__nav" aria-label="Skip navigation"><a href="#main-content" class="skip-links__link">Skip to main content</a><a href="#footer-content" class="skip-links__link">Skip to footer</a></nav>
10
10
  </div>
11
11
  </nav>
12
- <div class="responsive-header-stub" main-nav-aria-label="Main navigation" secondary-nav-aria-label="Secondary navigation" overflow-menu-aria-label="Overflow navigation menu"></div>
12
+ <div class="responsive-header-stub" main-nav-aria-label="Main navigation" secondary-nav-aria-label="Secondary navigation" overflow-menu-aria-label="Overflow navigation menu" overflow-button-label="More navigation" submenu-aria-label="{title} submenu"></div>
13
13
  </header>
14
14
  </div>"
15
15
  `;
@@ -70,6 +70,14 @@ Same shape again:
70
70
  something this token set changes. The animated border added by `has-pending-effect` has its own
71
71
  `--pending-effect-*` tokens, see `../pending-effect/CONSUMER-STYLING.md`.
72
72
 
73
+ ### Text wrapping
74
+
75
+ | Token | Default | Controls |
76
+ |---|---|---|
77
+ | `--input-button-text-white-space` | `nowrap` | Set `normal` to let a long label wrap onto several lines instead of overflowing the button. Unbroken strings break anywhere once wrapping is on |
78
+
79
+ > Added 2026-10-06. `ServicesCardGrid` sets it to `normal` on its CTA buttons so a long service title stays inside the card.
80
+
73
81
  `InputCopy` and `PricingCard` fall back through `--input-button-primary-*` for their own copy/CTA
74
82
  buttons (see their own `CONSUMER-STYLING.md`) rather than duplicating this chain.
75
83
 
@@ -292,7 +292,9 @@ watch(
292
292
 
293
293
  .button-text {
294
294
  display: inline-block;
295
- white-space: nowrap;
295
+ min-inline-size: 0;
296
+ white-space: var(--input-button-text-white-space, nowrap);
297
+ overflow-wrap: anywhere;
296
298
  font-size: var(--button-font-size);
297
299
  line-height: var(--button-line-height);
298
300
  font-weight: var(--button-font-weight);
@@ -75,6 +75,14 @@ describe("useAnchorScroll", () => {
75
75
  expect(history.pushState).not.toHaveBeenCalled();
76
76
  });
77
77
 
78
+ it("still records the clicked hash as active", () => {
79
+ stubMatchMedia(true);
80
+ appendEl("overview");
81
+ const { handleNavClick, activeHash } = useAnchorScroll();
82
+ handleNavClick(makeEvent(), "#overview");
83
+ expect(activeHash.value).toBe("#overview");
84
+ });
85
+
78
86
  it("does not scroll", () => {
79
87
  stubMatchMedia(true);
80
88
  const el = appendEl("overview");
@@ -47,10 +47,10 @@ export const useAnchorScroll = (options: UseAnchorScrollOptions = {}) => {
47
47
  // and smooth-scroll ourselves.
48
48
  const handleNavClick = (event: MouseEvent, href: string): void => {
49
49
  if (!href.startsWith("#")) return;
50
+ activeHash.value = href;
50
51
  if (prefersReducedMotion()) return;
51
52
 
52
53
  event.preventDefault();
53
- activeHash.value = href;
54
54
  history.pushState(null, "", href);
55
55
  scrollToAnchor(href);
56
56
  };
@@ -8,8 +8,29 @@ import type {
8
8
 
9
9
  const SCHEMA_DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
10
10
 
11
- export const resolveOpeningStatus = (entry: { status?: OpeningStatus; sessions?: OpeningSession[] }): OpeningStatus =>
12
- entry.status ?? (entry.sessions?.length ? "open" : "closed");
11
+ // "HH:MM" from 00:00 to 23:59, else null so the caller can show the raw value instead of throwing
12
+ export const parseOpeningTime = (time: string): { hours: number; minutes: number } | null => {
13
+ const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
14
+ if (!match) return null;
15
+ const hours = Number(match[1]);
16
+ const minutes = Number(match[2]);
17
+ return hours < 24 && minutes < 60 ? { hours, minutes } : null;
18
+ };
19
+
20
+ // A real calendar date in "YYYY-MM-DD" form, as a UTC midnight Date, else null
21
+ export const parseOpeningDate = (iso: string): Date | null => {
22
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso.trim());
23
+ if (!match) return null;
24
+ const [y, m, d] = [Number(match[1]), Number(match[2]), Number(match[3])];
25
+ const date = new Date(Date.UTC(y, m - 1, d));
26
+ return date.getUTCFullYear() === y && date.getUTCMonth() === m - 1 && date.getUTCDate() === d ? date : null;
27
+ };
28
+
29
+ // "open" with no sessions has nothing to show, so it's treated as closed
30
+ export const resolveOpeningStatus = (entry: { status?: OpeningStatus; sessions?: OpeningSession[] }): OpeningStatus => {
31
+ const status = entry.status ?? "open";
32
+ return status === "open" && !entry.sessions?.length ? "closed" : status;
33
+ };
13
34
 
14
35
  const specsFor = (
15
36
  entry: { status?: OpeningStatus; sessions?: OpeningSession[] },
package/nuxt.config.ts CHANGED
@@ -150,7 +150,7 @@ export default defineNuxtConfig({
150
150
  // "esbuild". lightningcss has an open bug where it drops the unprefixed `backdrop-filter`
151
151
  // once autoprefixer adds `-webkit-backdrop-filter` alongside it, breaking glass/blur effects
152
152
  // (see https://github.com/vitejs/vite/issues/22649) — this repo relies on backdrop-filter in
153
- // several components (GlassPanel, SiteNavigation, TabNavigation, DisplayDialog,
153
+ // several components (GlassPanel, TabNavigation, DisplayDialog,
154
154
  // MagneticNavigation). Pin back to esbuild until upstream fixes it.
155
155
  build: {
156
156
  cssMinify: "esbuild",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.5.6",
4
+ "version": "9.6.0",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",
@@ -1,145 +0,0 @@
1
- # SiteNavigation
2
-
3
- ## Overview
4
-
5
- `SiteNavigation` is a responsive site-wide navigation component. It renders a horizontal link list on wide viewports and automatically collapses to a burger-menu + slide-down panel on narrow viewports. Collapse is driven by a `ResizeObserver` that compares the list's natural `scrollWidth` against the nav container's `clientWidth` — no breakpoint prop needed.
6
-
7
- Both the horizontal list and the panel include animated active/hover indicator decorators (underline indicator + background highlight) that snap into position using CSS custom properties set via JavaScript.
8
-
9
- ## Component location
10
-
11
- `app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue`
12
-
13
- ## Props
14
-
15
- | Prop | Type | Default | Description |
16
- |---|---|---|---|
17
- | `navItemData` | `NavItemData` | — (required) | Navigation items — see type below |
18
- | `navAlign` | `"left" \| "center" \| "right"` | `"left"` | Alignment of the horizontal nav list |
19
- | `ariaLabel` | `string` | `"Site navigation"` | aria-label on the root `<nav>` landmark — override for localisation |
20
- | `openMenuLabel` | `string` | `"Open navigation menu"` | Burger button text (screen-reader) while closed — override for localisation |
21
- | `closeMenuLabel` | `string` | `"Close navigation menu"` | Burger button text (screen-reader) while open — override for localisation |
22
- | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `<nav>` |
23
-
24
- ## NavItemData type
25
-
26
- **Within the layer:**
27
-
28
- ```ts
29
- import type { NavItemData } from "~/types/components/nav-item.d";
30
- ```
31
-
32
- **In a consuming app:**
33
-
34
- ```ts
35
- import type { NavItemData } from "srcdev-nuxt-components";
36
- ```
37
-
38
- ```ts
39
- const navItemData: NavItemData = {
40
- main: [
41
- { text: "Home", href: "/" },
42
- { text: "About", href: "/about" },
43
- { text: "Services", href: "/services", cssName: "is-featured" },
44
- { text: "Contact", href: "/contact", iconName: "heroicons:envelope", isExternal: false },
45
- ],
46
- };
47
- ```
48
-
49
- `NavItem` fields:
50
-
51
- | Field | Type | Description |
52
- |---|---|---|
53
- | `text` | `string` | Link label |
54
- | `href` | `string` | Link destination |
55
- | `isExternal` | `boolean?` | Passed to NuxtLink `:external` — opens in new tab |
56
- | `iconName` | `string?` | Iconify icon name rendered before the label |
57
- | `cssName` | `string?` | CSS class applied to the `<li>` element |
58
-
59
- ## Basic usage
60
-
61
- ```vue
62
- <SiteNavigation :nav-item-data="navItemData" nav-align="left" />
63
- ```
64
-
65
- Always use hyphenated prop names in templates (ESLint enforces this).
66
-
67
- ## CSS token API
68
-
69
- Set these tokens on a parent element (e.g. your `<header>`) to theme the navigation.
70
-
71
- ```css
72
- .your-header {
73
- /* ── Decorators ────────────────────────────────────── */
74
- --site-nav-decorator-indicator-color: var(--rose-05); /* underline bar */
75
- --site-nav-decorator-hovered-bg: transparent; /* hover pill behind a link */
76
- --site-nav-decorator-active-bg: transparent; /* active pill behind a link */
77
-
78
- /* ── Horizontal nav links ──────────────────────────── */
79
- --site-nav-link-color: var(--warm-01);
80
- --site-nav-link-hover-color: var(--rose-04);
81
- --site-nav-link-active-color: var(--rose-05);
82
- --site-nav-link-size: 1.6rem;
83
- --site-nav-link-weight: 400;
84
- --site-nav-link-tracking: 0.06em;
85
- --site-nav-gap: 2.2rem;
86
- --site-nav-transition: 250ms ease;
87
-
88
- /* ── Mobile panel ──────────────────────────────────── */
89
- --site-nav-panel-bg: var(--page-bg, #1a1614);
90
- --site-nav-panel-border-color: color-mix(in oklch, var(--rose-05) 35%, transparent);
91
- --site-nav-panel-link-color: var(--warm-01);
92
- --site-nav-panel-link-hover-color: var(--rose-04);
93
- --site-nav-panel-link-active-color: var(--rose-05);
94
- --site-nav-panel-padding-block: 1.4rem;
95
- --site-nav-panel-padding-inline: 1.5rem;
96
- --site-nav-panel-slide-duration: 350ms;
97
- --site-nav-panel-slide-easing: cubic-bezier(0.4, 0, 0.2, 1);
98
- --site-nav-panel-decorator-indicator-color: var(--rose-05);
99
- --site-nav-panel-indicator-left: 0; /* position the panel indicator bar */
100
- --site-nav-panel-indicator-right: auto;
101
-
102
- /* ── Burger button ─────────────────────────────────── */
103
- --site-nav-burger-color: var(--warm-01);
104
- --site-nav-burger-width: 22px;
105
- --site-nav-burger-height: 1.5px;
106
- --site-nav-burger-gap: 5px;
107
- --site-nav-burger-transition: 300ms ease;
108
-
109
- }
110
- ```
111
-
112
- The backdrop is teleported to `<body>`, so its tokens only land when set on `:root`/`html`/`body`,
113
- not on your header:
114
-
115
- ```css
116
- :where(html) {
117
- --site-nav-backdrop-bg: oklch(0% 0 0 / 55%);
118
- --site-nav-backdrop-blur: 3px;
119
- --site-nav-backdrop-duration: 350ms;
120
- --site-nav-backdrop-z-index: 10; /* keep your header above this */
121
- }
122
- ```
123
-
124
- Full reference: `app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md`.
125
-
126
- ## Behaviour notes
127
-
128
- - **Collapse detection**: `ResizeObserver` fires on every container resize. The list's `scrollWidth` is cached whenever the list is in the DOM; that cached value is compared against the container's `clientWidth` to set `isCollapsed`.
129
- - **isLoaded state**: The component uses `useState("site-nav-loaded")` — a Nuxt shared state — to gate visibility until the first measurement is complete, preventing a flash of the wrong nav state on load. The nav renders `opacity: 0` until `is-loaded` is applied.
130
- - **Teleport**: The backdrop overlay is teleported to `<body>` via `<Teleport>` and is only mounted when `isCollapsed && isLoaded`.
131
- - **Panel inert**: The panel div (id `site-nav-panel-{useId}`, unique per instance) receives `:inert="!isMenuOpen ? true : undefined"` — it is inert (keyboard/pointer-inaccessible) when closed.
132
- - **Decorator init**: `initNavDecorators()` and `initPanelDecorators()` inject `<li>` elements with CSS-driven indicator `<div>`s. They query `[data-nav-item]` / `[data-panel-nav-item]` to find links, and look for `router-link-active` to set the initial active position.
133
-
134
- ## Accessibility
135
-
136
- - Root `<nav>` has `aria-label` from the `ariaLabel` prop (default `"Site navigation"`).
137
- - Burger button text comes from `openMenuLabel`/`closeMenuLabel`, with `aria-expanded` (string `"true"/"false"`) and `aria-controls` pointing at the panel's per-instance id (was a fixed `site-nav-panel`, which collided when two navs were on one page; fixed 2026-09-27).
138
- - Backdrop and indicator `<li>` elements are `aria-hidden="true"`.
139
- - Panel div is `inert` when closed.
140
-
141
- ## Related files
142
-
143
- - Type: `app/types/components/nav-item.d.ts`
144
- - Tests: `app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts`
145
- - Story: `app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts`
@@ -1,38 +0,0 @@
1
- {
2
- "SRCDEV SiteNavigation": {
3
- "description": "SiteNavigation: horizontal links that collapse to a burger panel when they don't fit",
4
- "scope": "vue,html",
5
- "body": [
6
- "<SiteNavigation",
7
- " :nav-item-data=\"$1navItems\"",
8
- " nav-align=\"${2|left,center,right|}\"",
9
- " aria-label=\"${3:Site navigation}\"",
10
- "/>"
11
- ]
12
- },
13
- "SRCDEV SiteNavigation Items": {
14
- "description": "NavItemData object for SiteNavigation",
15
- "scope": "javascript,typescript",
16
- "body": [
17
- "const $1navItems: NavItemData = {",
18
- " main: [",
19
- " { text: \"$2Home\", href: \"/\" },",
20
- " { text: \"$3About\", href: \"/about\" },",
21
- " ],",
22
- "};"
23
- ]
24
- },
25
- "SRCDEV SiteNavigation CSS Override": {
26
- "description": "CSS override scaffold for SiteNavigation tokens (backdrop tokens go on :root)",
27
- "scope": "css",
28
- "body": [
29
- ".$1site-header {",
30
- " --site-nav-link-color: $2;",
31
- " --site-nav-link-hover-color: $3;",
32
- " --site-nav-decorator-indicator-color: $4;",
33
- " --site-nav-panel-bg: $5;",
34
- " --site-nav-burger-color: $6;",
35
- "}"
36
- ]
37
- }
38
- }
@@ -1,109 +0,0 @@
1
- # SiteNavigation — Consumer Styling Guide
2
-
3
- ## Public token API
4
-
5
- Defaults that read `var(--slate-01, currentColor)` use the library palette when it's loaded and fall
6
- back to the inherited text colour otherwise.
7
-
8
- ### Horizontal nav
9
-
10
- | Token | Default | Controls |
11
- |---|---|---|
12
- | `--site-nav-link-color` | `var(--slate-01, currentColor)` | Link colour |
13
- | `--site-nav-link-hover-color` | `var(--slate-04, <link colour>)` | Link colour on hover |
14
- | `--site-nav-link-active-color` | `var(--slate-01, <link colour>)` | Active link colour |
15
- | `--site-nav-link-accent` | `var(--slate-01, currentColor)` | Link accent colour |
16
- | `--site-nav-link-size` | `1.6rem` | Link font size |
17
- | `--site-nav-link-weight` | `400` | Link font weight |
18
- | `--site-nav-link-tracking` | `0.06em` | Link letter spacing |
19
- | `--site-nav-gap` | `2.2rem` | Gap between links |
20
- | `--site-nav-transition` | `250ms ease` | Link colour transition |
21
- | `--site-nav-decorator-indicator-color` | `var(--slate-01, currentColor)` | Sliding underline bar |
22
- | `--site-nav-decorator-hovered-bg` | `transparent` | Sliding pill behind the hovered link |
23
- | `--site-nav-decorator-active-bg` | `transparent` | Pill behind the active link |
24
-
25
- > Changed 2026-09-27: `--site-nav-decorator-hovered-bg` and `--site-nav-decorator-active-bg` are
26
- > new. The values were hardcoded `transparent` with no override, unlike the panel equivalents.
27
-
28
- ### Collapsed panel
29
-
30
- | Token | Default | Controls |
31
- |---|---|---|
32
- | `--site-nav-panel-bg` | `var(--page-bg, #1a1614)` | Panel background |
33
- | `--site-nav-panel-border-color` | `color-mix(in oklch, var(--slate-01, #c0847a) 35%, transparent)` | Top border when open |
34
- | `--site-nav-panel-item-border` | `color-mix(in oklch, var(--slate-01, white) 8%, transparent)` | Divider between panel links |
35
- | `--site-nav-panel-link-color` | `var(--slate-01, currentColor)` | Panel link colour |
36
- | `--site-nav-panel-link-hover-color` | `var(--slate-04, <panel link colour>)` | Panel link hover colour |
37
- | `--site-nav-panel-link-active-color` | `var(--slate-01, <panel link colour>)` | Active panel link colour |
38
- | `--site-nav-panel-padding-block` | `1.4rem` | Panel link vertical padding |
39
- | `--site-nav-panel-padding-inline` | `1.5rem` | Panel link horizontal padding |
40
- | `--site-nav-panel-slide-duration` | `350ms` | Open/close slide duration |
41
- | `--site-nav-panel-slide-easing` | `cubic-bezier(0.4, 0, 0.2, 1)` | Open/close easing |
42
- | `--site-nav-panel-decorator-indicator-color` | `var(--slate-01, currentColor)` | Panel indicator bar |
43
- | `--site-nav-panel-decorator-hovered-bg` | `transparent` | Pill behind the hovered panel link |
44
- | `--site-nav-panel-decorator-active-bg` | `transparent` | Pill behind the active panel link |
45
- | `--site-nav-panel-indicator-left` | `0` | Indicator bar left position |
46
- | `--site-nav-panel-indicator-right` | `auto` | Indicator bar right position |
47
-
48
- ### Burger button
49
-
50
- | Token | Default | Controls |
51
- |---|---|---|
52
- | `--site-nav-burger-color` | `var(--slate-01, currentColor)` | Bar colour |
53
- | `--site-nav-burger-width` | `22px` | Bar width |
54
- | `--site-nav-burger-height` | `1.5px` | Bar thickness |
55
- | `--site-nav-burger-gap` | `5px` | Gap between bars |
56
- | `--site-nav-burger-transition` | `300ms ease` | Open/close morph |
57
-
58
- ### Backdrop (teleported to `<body>`)
59
-
60
- | Token | Default | Controls |
61
- |---|---|---|
62
- | `--site-nav-backdrop-bg` | `oklch(0% 0 0 / 55%)` | Scrim colour |
63
- | `--site-nav-backdrop-blur` | `3px` | Blur behind the scrim |
64
- | `--site-nav-backdrop-duration` | `350ms` | Fade duration |
65
- | `--site-nav-backdrop-z-index` | `10` | Scrim stacking order; keep your header above it |
66
-
67
- > Changed 2026-09-27: `--site-nav-backdrop-z-index` is new (was a hardcoded `10`).
68
-
69
- The `--_*` variables declared on `.site-navigation` are the resolved copies of the tokens above
70
- that are read in more than one place: not public API.
71
-
72
- ---
73
-
74
- ## State hooks
75
-
76
- | Hook | Element | When |
77
- |---|---|---|
78
- | `.site-navigation--left`, `--center`, `--right` | root `<nav>` | `nav-align` |
79
- | `.is-collapsed` | root | Links don't fit, so the burger and panel are shown |
80
- | `.is-loaded` | root | First measurement done (the nav is `opacity: 0` until then) |
81
- | `.menu-open` | root | Panel open |
82
- | `.is-animated` | root | Indicator transitions enabled (after first layout) |
83
- | `li.is-active` | nav and panel items | Item matches the current route |
84
- | `.is-open` | `.site-nav-burger`, `.site-nav-panel`, `.site-nav-backdrop` | Panel open |
85
-
86
- Inner classes: `.site-nav-list`, `.site-nav-link`, `.site-nav-burger`, `.burger-bar`,
87
- `.site-nav-panel`, `.site-nav-panel-inner`, `.site-nav-panel-list`, `.site-nav-panel-link`,
88
- `.site-nav-backdrop`; decorators `.nav__hovered`, `.nav__active`, `.nav__active-indicator`.
89
- Each item `li` also gets its `NavItem.cssName`.
90
-
91
- ---
92
-
93
- ## Local overrides
94
-
95
- Set the tokens above on an element you own (a page or section class, or a class added with
96
- `:style-class-passthrough`): they inherit down into the component. Keep the block **unlayered** (no
97
- `@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
98
- `<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
99
- `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
100
-
101
- **Caveat:** the backdrop is teleported to `<body>`, outside your header, so the
102
- `--site-nav-backdrop-*` tokens only land when set on `:root`, `html` or `body`.
103
-
104
- ---
105
-
106
- ## Class passthrough
107
-
108
- `style-class-passthrough` adds classes to the root `<nav>`. Reactive after mount. It doesn't reach
109
- the teleported backdrop.