srcdev-nuxt-components 9.1.50 → 9.1.52

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 (34) hide show
  1. package/.claude/settings.json +8 -27
  2. package/.claude/settings.local.json +24 -52
  3. package/.claude/skills/components/action-menu.md +141 -0
  4. package/.claude/skills/components/stepper-list.md +52 -18
  5. package/.claude/skills/css-nesting-conventions.md +121 -0
  6. package/.claude/skills/index.md +3 -0
  7. package/.claude/skills/pull-request-description.md +48 -0
  8. package/.claude/skills/testing-add-unit-test.md +40 -2
  9. package/README.md +26 -5
  10. package/app/components/02.molecules/action-menu/ActionMenu.vue +218 -0
  11. package/app/components/02.molecules/action-menu/ActionMenuItemCore.vue +123 -0
  12. package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +125 -0
  13. package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +326 -0
  14. package/app/components/02.molecules/action-menu/tests/ActionMenu.spec.ts +458 -0
  15. package/app/components/02.molecules/action-menu/tests/ActionMenuItemCore.spec.ts +199 -0
  16. package/app/components/02.molecules/action-menu/tests/__snapshots__/ActionMenu.spec.ts.snap +28 -0
  17. package/app/components/02.molecules/action-menu/tests/__snapshots__/ActionMenuItemCore.spec.ts.snap +27 -0
  18. package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +5 -1
  19. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +2 -1
  20. package/app/components/02.molecules/stepper-list/CONSUMER-STYLING.md +138 -0
  21. package/app/components/02.molecules/stepper-list/StepperList.vue +50 -24
  22. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +5 -1
  23. package/app/components/05.forms/input-button/InputButtonCore.vue +1 -1
  24. package/app/components/carousels/tests/CarouselFlip.spec.ts +1 -0
  25. package/app/components/responsive-header/NavigationItems.vue +302 -81
  26. package/app/components/responsive-header/ResponsiveHeader.vue +360 -56
  27. package/app/layouts/default.vue +43 -352
  28. package/app/layouts/site-navigation-demo.vue +36 -34
  29. package/app/pages/page-hero-highlights.vue +3 -3
  30. package/app/pages/ui/scroll-reveal-image.vue +3 -3
  31. package/app/pages/ui/simple-grid.vue +9 -20
  32. package/modules/colour-scheme.ts +1 -1
  33. package/nuxt.config.ts +11 -2
  34. package/package.json +17 -16
package/README.md CHANGED
@@ -104,15 +104,36 @@ When disabled, no `data-color-scheme` attribute is set on `<html>` and `useColou
104
104
 
105
105
  ---
106
106
 
107
- ## Known Dev Server Warnings
107
+ ## Known Build / Production Issues
108
108
 
109
- ### `[request error] [GET] http://localhost:3000/_nuxt/` (404)
109
+ ### `ERR_MODULE_NOT_FOUND: vue/index.mjs` 500 on every request (Node 22)
110
110
 
111
- This error appears in the terminal when running `npm run dev` and is **harmless** it does not affect dev server operation.
111
+ **Symptom**: After `npm run build`, running `node .output/server/index.mjs` returns HTTP 500 on every page. The terminal shows:
112
112
 
