srcdev-nuxt-components 9.5.5 → 9.5.7
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/skills/component-compliance-checklist.md +6 -2
- package/.claude/skills/components/action-menu.md +21 -15
- package/.claude/skills/components/deep-expanding-menu.md +18 -6
- package/.claude/skills/components/display-tooltip-defined.md +2 -1
- package/.claude/skills/components/display-tooltip.md +10 -4
- package/.claude/skills/components/navigation-items.md +1 -0
- package/.claude/skills/components/opening-hours.md +12 -0
- package/.claude/skills/components/pop-over.md +11 -8
- package/.claude/skills/components/price-list.md +10 -1
- package/.claude/skills/components/responsive-header.md +25 -0
- package/.claude/skills/components/select-menu.md +18 -8
- package/.claude/skills/components/site-header.md +2 -0
- package/.claude/skills/components/ui-block-decorated.md +7 -0
- package/.claude/skills/composable-anchored-popover.md +108 -0
- package/.claude/skills/composable-tooltips-guide.md +3 -1
- package/.claude/skills/index.md +1 -0
- package/.claude/skills/storybook-add-story.md +14 -0
- package/.vscode/srcdev-component-responsive-header.code-snippets +3 -1
- package/.vscode/srcdev-component-site-header.code-snippets +3 -1
- package/app/assets/styles/setup/06.utility-classes/animations/_animation-scroller-x.css +5 -3
- package/app/assets/styles/setup/06.utility-classes/animations/_auto-rotate.css +5 -3
- package/app/assets/styles/setup/06.utility-classes/animations/_entry-exit-blur.css +5 -3
- package/app/assets/styles/setup/06.utility-classes/animations/_entry-slide-in.css +5 -3
- package/app/assets/styles/setup/06.utility-classes/animations/_entry-zoom-reveal.css +5 -3
- package/app/components/01.atoms/animations/entry/CONSUMER-STYLING.md +6 -2
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +15 -3
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +62 -3
- package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +36 -1
- package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +1 -1
- package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +1 -1
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +9 -3
- package/app/components/01.atoms/pop-over/PopOver.vue +70 -9
- package/app/components/01.atoms/pop-over/tests/PopOver.spec.ts +53 -0
- package/app/components/01.atoms/pop-over/tests/__snapshots__/PopOver.spec.ts.snap +1 -1
- package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +12 -4
- package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +20 -16
- package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +124 -96
- package/app/components/02.molecules/action-menu/ActionMenu.vue +41 -16
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +16 -3
- package/app/components/02.molecules/action-menu/tests/ActionMenu.spec.ts +28 -0
- package/app/components/02.molecules/action-menu/tests/__snapshots__/ActionMenu.spec.ts.snap +3 -3
- package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +2 -1
- package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +7 -4
- package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +20 -1
- package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +18 -4
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +87 -16
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +83 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +2 -2
- package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +1 -1
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +1 -1
- package/app/components/02.molecules/opening-hours/CONSUMER-STYLING.md +11 -1
- package/app/components/02.molecules/opening-hours/OpeningHours.vue +71 -43
- package/app/components/02.molecules/opening-hours/stories/OpeningHours.stories.ts +138 -6
- package/app/components/02.molecules/opening-hours/tests/OpeningHours.spec.ts +53 -0
- package/app/components/02.molecules/opening-hours/tests/__snapshots__/OpeningHours.spec.ts.snap +1 -1
- package/app/components/02.molecules/opening-hours/tests/buildOpeningHoursSpecification.spec.ts +37 -1
- package/app/components/02.molecules/price-list/CONSUMER-STYLING.md +17 -5
- package/app/components/02.molecules/price-list/PriceList.vue +32 -9
- package/app/components/02.molecules/price-list/stories/PriceList.stories.ts +125 -29
- package/app/components/02.molecules/price-list/tests/PriceList.spec.ts +20 -0
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +17 -5
- package/app/components/02.molecules/select-menu/SelectMenu.vue +51 -16
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +36 -0
- package/app/components/03.organisms/google-reviews/stories/GoogleReviews.stories.ts +9 -4
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +14 -0
- package/app/components/03.organisms/responsive-header/NavigationItems.vue +23 -14
- package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +58 -30
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +149 -18
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +21 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +66 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +30 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +1 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +35 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +23 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +1 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +71 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +386 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +11 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +79 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +17 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +64 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +29 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +32 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +10 -0
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +16 -0
- package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +91 -6
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +9 -9
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +1 -0
- package/app/components/03.organisms/site-header/SiteHeader.vue +8 -0
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +1 -1
- package/app/composables/tests/useAnchoredPopover.spec.ts +231 -0
- package/app/composables/tests/useTooltips.spec.ts +69 -0
- package/app/composables/useAnchoredPopover.ts +183 -0
- package/app/composables/useTooltips.ts +37 -20
- package/app/utils/opening-hours.ts +23 -2
- package/package.json +1 -1
|
@@ -13,7 +13,8 @@ skip silently. Notes marked **Existing component** only apply when migrating.
|
|
|
13
13
|
|
|
14
14
|
A component passes when its Component Ledger row (`node .claude/component-ledger/build.mjs`, then
|
|
15
15
|
`.claude/component-ledger/audit.json`) shows `score: 5`, a real tier, and `variants`, `legacy_props`,
|
|
16
|
-
`story_args_bug`, `eslint_issues`, `redundant_priv_tokens
|
|
16
|
+
`story_args_bug`, `eslint_issues`, `redundant_priv_tokens`, `styling_doc_outdated` and `missing_stress_story`
|
|
17
|
+
all `false`.
|
|
17
18
|
The ledger can't see items 9, 10, 11 and 14, so check those by hand.
|
|
18
19
|
|
|
19
20
|
## The checklist
|
|
@@ -131,7 +132,10 @@ The ledger can't see items 9, 10, 11 and 14, so check those by hand.
|
|
|
131
132
|
covers ellipsis too (`1` is single-line ellipsis). Default it to `none` unless the layout
|
|
132
133
|
clearly needs a cap, and never clamp text that must stay readable (legal or attribution text,
|
|
133
134
|
link text that names its destination). Shape and rules: `theming-component-token-pattern.md`,
|
|
134
|
-
"Line-clamp tokens".
|
|
135
|
+
"Line-clamp tokens". Give each line-clamp token a Controls-panel select in the stories (set on
|
|
136
|
+
a wrapper `:style`, stripped from the component's args), labelled as a story-only CSS token so
|
|
137
|
+
nobody mistakes it for a prop: `storybook-add-story.md`, "Label token controls as story-only".
|
|
138
|
+
Add a unit test for any logic fix. A component that takes no data or copy
|
|
135
139
|
(e.g. a pure layout wrapper) still gets one with oversized slot content.
|
|
136
140
|
7. **Skill doc** — create or update `.claude/skills/components/<component-name>.md`, following the
|
|
137
141
|
pattern of an existing one. A labelled `<Name>Field` wrapper (or any other wrapper) is documented
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
Clicking it opens an anchored menu list populated via indexed dynamic slots (`item-{n}`). Each slot
|
|
9
9
|
should contain a single `ActionMenuItem` — either a `<button>` (for actions) or a link (for
|
|
10
10
|
navigation). The popover API and CSS anchor positioning handle positioning and dismiss behaviour
|
|
11
|
-
natively
|
|
11
|
+
natively, with a JS fallback for older Safari (see Notes).
|
|
12
12
|
|
|
13
13
|
**Location**: `app/components/02.molecules/action-menu/`
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ natively; no JavaScript click-outside logic is needed.
|
|
|
16
16
|
|
|
17
17
|
## Components
|
|
18
18
|
|
|
19
|
-
### ActionMenu
|
|
19
|
+
### `ActionMenu`
|
|
20
20
|
|
|
21
21
|
| Prop | Type | Default | Notes |
|
|
22
22
|
|---|---|---|---|
|
|
@@ -24,7 +24,7 @@ natively; no JavaScript click-outside logic is needed.
|
|
|
24
24
|
| `triggerIcon` | `string` | `"lucide:ellipsis"` | Icon on the trigger button. |
|
|
25
25
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root `<div>`. |
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
#### Slots
|
|
28
28
|
|
|
29
29
|
| Slot | When used |
|
|
30
30
|
|---|---|
|
|
@@ -32,7 +32,7 @@ natively; no JavaScript click-outside logic is needed.
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
-
### ActionMenuItem
|
|
35
|
+
### `ActionMenuItem`
|
|
36
36
|
|
|
37
37
|
| Prop | Type | Default | Notes |
|
|
38
38
|
|---|---|---|---|
|
|
@@ -41,19 +41,20 @@ natively; no JavaScript click-outside logic is needed.
|
|
|
41
41
|
| `arrowIcon` | `string` | `"lucide:arrow-right"` | Decorative trailing arrow icon. |
|
|
42
42
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root element. |
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
#### Slots
|
|
45
45
|
|
|
46
46
|
| Slot | Content |
|
|
47
47
|
|---|---|
|
|
48
48
|
| `#icon` | Optional left icon (e.g. `<Icon name="lucide:pencil" />`). Wrapped in `aria-hidden` span. |
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
#### Emits
|
|
51
51
|
|
|
52
52
|
| Event | Payload | Notes |
|
|
53
53
|
|---|---|---|
|
|
54
54
|
| `click` | `MouseEvent` | Fired on every click regardless of whether the item is a button or link. |
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
#### Notes on routing
|
|
57
|
+
|
|
57
58
|
- Internal paths (`/…`) resolve to `<NuxtLink>` via `resolveComponent`.
|
|
58
59
|
- External URLs or relative paths without a leading `/` render as plain `<a>`.
|
|
59
60
|
- `type="button"` is set automatically on `<button>` elements to prevent accidental form submission.
|
|
@@ -63,7 +64,7 @@ natively; no JavaScript click-outside logic is needed.
|
|
|
63
64
|
## Basic usage
|
|
64
65
|
|
|
65
66
|
```vue
|
|
66
|
-
<ActionMenu
|
|
67
|
+
<ActionMenu label="Row actions">
|
|
67
68
|
<template #item-0>
|
|
68
69
|
<ActionMenuItem label="Edit" @click="handleEdit">
|
|
69
70
|
<template #icon><Icon name="lucide:pencil" /></template>
|
|
@@ -122,13 +123,18 @@ Quick reference:
|
|
|
122
123
|
|
|
123
124
|
## Notes
|
|
124
125
|
|
|
125
|
-
- **Popover API + CSS anchor positioning** —
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
- **Popover API + CSS anchor positioning, with fallbacks** — open/close and positioning go through
|
|
127
|
+
the shared `useAnchoredPopover` composable, same as `SelectMenu`. Without anchor
|
|
128
|
+
positioning (Safari 17–18) JS measures the trigger and writes `--_anchor-*` px values
|
|
129
|
+
for the `@supports not (anchor-name: --a)` block. Without the Popover API (Safari 16) the trigger
|
|
130
|
+
toggles `isOpen`, `.action-menu-popover-open` shows the menu, outside pointerdown and Escape
|
|
131
|
+
close it, and `--action-menu-popover-z-index` (default `999999`) applies. See the `select-menu`
|
|
132
|
+
skill doc for the details and the `:popover-open` selector-list caveat.
|
|
133
|
+
- **`aria-expanded`** on the trigger tracks the open state in every browser (added 2026-10-05).
|
|
134
|
+
- **Auto-close** — clicking any `<li>` row calls the composable's `hide()` (native `hidePopover()`
|
|
135
|
+
or the fallback). The `ActionMenuItem` emitting `click` triggers normally before the menu closes.
|
|
136
|
+
- **Focus management** — on open (the `toggle` event, or after render in the fallback), focus moves
|
|
137
|
+
to the first `[role="menuitem"]` inside the popover.
|
|
132
138
|
- **Keyboard navigation `currentIndex === -1` guard** — `handleKeydown` computes the current
|
|
133
139
|
position via `items.indexOf(document.activeElement)`. When focus is outside the menu this returns
|
|
134
140
|
`-1`. Always guard explicitly before applying wrap-around math: `ArrowDown` should focus
|
|
@@ -13,12 +13,12 @@ type: reference
|
|
|
13
13
|
anchor-positioning (`anchor-name`/`position-anchor`/`anchor()`) and the native Popover API
|
|
14
14
|
(`popover`/`popovertarget`).
|
|
15
15
|
|
|
16
|
-
**Browser support
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
**Browser support:** since 2026-10-05 open/close and positioning go through the shared
|
|
17
|
+
`useAnchoredPopover` composable (see `composable-anchored-popover.md`), so the menu also works
|
|
18
|
+
without anchor positioning (Safari 17–18: JS-measured `position: fixed`) and without the Popover
|
|
19
|
+
API (Safari 16: JS open/close, outside click and Escape). Before that, panels never opened on
|
|
20
|
+
Safari 16. [`DeepExpandingMenuClassic`](deep-expanding-menu-classic.md) is still the alternative
|
|
21
|
+
built on `<details>`/`<summary>`: same API and visual result, with no popover layer involved.
|
|
22
22
|
|
|
23
23
|
## Props
|
|
24
24
|
|
|
@@ -82,6 +82,18 @@ See `CONSUMER-STYLING.md` for the full token API and what's not tokenised.
|
|
|
82
82
|
- Auto-imported in Nuxt — no manual import needed.
|
|
83
83
|
- Anchor names and popover target ids are generated per-instance via `useId()`, so multiple
|
|
84
84
|
instances on one page don't collide.
|
|
85
|
+
- One `useAnchoredPopover` instance serves every group: `activeKey` tracks the open group, and the
|
|
86
|
+
composable's `triggerRef`/`popoverRef` are computeds over per-group element maps (filled by
|
|
87
|
+
function refs). Native `toggle` events for two auto popovers can arrive in either order (B opens,
|
|
88
|
+
then A closes), so a close event is ignored unless it's for the active group.
|
|
89
|
+
- Each group toggle has `aria-expanded`; the caret flip keys off it rather than
|
|
90
|
+
`:has(:popover-open)`, so it works in the fallback too.
|
|
91
|
+
- Panels have no ARIA role: this is the disclosure-navigation pattern (a toggle with
|
|
92
|
+
`aria-expanded` revealing ordinary links), not an ARIA menu. `role="menu"` was removed 2026-10-05
|
|
93
|
+
because it requires `menuitem` children and arrow-key handling the panels don't have, so screen
|
|
94
|
+
readers announced a menu that didn't behave like one. Don't add it back.
|
|
95
|
+
- 2026-10-05: removed two never-referenced `@position-try` blocks (`--anchor-left`, and an invalid
|
|
96
|
+
`@position-try-fallbacks --anchor-right` at-rule).
|
|
85
97
|
- 2026-09-07 migration: moved from an unplaced top-level folder into
|
|
86
98
|
`02.molecules/navigation/deep-expanding-menu/`; converted options-style `defineProps` to
|
|
87
99
|
`interface Props` + `withDefaults`; the previous hardcoded `nav-1` anchor/popover id prefix
|
|
@@ -10,7 +10,8 @@ type: reference
|
|
|
10
10
|
|
|
11
11
|
`DisplayTooltipDefined` composes [`DisplayTooltip`](display-tooltip.md), supplying a structured
|
|
12
12
|
title/body/action content shape (each rendered with a consumer-chosen tag) plus a built-in close
|
|
13
|
-
button wired to the popover's `popovertarget
|
|
13
|
+
button wired to the popover's `popovertarget` and to `DisplayTooltip`'s scoped `close()`, so it also
|
|
14
|
+
closes the tooltip on Safari 16 (no Popover API). Use this when you want the standard tooltip content
|
|
14
15
|
layout without hand-building the `tooltipContent` slot markup each time; use plain `DisplayTooltip`
|
|
15
16
|
directly when you need full control over the popover's content.
|
|
16
17
|
|
|
@@ -14,9 +14,11 @@ the native Popover API (`popover`/`popovertarget`) and CSS anchor-positioning
|
|
|
14
14
|
with a built-in close button, see [`DisplayTooltipDefined`](display-tooltip-defined.md), which
|
|
15
15
|
composes this component.
|
|
16
16
|
|
|
17
|
-
**Browser support
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
**Browser support:** since 2026-10-05 open/close and positioning go through the shared
|
|
18
|
+
`useAnchoredPopover` composable (`side: "right"`, see `composable-anchored-popover.md`). Without
|
|
19
|
+
anchor positioning (Safari 17–18) the panel is placed from the trigger's measured position, flipping
|
|
20
|
+
to the left when there's no room on the right. Without the Popover API (Safari 16) it opens and
|
|
21
|
+
closes in JS, with outside-click and Escape dismissal. The trigger has `aria-expanded`.
|
|
20
22
|
|
|
21
23
|
## Props
|
|
22
24
|
|
|
@@ -32,7 +34,7 @@ requirement, same caution as `DeepExpandingMenu`'s equivalent gap.
|
|
|
32
34
|
| Slot | Description |
|
|
33
35
|
|------|-------------|
|
|
34
36
|
| `triggerContent` | Optional content rendered before the trigger button (e.g. a label the tooltip icon sits next to). |
|
|
35
|
-
| `tooltipContent` | Content rendered inside the popover panel. |
|
|
37
|
+
| `tooltipContent` | Content rendered inside the popover panel. Scoped prop `close()` hides the panel in every browser: wire your own close button to it (`#tooltipContent="{ close }"`), since `popovertargetaction="hide"` alone does nothing without the Popover API. |
|
|
36
38
|
|
|
37
39
|
## Basic usage
|
|
38
40
|
|
|
@@ -54,6 +56,10 @@ popover width/colours/radius, and the close-button tokens `DisplayTooltipDefined
|
|
|
54
56
|
|
|
55
57
|
## Notes
|
|
56
58
|
|
|
59
|
+
- Slotted content: direct children of `tooltipContent` get `margin-block: 0` (in `:where()`, so
|
|
60
|
+
consumer margins win) and the panel sets `--display-tooltip-popover-font-size` (`1.4rem`) and
|
|
61
|
+
`-line-height` (`1.4`), so a bare `<p>` reads as tooltip text without extra styling.
|
|
62
|
+
|
|
57
63
|
- Auto-imported in Nuxt — no manual import needed.
|
|
58
64
|
- `tooltipId` is only reactive at mount — changing it after mount does not regenerate the linked
|
|
59
65
|
`popovertarget`/`id` pair reactively beyond Vue's normal re-render, since both derive from the
|
|
@@ -18,6 +18,7 @@ active-route highlighting, hover indicator).
|
|
|
18
18
|
| `mainNavigationState` | `ResponsiveHeaderState` | `{ clonedNavLinks: {}, navListVisibility: {}, hasSecondNav: false }` | Shared geometry/visibility state, normally supplied by the parent `ResponsiveHeader` — not something a consumer constructs by hand outside of tests/stories. |
|
|
19
19
|
| `panelVariant` | `"modern" \| "classic"` | `"classic"` | Which panel component renders a dropdown's submenu: `ExpandingPanel` (`"modern"`) or `ExpandingPanelClassic` (`"classic"`, default). `"modern"` is known not to work correctly on WebKit. Normally set via `ResponsiveHeader`'s own `panelVariant` prop rather than directly. See CLAUDE.md pitfall #19. |
|
|
20
20
|
| `ariaLabel` | `string` | `"Overflow navigation menu"` | aria-label on the root `role="menu"` element — override for localisation. Normally set via `ResponsiveHeader`'s own `overflowMenuAriaLabel` prop rather than directly. |
|
|
21
|
+
| `submenuAriaLabel` | `string` | `"{title} submenu"` | aria-label on each submenu summary; `{title}` is replaced with `childLinksTitle` (or `name`). Normally set via `ResponsiveHeader`'s `submenuAriaLabel`. |
|
|
21
22
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.overflow-navigation-wrapper` element. |
|
|
22
23
|
|
|
23
24
|
---
|
|
@@ -78,6 +78,18 @@ earlier than `opens` means the session runs past midnight and is displayed as gi
|
|
|
78
78
|
- Grouping compares status, sessions and note; only **consecutive** days (in `week-starts-on`
|
|
79
79
|
order) group. No wrap-around from Sunday to Monday.
|
|
80
80
|
- Exceptions don't alter the weekly table; they are listed separately.
|
|
81
|
+
- **Bad data never throws** (since 2026-10-05): a time that isn't a valid `HH:MM` from `00:00` to
|
|
82
|
+
`23:59` (e.g. `"abc"`, `"25:99"`, `"24:00"`) and an exception date that isn't a real
|
|
83
|
+
`YYYY-MM-DD` are shown exactly as written. An invalid `locale` falls back to `en-GB`, and an
|
|
84
|
+
invalid `timeZone` to the browser's own. An exception whose `to` is before its `from` is shown
|
|
85
|
+
the right way round, and duplicate exception dates both render. The checks are the
|
|
86
|
+
auto-imported `parseOpeningTime` and `parseOpeningDate` utils (`app/utils/opening-hours.ts`).
|
|
87
|
+
- A session that closes after midnight (`22:00`–`02:00`) displays as written; schema.org reads
|
|
88
|
+
`closes` before `opens` as the next day.
|
|
89
|
+
- `status: "open"` with no sessions is treated as closed (since 2026-10-05): the row shows
|
|
90
|
+
`closedLabel`, gets `data-status="closed"`, and structured data treats it as closed too.
|
|
91
|
+
- Long text wraps rather than overflowing; notes and exception labels have line-clamp tokens
|
|
92
|
+
(`CONSUMER-STYLING.md`). The `StressTest` story covers the worst-case data.
|
|
81
93
|
- Today's highlight and past-exception hiding run in `onMounted`, never on the server, so cached
|
|
82
94
|
HTML can't carry a stale day. Past exceptions are briefly visible before hydration.
|
|
83
95
|
- **Structured data**: emits `{ "@type": structuredData.type ?? "LocalBusiness", "@id", name,
|
|
@@ -15,9 +15,12 @@ with fully consumer-supplied content, built on the native Popover API
|
|
|
15
15
|
text) or `ActionMenu` (menu semantics with arrow-key navigation), `PopOver` makes no assumption
|
|
16
16
|
about what the trigger or content look like, and always renders its own visible close button.
|
|
17
17
|
|
|
18
|
-
**Browser support
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
**Browser support:** since 2026-10-05 open/close and positioning go through the shared
|
|
19
|
+
`useAnchoredPopover` composable, with `side` following the `placement` prop (see
|
|
20
|
+
`composable-anchored-popover.md`). Without anchor positioning (Safari 17–18) the panel is placed
|
|
21
|
+
from the trigger's measured position on the `placement` side, flipping to the opposite side when
|
|
22
|
+
there's no room. Without the Popover API (Safari 16) it opens and closes in JS, with outside-click
|
|
23
|
+
and Escape dismissal.
|
|
21
24
|
|
|
22
25
|
## Props
|
|
23
26
|
|
|
@@ -72,11 +75,11 @@ the only source of space above/below the slotted content — a plain `<p>` doesn
|
|
|
72
75
|
|
|
73
76
|
## Behaviour
|
|
74
77
|
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- On open, focus moves to the close button (`
|
|
79
|
-
|
|
78
|
+
- Clicking the trigger toggles the panel; the close button, Escape or an outside click hides it,
|
|
79
|
+
and focus returns to the trigger. Natively this is the Popover API; on Safari 16 the composable
|
|
80
|
+
does the same in JS. The trigger has `aria-expanded` in every browser.
|
|
81
|
+
- On open, focus moves to the close button (the composable's `onOpen`) so keyboard users land
|
|
82
|
+
inside the panel immediately.
|
|
80
83
|
|
|
81
84
|
## CSS custom properties
|
|
82
85
|
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
`PriceList` (`app/components/02.molecules/price-list/`) renders a service/menu-style price list:
|
|
6
6
|
one or more columns, each with a heading (optional icon) and a `<dl>` of description/price rows.
|
|
7
|
-
Columns stack
|
|
7
|
+
Columns stack in a narrow container and sit two-up once the component itself is wide enough
|
|
8
|
+
(a container-width grid since 2026-10-05, not a viewport media query). Not a SaaS plan comparison, use `PricingCard`
|
|
8
9
|
for that.
|
|
9
10
|
|
|
10
11
|
---
|
|
@@ -48,6 +49,8 @@ No slots, no events.
|
|
|
48
49
|
- Column titles are `HeroText` headings at `heading-tag`. Prices are plain `<span>`s inside `<dd>`,
|
|
49
50
|
not headings (before 2026-09-27 each price was an `<h2>`, which flooded the heading outline).
|
|
50
51
|
- Rows are `<div>`-wrapped `<dt>`/`<dd>` pairs, valid inside `<dl>`.
|
|
52
|
+
- A column with an empty `headingtext` renders no heading at all (no empty `<h2>`); its rows still
|
|
53
|
+
render.
|
|
51
54
|
|
|
52
55
|
---
|
|
53
56
|
|
|
@@ -57,6 +60,12 @@ All tokens are `--price-list-*`; full table in `CONSUMER-STYLING.md` next to the
|
|
|
57
60
|
Common ones: `--price-list-divider-colour`, `--price-list-divider-opacity`,
|
|
58
61
|
`--price-list-price-font-size`, `--price-list-heading-margin-block-end`.
|
|
59
62
|
|
|
63
|
+
Layout and long data: `--price-list-column-min-inline-size` (`32rem`) and `--price-list-max-columns`
|
|
64
|
+
(`2`) control when columns sit side by side; `--price-list-price-max-inline-size` (`50%`) caps the
|
|
65
|
+
price so a long price wraps instead of crushing the description; `--price-list-description-line-clamp`
|
|
66
|
+
(`none`) caps description lines. Unbroken strings wrap anywhere. The `StressTest` story covers the
|
|
67
|
+
worst-case data.
|
|
68
|
+
|
|
60
69
|
---
|
|
61
70
|
|
|
62
71
|
## Usage
|
|
@@ -110,8 +110,33 @@ buggy toggle-based version too.
|
|
|
110
110
|
| `mainNavAriaLabel` | `string` | `"Main navigation"` | aria-label on the primary nav landmark — override for localisation. |
|
|
111
111
|
| `secondaryNavAriaLabel` | `string` | `"Secondary navigation"` | aria-label on the secondary (overflow) nav landmark — override for localisation. |
|
|
112
112
|
| `overflowMenuAriaLabel` | `string` | `"Overflow navigation menu"` | Forwarded to `NavigationItems`' `ariaLabel` — override for localisation. |
|
|
113
|
+
| `overflowButtonLabel` | `string` | `"More navigation"` | Accessible name of the overflow/burger button, which shows only icons. |
|
|
114
|
+
| `submenuAriaLabel` | `string` | `"{title} submenu"` | aria-label on each dropdown summary, top bar and overflow panel; `{title}` is replaced with `childLinksTitle` (or `name`). Forwarded to `NavigationItems`. |
|
|
113
115
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.navigation` element. |
|
|
114
116
|
|
|
117
|
+
## Data edge cases (since 2026-10-05)
|
|
118
|
+
|
|
119
|
+
- A dropdown with no `childLinksTitle` shows its `name` instead (it used to render an empty
|
|
120
|
+
summary labelled "undefined submenu").
|
|
121
|
+
- Child links are keyed by index, so duplicate names are fine.
|
|
122
|
+
- The overflow button appears whenever any item is hidden, whatever the group keys are called.
|
|
123
|
+
It used to read `navListVisibility.firstNav`/`secondNav` only, so a group named anything else
|
|
124
|
+
(e.g. `main`) kept the burger showing even when everything fitted. Empty nav data never shows
|
|
125
|
+
it, and still completes the geometry pass.
|
|
126
|
+
- The top-bar dropdown and the overflow panel are capped in width and height (tokens in
|
|
127
|
+
`CONSUMER-STYLING.md`); long names wrap and long lists scroll inside them.
|
|
128
|
+
- **Fit check (fixed 2026-10-05):** an item is visible when its right edge is at or before the main
|
|
129
|
+
nav's own measured right edge (which already has the secondary nav and gap reserved as
|
|
130
|
+
`margin-inline-end`). It used to compare against the wrapper while adding the gap a second time
|
|
131
|
+
with a strict `<`, so the last item of a flush-right second group (e.g. "Contact") was always
|
|
132
|
+
sent to the overflow menu, however wide the screen.
|
|
133
|
+
- The component copies `responsiveNavLinks` before measuring. It used to write each item's
|
|
134
|
+
`config` straight into the consumer's own nav data object.
|
|
135
|
+
- The `StressTest` story covers all of the above.
|
|
136
|
+
- **Alignment (added 2026-10-05):** `--responsive-header-main-nav-justify-content`
|
|
137
|
+
(default `space-between`) sets how the main nav's groups sit along the bar; use `safe end` /
|
|
138
|
+
`safe center`, never plain `end`/`center`. The `MainNavAlignment` story shows the options.
|
|
139
|
+
|
|
115
140
|
## Slots
|
|
116
141
|
|
|
117
142
|
| Slot | Purpose |
|
|
@@ -171,11 +171,22 @@ Quick reference:
|
|
|
171
171
|
|
|
172
172
|
## Notes
|
|
173
173
|
|
|
174
|
-
- **Popover API + CSS anchor positioning** — same mechanism as `ActionMenu
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
- **Popover API + CSS anchor positioning, with fallbacks** — same mechanism as `ActionMenu`, both
|
|
175
|
+
via the shared `useAnchoredPopover` composable (`app/composables/useAnchoredPopover.ts`). The
|
|
176
|
+
Popover API arrived in Safari 17 and CSS anchor positioning in Safari 26, so:
|
|
177
|
+
- No anchor positioning (Safari 17–18): JS measures the trigger and writes `--_anchor-*` px values for the
|
|
178
|
+
`@supports not (anchor-name: --a)` block, re-measuring on scroll/resize and flipping above
|
|
179
|
+
(`data-placement="top"`) when there's no room below.
|
|
180
|
+
- No Popover API (Safari 16, e.g. first-generation iPad Pros that can't update): the trigger's
|
|
181
|
+
`@click` toggles `isOpen`, `.select-menu-popover-open` shows the list, and document listeners
|
|
182
|
+
close it on outside pointerdown and Escape. Not top-layer here, hence
|
|
183
|
+
`--select-menu-popover-z-index` (default `999999`).
|
|
184
|
+
- Detection runs in `onMounted` (`typeof HTMLElement.prototype.showPopover`, `CSS.supports`), so
|
|
185
|
+
SSR markup is identical everywhere. Every close goes through the composable's `hide()`, never
|
|
186
|
+
`hidePopover()` directly, so it works in both modes.
|
|
187
|
+
- Never put `:popover-open` in a selector list with the fallback class: browsers without it
|
|
188
|
+
drop the whole rule.
|
|
189
|
+
Confirmed broken on a Safari 16 iPad before this (trigger rendered, menu never opened).
|
|
179
190
|
- **Left-aligned popover** (`left: anchor(left)`) — unlike `ActionMenu` which right-aligns. Matches
|
|
180
191
|
native `<select>` dropdown behaviour. Flips above the trigger near the bottom of the viewport.
|
|
181
192
|
- **Keyboard navigation** follows the WAI-ARIA listbox pattern: `ArrowDown`/`ArrowUp` move between
|
|
@@ -198,9 +209,8 @@ Quick reference:
|
|
|
198
209
|
weight doesn't resize the popover. Hover/focus has `-text-color-hover` and `-border-hover`, which
|
|
199
210
|
fall back to the row's *current* state (selected or resting), not always to resting. There's no
|
|
200
211
|
hover font-weight token on purpose: the width reserve only covers one alternate weight.
|
|
201
|
-
- **Chevron rotation
|
|
202
|
-
|
|
203
|
-
on the trigger.
|
|
212
|
+
- **Chevron rotation** keys off the trigger's `aria-expanded` (from the composable's `isOpen`), not
|
|
213
|
+
`:has(...:popover-open)`, so it also works without the Popover API (changed 2026-10-05).
|
|
204
214
|
- **No native `<select>`/`appearance: base-select` involved** — this is a from-scratch popover
|
|
205
215
|
listbox, not a styled native select, so it doesn't inherit `InputSelectCore`'s WebKit
|
|
206
216
|
`appearance: base-select` constraints.
|
|
@@ -27,6 +27,8 @@ Branding (logo/wordmark) is entirely consumer-authored via the `#branding` slot
|
|
|
27
27
|
| `mainNavAriaLabel` | `string` | `"Main navigation"` | Forwarded to `ResponsiveHeader`'s `mainNavAriaLabel` — override for localisation. |
|
|
28
28
|
| `secondaryNavAriaLabel` | `string` | `"Secondary navigation"` | Forwarded to `ResponsiveHeader`'s `secondaryNavAriaLabel` — override for localisation. |
|
|
29
29
|
| `overflowMenuAriaLabel` | `string` | `"Overflow navigation menu"` | Forwarded to `ResponsiveHeader`'s `overflowMenuAriaLabel` — override for localisation. |
|
|
30
|
+
| `overflowButtonLabel` | `string` | `"More navigation"` | Forwarded to `ResponsiveHeader`'s `overflowButtonLabel`, the burger button's accessible name. |
|
|
31
|
+
| `submenuAriaLabel` | `string` | `"{title} submenu"` | Forwarded to `ResponsiveHeader`'s `submenuAriaLabel`; `{title}` is replaced with the item's title. |
|
|
30
32
|
|
|
31
33
|
---
|
|
32
34
|
|
|
@@ -42,6 +42,13 @@ short: `--ui-block-decorated-border-1` .. `-6`, `--ui-block-decorated-shadow-1`
|
|
|
42
42
|
`--ui-block-decorated-inner-shadow-1` .. `-4`, and `--ui-block-decorated-inner-shadow-highlight`
|
|
43
43
|
(layered on top of every inner shadow level).
|
|
44
44
|
|
|
45
|
+
To recolour every level at once (added 2026-10-05): `--ui-block-decorated-border-colour` (default
|
|
46
|
+
`var(--theme-border)`), `--ui-block-decorated-shadow-colour` (default `#000`) and
|
|
47
|
+
`--ui-block-decorated-inner-shadow-colour` (default: the shadow colour). Each shadow level mixes the
|
|
48
|
+
colour with its own opacity via `color-mix()` (8% .. 18%), so defaults render exactly as before.
|
|
49
|
+
The per-level tokens still override the whole value. The story has colour pickers for all three,
|
|
50
|
+
plus story-only background and text colour controls for checking contrast.
|
|
51
|
+
|
|
45
52
|
## Notes
|
|
46
53
|
|
|
47
54
|
- 2026-09-16 migration: previously used options-style `defineProps` and referenced six families of
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# useAnchoredPopover
|
|
2
|
+
|
|
3
|
+
`app/composables/useAnchoredPopover.ts`. Open/close state and positioning for a trigger + `popover`
|
|
4
|
+
element anchored with CSS anchor positioning, with fallbacks for browsers that lack either
|
|
5
|
+
feature. Used by `ActionMenu`, `SelectMenu`, `DeepExpandingMenu` (all `side: "bottom"`, the default)
|
|
6
|
+
`DisplayTooltip` (`side: "right"`) and `PopOver` (`side: () => props.placement`). Use it for any new anchored popover instead of calling `showPopover()`/`hidePopover()`
|
|
7
|
+
directly.
|
|
8
|
+
|
|
9
|
+
Several popovers where only one is open at a time (e.g. `DeepExpandingMenu`'s groups) can share
|
|
10
|
+
one instance: keep an `activeKey`, pass computeds over per-item element maps as `triggerRef`/
|
|
11
|
+
`popoverRef` (the options accept readonly refs), and ignore a `toggle` close event that isn't for
|
|
12
|
+
the active item, since auto popovers' open and close events can arrive in either order.
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
- The Popover API arrived in Safari 17; CSS anchor positioning only in Safari 26.
|
|
17
|
+
- Safari 16 (e.g. first-generation iPad Pros, which can't update past iPadOS 16) has neither: the
|
|
18
|
+
trigger renders but the menu never opens.
|
|
19
|
+
- Safari 17–18 has the Popover API but no anchor positioning: the menu opens in the wrong place.
|
|
20
|
+
|
|
21
|
+
## Modes (detected in `onMounted`, so SSR markup is the same everywhere)
|
|
22
|
+
|
|
23
|
+
| Support | Behaviour |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Popover API + anchor positioning | Fully native. The composable only tracks `isOpen` from `toggle`. |
|
|
26
|
+
| Popover API, no anchor positioning | JS measures the trigger on `beforetoggle`/`toggle` and on scroll/resize, and writes the trigger's edges as `--_anchor-*` px values (see below); flips to the opposite side (`data-placement`) when the preferred side has no room and the opposite side does. |
|
|
27
|
+
| No Popover API | Also opens/closes in JS: trigger `@click` toggles `isOpen`, document `pointerdown` outside the root and `Escape` close it, and `hide()` returns focus to the trigger when focus was inside the popover, as native popovers do. The component shows the popover from a fallback class. |
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const {
|
|
33
|
+
isOpen, // Ref<boolean>, accurate in every mode; bind to aria-expanded
|
|
34
|
+
usesFallbackPopover, // Ref<boolean>, true without the Popover API
|
|
35
|
+
needsPositioning, // Ref<boolean>, true without anchor positioning
|
|
36
|
+
positionStyle, // bind to the popover's :style (undefined when not needed)
|
|
37
|
+
popoverPlacement, // bind to :data-placement: the resolved side, or undefined when native
|
|
38
|
+
show, hide, // always use these, never showPopover()/hidePopover() directly
|
|
39
|
+
handleTriggerClick, // trigger @click (no-op where popovertarget works)
|
|
40
|
+
handleBeforeToggle, // popover @beforetoggle
|
|
41
|
+
handleToggle, // popover @toggle
|
|
42
|
+
} = useAnchoredPopover({ rootRef, triggerRef, popoverRef, side: "bottom", onOpen: focusFirstItem });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`rootRef` must contain both the trigger and the popover (outside-click detection). `onOpen` runs
|
|
46
|
+
after the popover is visible, so it can move focus into it.
|
|
47
|
+
|
|
48
|
+
`side` (`"bottom"` default, `"top"`, `"right"`, `"left"`, or a ref/getter of one, e.g. `() => props.placement`) is the preferred side for the fallback
|
|
49
|
+
flip; it doesn't affect native anchor positioning, which the component's own CSS controls.
|
|
50
|
+
|
|
51
|
+
### Fallback position variables
|
|
52
|
+
|
|
53
|
+
They mirror `anchor()`: `--_anchor-{top|bottom|left|right}` is the trigger edge in viewport px, for
|
|
54
|
+
the `top`/`left` properties (`top: anchor(bottom)` becomes `top: var(--_anchor-bottom)`).
|
|
55
|
+
`--_anchor-{edge}-inverse` is the same edge measured from the viewport's bottom/right, for the
|
|
56
|
+
`bottom`/`right` properties (`right: anchor(left)` becomes `right: var(--_anchor-left-inverse)`).
|
|
57
|
+
All eight are always written, so CSS picks what each side needs.
|
|
58
|
+
|
|
59
|
+
## Component wiring
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<div ref="rootRef" class="x-menu">
|
|
63
|
+
<button ref="triggerRef" :popovertarget="id" :aria-expanded="isOpen" @click="handleTriggerClick">…</button>
|
|
64
|
+
<div
|
|
65
|
+
:id="id"
|
|
66
|
+
ref="popoverRef"
|
|
67
|
+
popover
|
|
68
|
+
class="x-menu-popover"
|
|
69
|
+
:class="{ 'x-menu-popover-open': usesFallbackPopover && isOpen }"
|
|
70
|
+
:style="positionStyle"
|
|
71
|
+
:data-placement="popoverPlacement"
|
|
72
|
+
@beforetoggle="handleBeforeToggle"
|
|
73
|
+
@toggle="handleToggle"
|
|
74
|
+
>…</div>
|
|
75
|
+
</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
.x-menu-popover {
|
|
80
|
+
&:popover-open { display: block; }
|
|
81
|
+
/* Separate rule: a selector list containing :popover-open is dropped whole where unsupported. */
|
|
82
|
+
&.x-menu-popover-open { display: block; }
|
|
83
|
+
|
|
84
|
+
@supports not (anchor-name: --a) {
|
|
85
|
+
position: fixed;
|
|
86
|
+
top: calc(var(--_anchor-bottom, 0px) + var(--x-menu-block-distance, 0.4rem));
|
|
87
|
+
left: var(--_anchor-left, 0px);
|
|
88
|
+
z-index: var(--x-menu-popover-z-index, 999999);
|
|
89
|
+
|
|
90
|
+
&[data-placement="top"] {
|
|
91
|
+
top: auto;
|
|
92
|
+
bottom: calc(var(--_anchor-top-inverse, 0px) + var(--x-menu-block-distance, 0.4rem));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Drive any "open" visuals (e.g. a chevron) from `[aria-expanded="true"]` on the trigger, not
|
|
99
|
+
`:has(:popover-open)`.
|
|
100
|
+
|
|
101
|
+
## Caveats
|
|
102
|
+
|
|
103
|
+
- In the no-Popover-API mode the popover isn't in the top layer: give it a public z-index token
|
|
104
|
+
(default `999999`, pitfall #17), and an ancestor with `transform`/`filter`/`contain` becomes its
|
|
105
|
+
containing block and can misplace it.
|
|
106
|
+
- Tests: happy-dom has no Popover API and its `CSS.supports` can't be spied on. Define/undefine
|
|
107
|
+
`HTMLElement.prototype.showPopover` and swap `globalThis.CSS` (restore it in `afterEach`); see
|
|
108
|
+
`app/composables/tests/useAnchoredPopover.spec.ts`.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## Prerequisites
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
- Built around the native Popover API (Chrome 114+, Firefox 125+, Safari 17+). With `DisplayTooltip`/`DisplayTooltipDefined` it also works on Safari 16, because those components fall back to JS open/close (`useAnchoredPopover`) and the guide drives them through their trigger buttons. Hand-written raw `[popover]` markup like the example below still needs the Popover API.
|
|
12
12
|
- Popovers must have `id` attributes and corresponding trigger buttons with `popovertarget` and `popovertargetaction="toggle"` attributes
|
|
13
13
|
- Close buttons inside each popover must have `popovertargetaction="hide"`
|
|
14
14
|
|
|
@@ -74,6 +74,8 @@ await startGuide()
|
|
|
74
74
|
2. If `autoStart` is `true`, calls `startGuide()` which iterates the popovers in DOM order.
|
|
75
75
|
3. For each popover: finds the `[popovertarget][popovertargetaction="toggle"]` trigger button and clicks it to open the popover, then waits for the `[popovertargetaction="hide"]` button to be clicked before advancing.
|
|
76
76
|
4. After the last popover is dismissed, `autoRunGuide` is set to `false` and `isGuideRunning` becomes `false`.
|
|
77
|
+
5. With no close button, a step ends when the trigger's `aria-expanded` turns `"false"` (a `MutationObserver`), or on the popover's `toggle` event with `newState: "closed"` when there's no trigger either.
|
|
78
|
+
6. `stopGuide()`/`restartGuide()` close an open step by clicking its trigger when `aria-expanded="true"`, or with `hidePopover()` where `:popover-open` is supported. They never call `togglePopover()` or match `:popover-open` unguarded, both of which throw on Safari 16.
|
|
77
79
|
|
|
78
80
|
## API reference
|
|
79
81
|
|
package/.claude/skills/index.md
CHANGED
|
@@ -66,6 +66,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
66
66
|
├── composable-tooltips-guide.md — useTooltipsGuide: sequential popover guide with auto-start, dismiss-to-advance, manual controls
|
|
67
67
|
├── composable-cookie-consent.md — useCookieConsent: unset/granted/denied state, cookie persistence, wraps @nuxt/scripts' useScriptTriggerConsent
|
|
68
68
|
├── composable-analytics.md — useAnalytics: provider-agnostic trackEvent/page-view tracking (google-analytics only implemented), consent-gated, single call site for setup + firing events
|
|
69
|
+
├── composable-anchored-popover.md — useAnchoredPopover: popover + anchor positioning with JS fallbacks for Safari 16 (no Popover API) and 17–18 (no anchor positioning); used by ActionMenu/SelectMenu/DeepExpandingMenu/DisplayTooltip/PopOver
|
|
69
70
|
└── components/
|
|
70
71
|
├── alert-content.md — AlertContent + AlertContentInner: themed alert panel (icon/title/content/actions/dismiss) under DisplayToast, DisplayPrompt and AlertMaskedContent; showIcon, #actions row, --alert-content-* tokens, app.config icon map
|
|
71
72
|
├── alert-mask-core.md — AlertMaskCore: SVG border/background mask sized to slotted content via ResizeObserver, config-prop-driven geometry/colour (no CSS token API)
|
|
@@ -271,6 +271,20 @@ Key points:
|
|
|
271
271
|
- Extra args must be stripped before `v-bind` — spreading unknown keys onto a component makes them unknown HTML attributes.
|
|
272
272
|
- CSS custom properties set via `:style` on the component root are picked up by `var()` in the component's scoped CSS.
|
|
273
273
|
|
|
274
|
+
### Label token controls as story-only
|
|
275
|
+
|
|
276
|
+
A control that drives a CSS token (a line clamp, a colour) looks exactly like a prop in the
|
|
277
|
+
Controls panel and the generated docs table, so a developer reading the story can assume it's a
|
|
278
|
+
prop and try to pass it. Make it unmistakable:
|
|
279
|
+
|
|
280
|
+
- Put it in the category `"CSS tokens (story only, set in your CSS)"`.
|
|
281
|
+
- Start the description with `**Story control, not a prop.**`, name the token, and show the CSS a
|
|
282
|
+
consumer would write instead, e.g.
|
|
283
|
+
`` `.salon-menu { --price-list-description-line-clamp: 2; }` ``.
|
|
284
|
+
- If a story's docs text tells people to try the control, say there too that it's a CSS token.
|
|
285
|
+
|
|
286
|
+
`PriceList.stories.ts` and `GoogleReviews.stories.ts` (line-clamp controls) follow this.
|
|
287
|
+
|
|
274
288
|
## Notes
|
|
275
289
|
|
|
276
290
|
- **Colour scheme:** `.storybook/preview.ts` pins every story to the light scheme
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
" main-nav-aria-label=\"${2:Main navigation}\"",
|
|
25
25
|
" secondary-nav-aria-label=\"${3:Secondary navigation}\"",
|
|
26
26
|
" overflow-menu-aria-label=\"${4:Overflow navigation menu}\"",
|
|
27
|
-
"
|
|
27
|
+
" overflow-button-label=\"${5:More navigation}\"",
|
|
28
|
+
" submenu-aria-label=\"${6:{title\\} submenu}\"",
|
|
29
|
+
"></ResponsiveHeader>"
|
|
28
30
|
]
|
|
29
31
|
},
|
|
30
32
|
"SRCDEV ResponsiveHeader Nav Data (with dropdown)": {
|
|
@@ -40,9 +40,11 @@
|
|
|
40
40
|
" main-nav-aria-label=\"${3:Main navigation}\"",
|
|
41
41
|
" secondary-nav-aria-label=\"${4:Secondary navigation}\"",
|
|
42
42
|
" overflow-menu-aria-label=\"${5:Overflow navigation menu}\"",
|
|
43
|
+
" overflow-button-label=\"${6:More navigation}\"",
|
|
44
|
+
" submenu-aria-label=\"${7:{title\\} submenu}\"",
|
|
43
45
|
">",
|
|
44
46
|
" <template #branding>",
|
|
45
|
-
" <NuxtLink to=\"/\" class=\"home-link\">$
|
|
47
|
+
" <NuxtLink to=\"/\" class=\"home-link\">$8Brand</NuxtLink>",
|
|
46
48
|
" </template>",
|
|
47
49
|
"</SiteHeader>"
|
|
48
50
|
]
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
/* Used to scroll scale elements when scrolling x-axis like a carousel. */
|
|
15
15
|
|
|
16
16
|
@media (prefers-reduced-motion: no-preference) {
|
|
17
|
-
|
|
18
|
-
animation
|
|
19
|
-
|
|
17
|
+
@supports (animation-timeline: view()) {
|
|
18
|
+
.animation-scroller-x {
|
|
19
|
+
animation: scroller linear both;
|
|
20
|
+
animation-timeline: view(x);
|
|
21
|
+
}
|
|
20
22
|
}
|
|
21
23
|
}
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
@media (prefers-reduced-motion: no-preference) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
@supports (animation-timeline: view()) {
|
|
12
|
+
.auto-rotate {
|
|
13
|
+
animation: autoRotateAnimation;
|
|
14
|
+
animation-timeline: view();
|
|
15
|
+
}
|
|
14
16
|
}
|
|
15
17
|
}
|