srcdev-nuxt-components 9.6.6 → 9.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/consumer/ids-from-useid.md +14 -0
- package/.claude/skills/component-compliance-checklist.md +6 -0
- package/.claude/skills/component-naming.md +2 -2
- package/.claude/skills/components/input-checkbox-radio.md +19 -3
- package/.claude/skills/components/input-checkbox.md +14 -3
- package/.claude/skills/components/input-description.md +1 -1
- package/.claude/skills/components/input-error.md +1 -1
- package/.claude/skills/components/input-label.md +2 -2
- package/.claude/skills/components/input-number.md +44 -13
- package/.claude/skills/components/input-radio.md +15 -1
- package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
- package/.claude/skills/components/input-textarea-core.md +4 -4
- package/.claude/skills/components/page-hero-highlights.md +22 -18
- package/.claude/skills/index.md +6 -6
- package/.claude/skills/testing-add-unit-test.md +17 -0
- package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
- package/.vscode/srcdev-component-input-range.code-snippets +38 -18
- package/.vscode/srcdev-component-page-hero-highlights.code-snippets +2 -1
- package/app/components/04.templates/page-hero-highlights/CONSUMER-STYLING.md +32 -12
- package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +56 -47
- package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +158 -307
- package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlightsHeader.stories.ts +10 -0
- package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +49 -27
- package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlightsHeader.spec.ts +42 -7
- package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +12 -10
- package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
- package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
- package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
- package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
- package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
- package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
- package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +42 -2
- package/app/components/05.forms/input-number/InputNumber.vue +37 -25
- package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
- package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
- package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
- package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
- package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
- package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
- package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
- package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
- package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
- package/app/components/05.forms/input-range/InputRange.vue +247 -0
- package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
- package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
- package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
- package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
- package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +5 -5
- package/package.json +1 -1
- package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
- package/app/components/05.forms/input-range/stories/InputRangeDefault.stories.ts +0 -108
package/.claude/skills/index.md
CHANGED
|
@@ -88,7 +88,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
88
88
|
├── link-text.md — LinkText props, slots, usage patterns, styling
|
|
89
89
|
├── header-block.md — HeaderBlock: tagLevel/classLevel semantic-vs-visual heading decoupling, page-heading-N utility classes
|
|
90
90
|
├── text-block.md — TextBlock: vertical-rhythm text wrapper, --text-block-padding-block-start/-end tokens, heading-id slot prop for section/article aria-labelledby
|
|
91
|
-
├── page-hero-highlights.md — PageHeroHighlights template: hero + highlights strip grid,
|
|
91
|
+
├── page-hero-highlights.md — PageHeroHighlights template: hero + straddling highlights strip grid (root is the `page-hero-highlights` container, grid in `.page-hero-highlights-grid`), widthConstrained caps the column, equal-width highlights wrap at --page-hero-highlights-item-min-width, light theme-token defaults, PageHeroHighlightsHeader companion
|
|
92
92
|
├── slider-gallery.md — SliderGallery: full-screen image carousel, IGalleryData, pause-on-hover/reduced-motion auto-advance, prefixed classes, CSS tokens
|
|
93
93
|
├── services-card.md — ServicesCard props (incl. eyebrowConfig/heroConfig), actions slot, CSS tokens, page boilerplate
|
|
94
94
|
├── google-reviews.md — GoogleReviews + GoogleReviewCard: Google Places reviews in a scrolling row; env vars, server route, useGoogleReviews, props, slots, tokens, Google terms
|
|
@@ -147,8 +147,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
147
147
|
├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
|
|
148
148
|
├── input-text-core.md — InputTextCore: native text/date/number input primitive, min/max pass-through (date-picker range), CSS token API; Variants section covers InputTextWithLabel/InputPasswordWithLabel/InputTextAsNumberWithLabel
|
|
149
149
|
├── input-button.md — InputButton (renamed from InputButtonCore 2026-09-28): button/NuxtLink/a, variants, icon slots, readonly (aria-disabled, activation blocked), tokens
|
|
150
|
-
├── input-range
|
|
151
|
-
├── input-number.md — InputNumber: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberField
|
|
150
|
+
├── input-range.md — InputRange: native range-slider primitive, markers/datalist slots, dead --theme-form-range-accent-color token fixed, CSS token API; Variants section covers InputRangeField; renamed from InputRangeCore/InputRangeDefault 2026-10-16, clamped stepping, useId datalist id, data-* hooks
|
|
151
|
+
├── input-number.md — InputNumber: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberField; clamped, rounded stepping, data-* state hooks, min/step-aware inputmode
|
|
152
152
|
├── input-otp.md — InputOtp: one-time-code digit boxes (paste/autofill spreading, Backspace/arrow/Home/End nav, Enter submits, leading zeros kept, complete event, hidden input); Variants section covers InputOtpField
|
|
153
153
|
├── form-field.md — FormField: layout wrapper for one form control (width cap, gutter, field spacing), data-width/data-has-gutter/data-invalid hooks, dead .underline rule removed, CSS token API
|
|
154
154
|
├── form-fieldset.md — FormFieldset: shared fieldset+legend for checkbox/radio groups, groupRole prop (hardcoded radiogroup bug fixed), aria-required only on radiogroup, dead description slot removed, legend token API
|
|
@@ -159,9 +159,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
159
159
|
├── pending-effect.md — PendingEffect: animated dashed border InputButton draws while is-pending (has-pending-effect), tokens renamed to --pending-effect-* and read at point of use, animation moved off the host button, aria-hidden
|
|
160
160
|
├── input-textarea-core.md — InputTextareaCore: native textarea primitive, left/right decorative slots, undefined-token/dead-code/label-leak bugs fixed, CSS token API; Variants section covers InputTextareaWithLabel
|
|
161
161
|
├── input-select-core.md — InputSelectCore: native select primitive, data-driven options with icon decorator, dead required/styleClassPassthrough/isDirty-isActive functionality fixed, appearance:base-select browser-support note, CSS token API; Variants section covers InputSelectWithLabel
|
|
162
|
-
├── input-checkbox-radio.md — InputCheckboxRadio (bare control), InputCheckboxRadioField (labelled), InputCheckboxRadioButton (button/pill option): renamed from Core/WithLabel, state classes → data-* hooks, type-switch v-model fix, no aria-checked, icon font-size sizing, button spacing tokens
|
|
163
|
-
├── input-checkbox.md — MultipleCheckboxes/SingleCheckbox: fieldset-wrapped checkbox group (data-driven, button or labelled) and single checkbox, per-box native-required/styleClassPassthrough/data-testid bugs fixed, spacing token API
|
|
164
|
-
├── input-radio.md — MultipleRadiobuttons: fieldset radiogroup (data-driven, button or labelled), shared-name grouping bug fixed, dead label/placeholder/equalCols props removed, data-options-layout hook, spacing token API
|
|
162
|
+
├── input-checkbox-radio.md — InputCheckboxRadio (bare control), InputCheckboxRadioField (labelled), InputCheckboxRadioButton (button/pill option): renamed from Core/WithLabel, state classes → data-* hooks, type-switch v-model fix, no aria-checked, icon font-size sizing, button spacing tokens, long labels wrap (inline nowrap removed), optional useId button id
|
|
163
|
+
├── input-checkbox.md — MultipleCheckboxes/SingleCheckbox: fieldset-wrapped checkbox group (data-driven, button or labelled) and single checkbox, per-box native-required/styleClassPassthrough/data-testid bugs fixed, spacing token API, wraps hostile content (fieldset min-inline-size, useId option ids)
|
|
164
|
+
├── input-radio.md — MultipleRadiobuttons: fieldset radiogroup (data-driven, button or labelled), shared-name grouping bug fixed, dead label/placeholder/equalCols props removed, data-options-layout hook, spacing token API, useId button ids, wraps hostile content
|
|
165
165
|
├── toggle-switch-core.md — ToggleSwitchCore: pill/square checkbox-backed toggle primitive, dead round-prop/data-theme-error functionality and story theme-options bugs fixed, CSS token API; Variants section covers ToggleSwitchWithLabel (descriptionText/descriptionHtml via InputDescription, `description` slot removed 2026-09-29)/ToggleSwitchWithLabelInline
|
|
166
166
|
├── triple-toggle-switch.md — TripleToggleSwitch (renamed from TripleToggleSwitchCore): three-option icon radio pill with sliding marker, radiogroup + ariaLabel, system/light/dark marker-gradient tokens, reduced-motion, CSS token API
|
|
167
167
|
├── entry-animation.md — EntryAnimation: scroll-driven entry animation wrapper (slide-in/zoom-reveal/exit-blur utility classes), skipAnimation for above-the-fold loop items, reduced-motion handled at the CSS layer
|
|
@@ -321,6 +321,23 @@ it("passes myProp to ChildComponent", async () => {
|
|
|
321
321
|
|
|
322
322
|
Import the child component directly in the test file — it is not auto-imported there.
|
|
323
323
|
|
|
324
|
+
## Testing `useId()` uniqueness across instances
|
|
325
|
+
|
|
326
|
+
Each `mountSuspended` call is its own Nuxt app, so `useId()` restarts its counter and two separately
|
|
327
|
+
mounted instances get the **same** ids. That's a test artefact, not a bug. To prove two instances
|
|
328
|
+
don't collide, render both inside one host component (`MultipleCheckboxes.spec.ts`):
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const Host = defineComponent({
|
|
332
|
+
components: { ComponentUnderTest },
|
|
333
|
+
setup: () => ({ props: initialPropsData }),
|
|
334
|
+
template: `<div><ComponentUnderTest v-bind="props" /><ComponentUnderTest v-bind="props" /></div>`,
|
|
335
|
+
});
|
|
336
|
+
const host = await mountSuspended(Host);
|
|
337
|
+
const ids = host.findAll("input").map((input) => input.attributes("id"));
|
|
338
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
339
|
+
```
|
|
340
|
+
|
|
324
341
|
## Mocking browser APIs
|
|
325
342
|
|
|
326
343
|
### Global constructors (ResizeObserver, IntersectionObserver, etc.)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"SRCDEV
|
|
3
|
-
"description": "Basic
|
|
2
|
+
"SRCDEV InputRange Basic": {
|
|
3
|
+
"description": "Basic InputRange native range input (no label/error wrapper)",
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
|
-
"<
|
|
6
|
+
"<InputRange",
|
|
7
7
|
" id=\"$1fieldId\"",
|
|
8
8
|
" name=\"$2fieldName\"",
|
|
9
9
|
" v-model=\"$3fieldValue\"",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"/>"
|
|
14
14
|
]
|
|
15
15
|
},
|
|
16
|
-
"SRCDEV
|
|
17
|
-
"description": "
|
|
16
|
+
"SRCDEV InputRange With Markers": {
|
|
17
|
+
"description": "InputRange with custom tick-mark markers overlaid on the track",
|
|
18
18
|
"scope": "vue,html",
|
|
19
19
|
"body": [
|
|
20
|
-
"<
|
|
20
|
+
"<InputRange",
|
|
21
21
|
" id=\"$1fieldId\"",
|
|
22
22
|
" name=\"$2fieldName\"",
|
|
23
23
|
" v-model=\"$3fieldValue\"",
|
|
@@ -27,19 +27,39 @@
|
|
|
27
27
|
">",
|
|
28
28
|
" <template #markers>",
|
|
29
29
|
" <div class=\"input-range-markers\">",
|
|
30
|
-
" <div class=\"marker\"><span class=\"marker-icon\">$7\\ud83d\\ude00</span></div>",
|
|
31
|
-
" <div class=\"marker\"><span class=\"marker-icon\">$8\\ud83d\\ude10</span></div>",
|
|
32
|
-
" <div class=\"marker\"><span class=\"marker-icon\">$9\\ud83d\\ude1e</span></div>",
|
|
30
|
+
" <div class=\"input-range-marker\"><span class=\"input-range-marker-icon\">$7\\ud83d\\ude00</span></div>",
|
|
31
|
+
" <div class=\"input-range-marker\"><span class=\"input-range-marker-icon\">$8\\ud83d\\ude10</span></div>",
|
|
32
|
+
" <div class=\"input-range-marker\"><span class=\"input-range-marker-icon\">$9\\ud83d\\ude1e</span></div>",
|
|
33
33
|
" </div>",
|
|
34
34
|
" </template>",
|
|
35
|
-
"</
|
|
35
|
+
"</InputRange>"
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
|
-
"SRCDEV
|
|
39
|
-
"description": "
|
|
38
|
+
"SRCDEV InputRange With Datalist": {
|
|
39
|
+
"description": "InputRange with a datalist; the slot passes the generated datalistId",
|
|
40
40
|
"scope": "vue,html",
|
|
41
41
|
"body": [
|
|
42
|
-
"<
|
|
42
|
+
"<InputRange",
|
|
43
|
+
" id=\"$1fieldId\"",
|
|
44
|
+
" name=\"$2fieldName\"",
|
|
45
|
+
" v-model=\"$3fieldValue\"",
|
|
46
|
+
" :min=\"${4:0}\"",
|
|
47
|
+
" :max=\"${5:100}\"",
|
|
48
|
+
">",
|
|
49
|
+
" <template #datalist=\"{ datalistId }\">",
|
|
50
|
+
" <datalist :id=\"datalistId\" class=\"input-range-datalist\">",
|
|
51
|
+
" <option value=\"${4:0}\" label=\"${6:Low}\"></option>",
|
|
52
|
+
" <option value=\"${5:100}\" label=\"${7:High}\"></option>",
|
|
53
|
+
" </datalist>",
|
|
54
|
+
" </template>",
|
|
55
|
+
"</InputRange>"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"SRCDEV InputRangeField Basic": {
|
|
59
|
+
"description": "InputRangeField: labelled range slider with error/description support",
|
|
60
|
+
"scope": "vue,html",
|
|
61
|
+
"body": [
|
|
62
|
+
"<InputRangeField",
|
|
43
63
|
" v-model=\"$1fieldValue\"",
|
|
44
64
|
" name=\"$2fieldName\"",
|
|
45
65
|
" label=\"$3Field label\"",
|
|
@@ -51,11 +71,11 @@
|
|
|
51
71
|
"/>"
|
|
52
72
|
]
|
|
53
73
|
},
|
|
54
|
-
"SRCDEV
|
|
55
|
-
"description": "
|
|
74
|
+
"SRCDEV InputRangeField With Step Buttons": {
|
|
75
|
+
"description": "InputRangeField with step-down/step-up buttons wired into the left/right slots",
|
|
56
76
|
"scope": "vue,html",
|
|
57
77
|
"body": [
|
|
58
|
-
"<
|
|
78
|
+
"<InputRangeField",
|
|
59
79
|
" v-model=\"$1fieldValue\"",
|
|
60
80
|
" name=\"$2fieldName\"",
|
|
61
81
|
" label=\"$3Field label\"",
|
|
@@ -67,11 +87,11 @@
|
|
|
67
87
|
">",
|
|
68
88
|
" <template #left>\\u2212</template>",
|
|
69
89
|
" <template #right>+</template>",
|
|
70
|
-
"</
|
|
90
|
+
"</InputRangeField>"
|
|
71
91
|
]
|
|
72
92
|
},
|
|
73
93
|
"SRCDEV InputRange CSS Override": {
|
|
74
|
-
"description": "CSS override scaffold for
|
|
94
|
+
"description": "CSS override scaffold for InputRange tokens",
|
|
75
95
|
"scope": "css",
|
|
76
96
|
"body": [
|
|
77
97
|
".$1my-page {",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "PageHeroHighlights: header zone, straddling highlights strip, content panel",
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
|
-
"<PageHeroHighlights tag=\"section\" content-align=\"${1|center,start|}\">",
|
|
6
|
+
"<PageHeroHighlights tag=\"section\" :width-constrained=\"true\" content-align=\"${1|center,start|}\">",
|
|
7
7
|
" <template #header=\"{ headingId }\">",
|
|
8
8
|
" <PageHeroHighlightsHeader>",
|
|
9
9
|
" <template #start><h1 :id=\"headingId\">$2</h1></template>",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
" --page-hero-highlights-content-background: $3;",
|
|
32
32
|
" --page-hero-highlights-item-background: $4;",
|
|
33
33
|
" --page-hero-highlights-max-width: $5;",
|
|
34
|
+
" --page-hero-highlights-item-min-width: $6;",
|
|
34
35
|
"}"
|
|
35
36
|
]
|
|
36
37
|
}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
| Token | Default | Controls |
|
|
8
8
|
|---|---|---|
|
|
9
|
-
| `--page-hero-highlights-max-width` | `1064px` | Central column cap
|
|
9
|
+
| `--page-hero-highlights-max-width` | `1064px` | Central column cap with `width-constrained` |
|
|
10
10
|
| `--page-hero-highlights-gutter-mobile` | `16px` | Side gutter |
|
|
11
|
-
| `--page-hero-highlights-gutter-tablet` | `40px` | Side gutter at 768px+
|
|
12
|
-
| `--page-hero-highlights-gutter-desktop` | `32px` | Side gutter at 1024px+ |
|
|
13
|
-
| `--page-hero-highlights-header-background` | `
|
|
14
|
-
| `--page-hero-highlights-content-background` | `
|
|
11
|
+
| `--page-hero-highlights-gutter-tablet` | `40px` | Side gutter at 768px+ of the component's width |
|
|
12
|
+
| `--page-hero-highlights-gutter-desktop` | `32px` | Side gutter at 1024px+ of the component's width |
|
|
13
|
+
| `--page-hero-highlights-header-background` | `var(--theme-surface-subtle)` | Header zone background |
|
|
14
|
+
| `--page-hero-highlights-content-background` | `transparent` | Content zone background |
|
|
15
15
|
| `--page-hero-highlights-content-start-gap` | `1.2rem` | Space above the content panel |
|
|
16
16
|
| `--page-hero-highlights-content-end-gap` | `1.2rem` | Space below the content zone |
|
|
17
17
|
| `--page-hero-highlights-content-margin-block-start` | `2.4rem` | Space above the content slot |
|
|
@@ -23,24 +23,25 @@
|
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| `--page-hero-highlights-strip-gap` | `1rem` | Gap between highlight items |
|
|
25
25
|
| `--page-hero-highlights-strip-inset` | `1.2rem` | Strip inline inset (with the content panel) |
|
|
26
|
+
| `--page-hero-highlights-item-min-width` | `12rem` | Narrowest an item gets with `highlights-equal-widths` before the strip wraps to a new row |
|
|
26
27
|
| `--page-hero-highlights-title-height` | `1fr` | Title row height |
|
|
27
28
|
| `--page-hero-highlights-title-height-baseline` | `4rem` | Title row height with `highlight-title-baseline` |
|
|
28
29
|
| `--page-hero-highlights-item-padding` | `1.2rem` | Item padding |
|
|
29
30
|
| `--page-hero-highlights-item-padding-block-start` | the item padding | Item top padding |
|
|
30
31
|
| `--page-hero-highlights-item-padding-block-start-baseline` | `0` | Item top padding with `highlight-title-baseline` |
|
|
31
32
|
| `--page-hero-highlights-item-rows-gap` | `1.2rem` | Gap between item title and body |
|
|
32
|
-
| `--page-hero-highlights-item-background` | `
|
|
33
|
-
| `--page-hero-highlights-item-border` | `1px solid
|
|
33
|
+
| `--page-hero-highlights-item-background` | `var(--page-bg, Canvas)` | Item background |
|
|
34
|
+
| `--page-hero-highlights-item-border` | `1px solid var(--theme-border)` | Item border |
|
|
34
35
|
| `--page-hero-highlights-item-border-radius` | `8px` | Item rounding |
|
|
35
|
-
| `--page-hero-highlights-item-colour` | `
|
|
36
|
+
| `--page-hero-highlights-item-colour` | `var(--theme-text)` | Item text colour |
|
|
36
37
|
|
|
37
38
|
### Content panel (`content-panel`, on by default)
|
|
38
39
|
|
|
39
40
|
| Token | Default | Controls |
|
|
40
41
|
|---|---|---|
|
|
41
|
-
| `--page-hero-highlights-panel-background` | `var(--
|
|
42
|
-
| `--page-hero-highlights-panel-border` | `1px solid var(--
|
|
43
|
-
| `--page-hero-highlights-panel-outline` | `
|
|
42
|
+
| `--page-hero-highlights-panel-background` | `var(--theme-surface-subtle)` | Panel background |
|
|
43
|
+
| `--page-hero-highlights-panel-border` | `1px solid var(--theme-border)` | Panel border |
|
|
44
|
+
| `--page-hero-highlights-panel-outline` | `none` | Panel outline |
|
|
44
45
|
| `--page-hero-highlights-panel-border-radius` | `0.8rem` | Panel rounding |
|
|
45
46
|
|
|
46
47
|
### PageHeroHighlightsHeader
|
|
@@ -56,6 +57,20 @@
|
|
|
56
57
|
Private (not public API): `--_max-width`, `--_gutter`, `--_strip-inset`, `--_title-height`,
|
|
57
58
|
`--_item-padding`, `--_item-padding-block-start`, `--_header-slot-grid-row`.
|
|
58
59
|
|
|
60
|
+
> Changed 2026-10-16:
|
|
61
|
+
>
|
|
62
|
+
> - `width-constrained` was inverted: `true` gave a full-width column and `false` capped it at
|
|
63
|
+
> `--page-hero-highlights-max-width`, the opposite of every doc. It now matches the docs (`true` caps it,
|
|
64
|
+
> `content-align` applies). Flip any `width-constrained` you set to get the old look.
|
|
65
|
+
> - The tablet and desktop gutters never applied: their `@container` queries had no container to
|
|
66
|
+
> match. The root is now a named `page-hero-highlights` inline-size container and the rows sit in a
|
|
67
|
+
> new `.page-hero-highlights-grid` wrapper, so the gutters step at the component's own width.
|
|
68
|
+
> - Colour defaults are light, from theme tokens (were `darkblue`, `white`, `black` and `--slate-*`):
|
|
69
|
+
> set the header, item and panel tokens to keep a dark header.
|
|
70
|
+
> - `highlights-equal-widths` now wraps onto new rows below `--page-hero-highlights-item-min-width`
|
|
71
|
+
> instead of squeezing every item into one row, and grid tracks are `minmax(0, …)` so long
|
|
72
|
+
> unbroken text can't widen the layout. Highlight items get `overflow-wrap: anywhere`.
|
|
73
|
+
>
|
|
59
74
|
> Changed 2026-09-28: every token used to be declared on the root with its value, under generic
|
|
60
75
|
> names (`--max-width`, `--header-row-background-colour`, `--highlight-*`, `--content-row-*`,
|
|
61
76
|
> `--content-slot-*`, `--phh-*`), so a value set on an ancestor never landed and the names could
|
|
@@ -74,7 +89,7 @@ Private (not public API): `--_max-width`, `--_gutter`, `--_strip-inset`, `--_tit
|
|
|
74
89
|
| `.highlight-title-baseline` | root | `highlight-title-baseline` |
|
|
75
90
|
| `.equal-widths` / `.flexible-widths`, `.justify-*` | `.page-hero-highlights-strip` | `highlights-equal-widths`, `highlights-justify` |
|
|
76
91
|
|
|
77
|
-
Inner classes: `.page-hero-highlights-header-row`, `.page-hero-highlights-header-slot`,
|
|
92
|
+
Inner classes: `.page-hero-highlights-grid` (the grid itself, inside the root), `.page-hero-highlights-header-row`, `.page-hero-highlights-header-slot`,
|
|
78
93
|
`.page-hero-highlights-strip`, `.page-hero-highlights-content-row` (its `::before` is the content
|
|
79
94
|
panel), `.page-hero-highlights-content-slot`; header component `.page-hero-highlights-header-start`,
|
|
80
95
|
`.page-hero-highlights-header-end`.
|
|
@@ -96,6 +111,11 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
96
111
|
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
|
|
97
112
|
`:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
98
113
|
|
|
114
|
+
**Caveat:** the root is an inline-size container (`container: page-hero-highlights / inline-size`), so it
|
|
115
|
+
can't size itself from its content: give it a width from its context (a block in normal flow is fine; as a
|
|
116
|
+
flex or grid item give it `inline-size: 100%` or a track). The highlight item padding tokens are
|
|
117
|
+
re-declared by `highlight-title-baseline`, so with that prop on, set the `-baseline` variants.
|
|
118
|
+
|
|
99
119
|
---
|
|
100
120
|
|
|
101
121
|
## Class passthrough
|
|
@@ -5,17 +5,19 @@
|
|
|
5
5
|
:class="[elementClasses, componentClasses]"
|
|
6
6
|
:aria-labelledby="ariaLabelledby"
|
|
7
7
|
>
|
|
8
|
-
<div class="page-hero-highlights-
|
|
9
|
-
<div class="page-hero-highlights-header-
|
|
10
|
-
<
|
|
8
|
+
<div class="page-hero-highlights-grid">
|
|
9
|
+
<div class="page-hero-highlights-header-row">
|
|
10
|
+
<div class="page-hero-highlights-header-slot">
|
|
11
|
+
<slot name="header" :heading-id="headingId"></slot>
|
|
12
|
+
</div>
|
|
11
13
|
</div>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
<div class="page-hero-highlights-strip" :class="highlightClasses">
|
|
15
|
+
<slot name="highlights"></slot>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="page-hero-highlights-content-row">
|
|
18
|
+
<div class="page-hero-highlights-content-slot">
|
|
19
|
+
<slot name="content"></slot>
|
|
20
|
+
</div>
|
|
19
21
|
</div>
|
|
20
22
|
</div>
|
|
21
23
|
</component>
|
|
@@ -79,13 +81,7 @@ watch(
|
|
|
79
81
|
--_item-padding-block-start: var(--page-hero-highlights-item-padding-block-start, var(--_item-padding));
|
|
80
82
|
--_header-slot-grid-row: 1;
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
--_gutter: var(--page-hero-highlights-gutter-tablet, 40px);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@container (width >= 1024px) {
|
|
87
|
-
--_gutter: var(--page-hero-highlights-gutter-desktop, 32px);
|
|
88
|
-
}
|
|
84
|
+
container: page-hero-highlights / inline-size;
|
|
89
85
|
|
|
90
86
|
&.highlight-title-baseline {
|
|
91
87
|
--_title-height: var(--page-hero-highlights-title-height-baseline, 4rem);
|
|
@@ -93,38 +89,44 @@ watch(
|
|
|
93
89
|
--_header-slot-grid-row: 1 / span 2;
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
grid-template-columns: var(--_gutter) 1fr var(--_gutter);
|
|
102
|
-
}
|
|
92
|
+
.page-hero-highlights-grid {
|
|
93
|
+
display: grid;
|
|
94
|
+
grid-template-rows: auto var(--_title-height) 1fr auto;
|
|
95
|
+
grid-template-columns: var(--_gutter) minmax(0, 1fr) var(--_gutter);
|
|
96
|
+
gap: 0;
|
|
103
97
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
grid-template-columns: var(--_gutter) minmax(0, var(--_max-width)) minmax(var(--_gutter), 1fr);
|
|
98
|
+
@container page-hero-highlights (width >= 768px) {
|
|
99
|
+
--_gutter: var(--page-hero-highlights-gutter-tablet, 40px);
|
|
107
100
|
}
|
|
108
101
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
max(var(--_gutter), (100% - var(--_max-width)) / 2)
|
|
112
|
-
1fr
|
|
113
|
-
max(var(--_gutter), (100% - var(--_max-width)) / 2);
|
|
102
|
+
@container page-hero-highlights (width >= 1024px) {
|
|
103
|
+
--_gutter: var(--page-hero-highlights-gutter-desktop, 32px);
|
|
114
104
|
}
|
|
115
105
|
}
|
|
116
106
|
|
|
107
|
+
&.width-constrained.content-align-start .page-hero-highlights-grid {
|
|
108
|
+
grid-template-columns: var(--_gutter) minmax(0, var(--_max-width)) minmax(var(--_gutter), 1fr);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&.width-constrained.content-align-center .page-hero-highlights-grid {
|
|
112
|
+
grid-template-columns:
|
|
113
|
+
max(var(--_gutter), (100% - var(--_max-width)) / 2)
|
|
114
|
+
minmax(0, 1fr)
|
|
115
|
+
max(var(--_gutter), (100% - var(--_max-width)) / 2);
|
|
116
|
+
}
|
|
117
|
+
|
|
117
118
|
.page-hero-highlights-header-row {
|
|
118
119
|
grid-column: 1 / -1;
|
|
119
120
|
grid-row: 1 / 3;
|
|
120
121
|
display: grid;
|
|
121
122
|
grid-template-columns: subgrid;
|
|
122
123
|
grid-template-rows: subgrid;
|
|
123
|
-
background-color: var(--page-hero-highlights-header-background,
|
|
124
|
+
background-color: var(--page-hero-highlights-header-background, var(--theme-surface-subtle));
|
|
124
125
|
|
|
125
126
|
.page-hero-highlights-header-slot {
|
|
126
127
|
grid-column: 2;
|
|
127
128
|
grid-row: var(--_header-slot-grid-row);
|
|
129
|
+
min-inline-size: 0;
|
|
128
130
|
container-type: inline-size;
|
|
129
131
|
}
|
|
130
132
|
}
|
|
@@ -134,19 +136,27 @@ watch(
|
|
|
134
136
|
grid-row: 2 / 4;
|
|
135
137
|
position: relative;
|
|
136
138
|
z-index: 1;
|
|
139
|
+
min-inline-size: 0;
|
|
137
140
|
gap: var(--page-hero-highlights-strip-gap, 1rem);
|
|
138
141
|
margin-inline-start: 0;
|
|
139
142
|
|
|
140
143
|
&.equal-widths {
|
|
141
144
|
display: grid;
|
|
142
|
-
grid-
|
|
143
|
-
|
|
145
|
+
grid-template-columns: repeat(
|
|
146
|
+
auto-fit,
|
|
147
|
+
minmax(min(100%, var(--page-hero-highlights-item-min-width, 12rem)), 1fr)
|
|
148
|
+
);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
&.flexible-widths {
|
|
147
152
|
display: flex;
|
|
148
153
|
flex-wrap: wrap;
|
|
149
154
|
justify-content: center;
|
|
155
|
+
|
|
156
|
+
& > * {
|
|
157
|
+
min-inline-size: 0;
|
|
158
|
+
max-inline-size: 100%;
|
|
159
|
+
}
|
|
150
160
|
}
|
|
151
161
|
|
|
152
162
|
&.justify-start {
|
|
@@ -173,18 +183,19 @@ watch(
|
|
|
173
183
|
display: grid;
|
|
174
184
|
grid-template-rows: subgrid;
|
|
175
185
|
grid-auto-flow: row;
|
|
186
|
+
overflow-wrap: anywhere;
|
|
176
187
|
padding-block: var(--_item-padding-block-start) var(--_item-padding);
|
|
177
188
|
padding-inline: var(--_item-padding);
|
|
178
|
-
background-color: var(--page-hero-highlights-item-background,
|
|
179
|
-
border: var(--page-hero-highlights-item-border, 1px solid
|
|
189
|
+
background-color: var(--page-hero-highlights-item-background, var(--page-bg, Canvas));
|
|
190
|
+
border: var(--page-hero-highlights-item-border, 1px solid var(--theme-border));
|
|
180
191
|
border-radius: var(--page-hero-highlights-item-border-radius, 8px);
|
|
181
|
-
color: var(--page-hero-highlights-item-colour,
|
|
192
|
+
color: var(--page-hero-highlights-item-colour, var(--theme-text));
|
|
182
193
|
|
|
183
194
|
.page-hero-highlights-item-title {
|
|
184
195
|
display: grid;
|
|
185
196
|
grid-row: 2;
|
|
186
197
|
align-items: end;
|
|
187
|
-
|
|
198
|
+
block-size: var(--_title-height);
|
|
188
199
|
}
|
|
189
200
|
|
|
190
201
|
.page-hero-highlights-item-body {
|
|
@@ -202,12 +213,13 @@ watch(
|
|
|
202
213
|
grid-template-rows: subgrid;
|
|
203
214
|
position: relative;
|
|
204
215
|
isolation: isolate;
|
|
205
|
-
background-color: var(--page-hero-highlights-content-background,
|
|
216
|
+
background-color: var(--page-hero-highlights-content-background, transparent);
|
|
206
217
|
padding-block-end: var(--page-hero-highlights-content-end-gap, 1.2rem);
|
|
207
218
|
|
|
208
219
|
.page-hero-highlights-content-slot {
|
|
209
220
|
grid-column: 2;
|
|
210
221
|
grid-row: 2;
|
|
222
|
+
min-inline-size: 0;
|
|
211
223
|
margin-block: var(--page-hero-highlights-content-margin-block-start, 2.4rem)
|
|
212
224
|
var(--page-hero-highlights-content-margin, var(--_strip-inset));
|
|
213
225
|
margin-inline: 0;
|
|
@@ -222,15 +234,12 @@ watch(
|
|
|
222
234
|
.page-hero-highlights-content-row {
|
|
223
235
|
&::before {
|
|
224
236
|
content: "";
|
|
225
|
-
display: grid;
|
|
226
|
-
grid-template-columns: subgrid;
|
|
227
|
-
grid-template-rows: subgrid;
|
|
228
237
|
grid-column: 2;
|
|
229
238
|
grid-row: 1 / span 2;
|
|
230
|
-
margin-
|
|
231
|
-
background-color: var(--page-hero-highlights-panel-background, var(--
|
|
232
|
-
border: var(--page-hero-highlights-panel-border, 1px solid var(--
|
|
233
|
-
outline: var(--page-hero-highlights-panel-outline,
|
|
239
|
+
margin-block-start: var(--page-hero-highlights-content-start-gap, 1.2rem);
|
|
240
|
+
background-color: var(--page-hero-highlights-panel-background, var(--theme-surface-subtle));
|
|
241
|
+
border: var(--page-hero-highlights-panel-border, 1px solid var(--theme-border));
|
|
242
|
+
outline: var(--page-hero-highlights-panel-outline, none);
|
|
234
243
|
border-radius: var(--page-hero-highlights-panel-border-radius, 0.8rem);
|
|
235
244
|
}
|
|
236
245
|
|