113
- **Cause**: The Vue/Nuxt browser DevTools extension probes `/_nuxt/` to detect if the page is a Nuxt app. Since `/_nuxt/` is a directory (not a file), the server returns 404 and logs it.
113
+ ```text
114
+ Error [ERR_MODULE_NOT_FOUND]: Cannot find module
115
+ '.output/server/node_modules/vue/index.mjs'
116
+ Did you mean to import
117
+ '.output/server/node_modules/.nitro/vue@3.5.34/dist/vue.cjs.prod.js'?
118
+ ```
119
+
120
+ **Root cause**: Nitro's dependency tracer externalises `vue` and copies it to `.output/server/node_modules/vue/`, but only traces the CJS build (`dist/vue.cjs.prod.js`). Vue's `package.json` exports map resolves the `"import"` + `"node"` condition (used by Node 22 when importing from an ES module) to `./index.mjs` — a file that was never copied. This is a Nitro tracing bug exposed by Node 22's stricter ESM condition matching, made more likely by `vue.runtimeCompiler: true` (which changes how Nuxt aliases Vue at build time, causing Nitro to fall back to externalising it as a node_modules package).
121
+
122
+ **Fix** (already applied in this repo's `nuxt.config.ts`):
123
+
124
+ ```ts
125
+ nitro: {
126
+ externals: {
127
+ inline: ["vue", "@vue/runtime-core", "@vue/runtime-dom", "@vue/reactivity", "@vue/shared", "@vue/server-renderer"],
128
+ },
129
+ },
130
+ ```
131
+
132
+ This tells Nitro to bundle these packages inline rather than externalise them, which sidesteps the runtime resolution entirely.
133
+
134
+ **Consumer apps**: This config is not gated behind `isStandalone`, so it is inherited automatically by any app that extends this layer. No consumer-side action is required.
114
135
 
115
- **Resolution**: It cannot be suppressed without disabling the browser extension. Since the DevTools extension is useful for inspecting Pinia stores and component state, the recommended approach is to ignore this warning.
136
+ **When it's safe to remove**: If a future Nitro release fixes the dependency tracer so that all export-condition files are copied correctly, the `inline` list can be removed. Verify by checking that `.output/server/node_modules/vue/index.mjs` exists after a clean build without the config.
116
137
 
117
138
  ---
118
139
 
@@ -0,0 +1,218 @@
1
+ <template>
2
+ <div class="action-menu" :class="[elementClasses]" :style="`--_anchor-name: ${anchorName}`">
3
+ <button
4
+ ref="triggerRef"
5
+ :popovertarget="menuId"
6
+ popovertargetaction="toggle"
7
+ type="button"
8
+ class="action-menu-trigger"
9
+ :aria-label="label"
10
+ aria-haspopup="menu"
11
+ >
12
+ <Icon name="lucide:ellipsis" class="action-menu-trigger-icon" aria-hidden="true" />
13
+ </button>
14
+
15
+ <div
16
+ :id="menuId"
17
+ ref="popoverRef"
18
+ popover
19
+ class="action-menu-popover"
20
+ @toggle="handleToggle"
21
+ @keydown="handleKeydown"
22
+ >
23
+ <ul class="action-menu-list" role="menu" :aria-label="label">
24
+ <li v-for="n in itemCount" :key="n - 1" class="action-menu-list-item" role="none" @click="closeMenu">
25
+ <slot :name="`item-${n - 1}`"></slot>
26
+ </li>
27
+ </ul>
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ interface Props {
34
+ label?: string;
35
+ styleClassPassthrough?: string | string[];
36
+ }
37
+
38
+ const props = withDefaults(defineProps<Props>(), {
39
+ label: "Open actions menu",
40
+ styleClassPassthrough: () => [],
41
+ });
42
+
43
+ const slots = useSlots();
44
+ const itemCount = computed(() => Object.keys(slots).filter((name) => /^item-\d+$/.test(name)).length);
45
+
46
+ const id = useId();
47
+ const menuId = `action-menu-${id}`;
48
+ const anchorName = `--action-menu-anchor-${id}`;
49
+
50
+ const triggerRef = ref<HTMLButtonElement | null>(null);
51
+ const popoverRef = ref<HTMLDivElement | null>(null);
52
+
53
+ /** Returns all focusable menuitems in DOM order. */
54
+ const getMenuItems = (): HTMLElement[] =>
55
+ Array.from(popoverRef.value?.querySelectorAll<HTMLElement>('[role="menuitem"]') ?? []);
56
+
57
+ /**
58
+ * Close the menu and return focus to the trigger.
59
+ * Called on item click — Escape is handled natively by the Popover API
60
+ * (which also restores focus to the trigger automatically).
61
+ */
62
+ const closeMenu = () => {
63
+ popoverRef.value?.hidePopover();
64
+ triggerRef.value?.focus();
65
+ };
66
+
67
+ const handleToggle = (event: Event) => {
68
+ const toggleEvent = event as ToggleEvent;
69
+ if (toggleEvent.newState === "open") {
70
+ getMenuItems()[0]?.focus();
71
+ }
72
+ };
73
+
74
+ /**
75
+ * Keyboard navigation following the WAI-ARIA menu pattern.
76
+ *
77
+ * ArrowDown / ArrowUp — move between items (wraps around).
78
+ * Home / End — jump to first / last item.
79
+ * Tab — close the menu; let the browser Tab naturally
80
+ * (do NOT focus the trigger — Tab should advance
81
+ * to the next element in the page).
82
+ * Escape — handled natively by the Popover API.
83
+ */
84
+ const handleKeydown = (event: KeyboardEvent) => {
85
+ const items = getMenuItems();
86
+ if (!items.length) return;
87
+
88
+ const currentIndex = items.indexOf(document.activeElement as HTMLElement);
89
+
90
+ switch (event.key) {
91
+ case "ArrowDown":
92
+ event.preventDefault();
93
+ items[currentIndex === -1 ? 0 : (currentIndex + 1) % items.length]?.focus();
94
+ break;
95
+ case "ArrowUp":
96
+ event.preventDefault();
97
+ items[currentIndex === -1 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length]?.focus();
98
+ break;
99
+ case "Home":
100
+ event.preventDefault();
101
+ items[0]?.focus();
102
+ break;
103
+ case "End":
104
+ event.preventDefault();
105
+ items[items.length - 1]?.focus();
106
+ break;
107
+ case "Tab":
108
+ // Close without stealing focus — Tab exits to the next DOM element naturally.
109
+ popoverRef.value?.hidePopover();
110
+ break;
111
+ }
112
+ };
113
+
114
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
115
+ </script>
116
+
117
+ <style lang="css">
118
+ @layer components {
119
+ .action-menu {
120
+ --_block-distance: var(--action-menu-block-distance, 0.4rem);
121
+ --_trigger-size: var(--action-menu-trigger-size, 3.2rem);
122
+ --_trigger-border-radius: var(--action-menu-trigger-border-radius, var(--button-border-radius-icon-only, 50%));
123
+ --_trigger-surface: var(--action-menu-trigger-surface, transparent);
124
+ --_trigger-surface-hover: var(--action-menu-trigger-surface-hover, var(--slate-01));
125
+ --_trigger-icon-size: var(--action-menu-trigger-icon-size, 2rem);
126
+ --_trigger-icon-color: var(--action-menu-trigger-icon-color, var(--slate-07));
127
+
128
+ --_popover-background: var(--action-menu-popover-background, var(--slate-00));
129
+ --_popover-border: var(--action-menu-popover-border, 0.1rem solid var(--slate-03));
130
+ --_popover-border-radius: var(--action-menu-popover-border-radius, 0.8rem);
131
+ --_popover-min-width: var(--action-menu-popover-min-width, 20rem);
132
+ --_popover-shadow: var(--action-menu-popover-shadow, 0 0.4rem 1.6rem rgba(0, 0, 0, 0.1));
133
+ --_popover-transition-duration: var(--action-menu-popover-transition-duration, 200ms);
134
+ --_item-divider: var(--action-menu-item-divider, 0.1rem solid var(--slate-02));
135
+
136
+ position: relative;
137
+ display: inline-block;
138
+
139
+ .action-menu-trigger {
140
+ all: unset;
141
+ cursor: pointer;
142
+ display: grid;
143
+ place-items: center;
144
+ width: var(--_trigger-size);
145
+ height: var(--_trigger-size);
146
+ border-radius: var(--_trigger-border-radius);
147
+ background-color: var(--_trigger-surface);
148
+ color: var(--_trigger-icon-color);
149
+ anchor-name: var(--_anchor-name);
150
+ transition: background-color var(--control-transition-duration, 200ms) var(--control-transition-ease, ease);
151
+
152
+ &:hover,
153
+ &:focus-visible {
154
+ background-color: var(--_trigger-surface-hover);
155
+ }
156
+
157
+ &:focus-visible {
158
+ outline: var(--button-outline-width, 0.2rem) solid var(--theme-button-primary-outline, currentcolor);
159
+ outline-offset: 0.2rem;
160
+ }
161
+
162
+ .action-menu-trigger-icon {
163
+ display: block;
164
+ width: var(--_trigger-icon-size);
165
+ height: var(--_trigger-icon-size);
166
+ }
167
+ }
168
+
169
+ .action-menu-popover {
170
+ border: var(--_popover-border);
171
+ margin: 0;
172
+ padding: 0;
173
+ inset: auto;
174
+ background-color: var(--_popover-background);
175
+ border-radius: var(--_popover-border-radius);
176
+ min-width: var(--_popover-min-width);
177
+ box-shadow: var(--_popover-shadow);
178
+ overflow: hidden;
179
+
180
+ position-anchor: var(--_anchor-name);
181
+ top: calc(anchor(bottom) + var(--_block-distance));
182
+ right: anchor(right);
183
+ left: auto;
184
+ position-try-fallbacks: flip-block;
185
+
186
+ opacity: 0;
187
+ display: none;
188
+ transition:
189
+ opacity var(--_popover-transition-duration),
190
+ display var(--_popover-transition-duration),
191
+ overlay var(--_popover-transition-duration);
192
+ transition-behavior: allow-discrete;
193
+
194
+ &:popover-open {
195
+ display: block;
196
+ opacity: 1;
197
+
198
+ @starting-style {
199
+ display: block;
200
+ opacity: 0;
201
+ }
202
+ }
203
+
204
+ .action-menu-list {
205
+ list-style: none;
206
+ padding: 0;
207
+ margin: 0;
208
+
209
+ .action-menu-list-item {
210
+ &:not(:last-child) {
211
+ border-bottom: var(--_item-divider);
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+ </style>
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ :type="!isLink ? 'button' : undefined"
5
+ :href="isLink ? href : undefined"
6
+ class="action-menu-item-core"
7
+ :class="[elementClasses]"
8
+ role="menuitem"
9
+ @click="emit('click', $event)"
10
+ >
11
+ <span v-if="slots.icon" class="action-menu-item-icon" aria-hidden="true">
12
+ <slot name="icon"></slot>
13
+ </span>
14
+ <span class="action-menu-item-label">{{ label }}</span>
15
+ <span class="action-menu-item-arrow" aria-hidden="true">
16
+ <Icon name="lucide:arrow-right" class="action-menu-item-arrow-icon" />
17
+ </span>
18
+ </component>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ interface Props {
23
+ label: string;
24
+ href?: string;
25
+ styleClassPassthrough?: string | string[];
26
+ }
27
+
28
+ const props = withDefaults(defineProps<Props>(), {
29
+ href: undefined,
30
+ styleClassPassthrough: () => [],
31
+ });
32
+
33
+ const emit = defineEmits<{
34
+ click: [event: MouseEvent];
35
+ }>();
36
+
37
+ const NuxtLink = resolveComponent("NuxtLink");
38
+ const isLink = computed(() => Boolean(props.href));
39
+ const isInternalLink = computed(() => isLink.value && props.href?.startsWith("/"));
40
+ const tag = computed(() => {
41
+ if (isInternalLink.value) return NuxtLink;
42
+ if (isLink.value) return "a";
43
+ return "button";
44
+ });
45
+
46
+ const slots = useSlots();
47
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
48
+
49
+ watch(
50
+ () => props.styleClassPassthrough,
51
+ () => {
52
+ resetElementClasses(props.styleClassPassthrough);
53
+ }
54
+ );
55
+ </script>
56
+
57
+ <style lang="css">
58
+ @layer components {
59
+ .action-menu-item-core {
60
+ --_surface-hover: var(--action-menu-item-surface-hover, light-dark(var(--slate-01), var(--slate-09)));
61
+ --_text-color: var(--action-menu-item-text-color, light-dark(var(--slate-09), var(--slate-01)));
62
+ --_icon-size: var(--action-menu-item-icon-size, 2rem);
63
+ --_padding-inline: var(--action-menu-item-padding-inline, 1.6rem);
64
+ --_padding-block: var(--action-menu-item-padding-block, 1.2rem);
65
+
66
+ all: unset;
67
+ box-sizing: border-box;
68
+ cursor: pointer;
69
+ display: grid;
70
+ grid-template-columns: auto 1fr auto;
71
+ align-items: center;
72
+ gap: 1.2rem;
73
+ width: 100%;
74
+ padding-inline: var(--_padding-inline);
75
+ padding-block: var(--_padding-block);
76
+ color: var(--_text-color);
77
+ font-family: var(--font-family);
78
+ font-size: var(--button-font-size, 1.4rem);
79
+ font-weight: var(--button-font-weight, 500);
80
+ line-height: var(--button-line-height, 1.2);
81
+ transition: background-color var(--control-transition-duration, 200ms) var(--control-transition-ease, ease);
82
+ text-decoration: none;
83
+
84
+ &:hover,
85
+ &:focus-visible {
86
+ background-color: var(--_surface-hover);
87
+ }
88
+
89
+ &:focus-visible {
90
+ outline: var(--button-outline-width, 0.2rem) solid var(--theme-button-primary-outline, currentcolor);
91
+ outline-offset: -0.2rem;
92
+ }
93
+
94
+ .action-menu-item-icon {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ width: var(--_icon-size);
99
+ height: var(--_icon-size);
100
+ flex-shrink: 0;
101
+ }
102
+
103
+ .action-menu-item-label {
104
+ white-space: nowrap;
105
+ overflow: hidden;
106
+ text-overflow: ellipsis;
107
+ }
108
+
109
+ .action-menu-item-arrow {
110
+ display: flex;
111
+ align-items: center;
112
+ flex-shrink: 0;
113
+ opacity: 0.5;
114
+
115
+ .action-menu-item-arrow-icon {
116
+ display: block;
117
+ width: 1.6rem;
118
+ height: 1.6rem;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ </style>
@@ -0,0 +1,125 @@
1
+ # ActionMenu — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ All `--action-menu-*` tokens are the stable override surface. Because action menus appear
6
+ repeatedly across the UI (tables, cards, list rows) the recommended approach is to set tokens
7
+ once in a **global CSS file** rather than per-instance via `styleClassPassthrough`.
8
+
9
+ ### Trigger button
10
+
11
+ | Token | Default | Controls |
12
+ |---|---|---|
13
+ | `--action-menu-trigger-size` | `3.2rem` | Trigger button width and height |
14
+ | `--action-menu-trigger-border-radius` | `var(--button-border-radius-icon-only, 50%)` | Trigger corner rounding |
15
+ | `--action-menu-trigger-surface` | `transparent` | Trigger background (rest state) |
16
+ | `--action-menu-trigger-surface-hover` | `light-dark(var(--slate-01), var(--slate-09))` | Trigger background on hover/focus |
17
+ | `--action-menu-trigger-icon-size` | `2rem` | Ellipsis icon size |
18
+ | `--action-menu-trigger-icon-color` | `light-dark(var(--slate-07), var(--slate-03))` | Ellipsis icon colour |
19
+
20
+ ### Menu popover
21
+
22
+ | Token | Default | Controls |
23
+ |---|---|---|
24
+ | `--action-menu-block-distance` | `0.4rem` | Gap between trigger bottom and menu top |
25
+ | `--action-menu-popover-background` | `light-dark(var(--slate-00), var(--slate-10))` | Menu panel background |
26
+ | `--action-menu-popover-border` | `0.1rem solid light-dark(var(--slate-03), var(--slate-07))` | Menu panel border shorthand |
27
+ | `--action-menu-popover-border-radius` | `0.8rem` | Menu panel corner rounding |
28
+ | `--action-menu-popover-min-width` | `20rem` | Minimum menu width |
29
+ | `--action-menu-popover-shadow` | `0 0.4rem 1.6rem light-dark(rgba(0,0,0,0.1), rgba(0,0,0,0.4))` | Menu panel drop shadow |
30
+ | `--action-menu-popover-transition-duration` | `200ms` | Open/close fade duration |
31
+
32
+ ### Menu items (`ActionMenuItemCore`)
33
+
34
+ | Token | Default | Controls |
35
+ |---|---|---|
36
+ | `--action-menu-item-divider` | `0.1rem solid light-dark(var(--slate-02), var(--slate-08))` | Divider line between items |
37
+ | `--action-menu-item-surface-hover` | `light-dark(var(--slate-01), var(--slate-09))` | Item row background on hover/focus |
38
+ | `--action-menu-item-text-color` | `light-dark(var(--slate-09), var(--slate-01))` | Item label and icon colour |
39
+ | `--action-menu-item-icon-size` | `2rem` | Left icon container size |
40
+ | `--action-menu-item-padding-inline` | `1.6rem` | Item horizontal padding |
41
+ | `--action-menu-item-padding-block` | `1.2rem` | Item vertical padding |
42
+
43
+ ---
44
+
45
+ ## Global theming — recommended approach
46
+
47
+ Create `assets/styles/setup/07.components/action-menu.css` in the consuming app and set tokens
48
+ on `:root`. This applies to every `ActionMenu` across the site.
49
+
50
+ ```css
51
+ /* assets/styles/setup/07.components/action-menu.css */
52
+ :root {
53
+ --action-menu-trigger-border-radius: 0.4rem;
54
+ --action-menu-trigger-surface-hover: var(--brand-surface-subtle);
55
+ --action-menu-trigger-icon-color: var(--brand-text-muted);
56
+
57
+ --action-menu-popover-background: var(--brand-surface);
58
+ --action-menu-popover-border: 0.1rem solid var(--brand-border);
59
+ --action-menu-popover-border-radius: 0.6rem;
60
+ --action-menu-popover-shadow: 0 0.8rem 2.4rem rgba(0, 0, 0, 0.15);
61
+
62
+ --action-menu-item-surface-hover: var(--brand-surface-subtle);
63
+ --action-menu-item-text-color: var(--brand-text);
64
+ --action-menu-item-divider: 0.1rem solid var(--brand-border);
65
+ --action-menu-block-distance: 0.6rem;
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Page-scoped overrides
72
+
73
+ Override tokens for a specific section by scoping them under the page or layout wrapper.
74
+ No `:deep()` is required (component styles are unscoped).
75
+
76
+ ```css
77
+ /* In the consuming page's unscoped <style> block */
78
+ .admin-table {
79
+ .action-menu {
80
+ --action-menu-trigger-size: 2.8rem;
81
+ --action-menu-trigger-icon-size: 1.6rem;
82
+ --action-menu-popover-min-width: 16rem;
83
+ --action-menu-item-padding-block: 0.8rem;
84
+ }
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Per-instance overrides via styleClassPassthrough
91
+
92
+ Use sparingly — prefer global or page-scoped CSS. When a single instance needs a distinct
93
+ visual style, pass a modifier class:
94
+
95
+ ```vue
96
+ <ActionMenu
97
+ :style-class-passthrough="['danger-actions']"
98
+ >
99
+ ...
100
+ </ActionMenu>
101
+ ```
102
+
103
+ ```css
104
+ .action-menu.danger-actions {
105
+ --action-menu-trigger-icon-color: var(--color-danger);
106
+ --action-menu-trigger-surface-hover: light-dark(var(--red-01), var(--red-09));
107
+ --action-menu-item-surface-hover: light-dark(var(--red-01), var(--red-09));
108
+ --action-menu-item-text-color: light-dark(var(--red-09), var(--red-01));
109
+ --action-menu-item-divider: 0.1rem solid light-dark(var(--red-02), var(--red-08));
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Notes
116
+
117
+ - `--action-menu-block-distance` accepts any valid `<length>`. Negative values will cause the
118
+ menu to overlap the trigger.
119
+ - The menu opens **below** the trigger and right-aligns with it by default. It flips above
120
+ when near the bottom of the viewport (`position-try-fallbacks: flip-block`).
121
+ - `--action-menu-popover-min-width` sets the floor — long labels will naturally expand the
122
+ menu wider. Set `width: max-content` on `.action-menu-popover` in a consumer override if
123
+ you want to suppress that.
124
+ - The `--action-menu-item-*` tokens resolve on `.action-menu-item-core` elements, so they
125
+ take effect even when items are used in other contexts.