srcdev-nuxt-components 9.0.15 → 9.0.16
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/settings.json +25 -0
- package/.claude/skills/component-aria-landmark.md +68 -0
- package/.claude/skills/component-dynamic-slots.md +150 -0
- package/.claude/skills/component-local-style-override.md +126 -0
- package/.claude/skills/component-prop-driven-container-layout.md +42 -0
- package/.claude/skills/components/accordian-core.md +159 -0
- package/.claude/skills/components/contact-section.md +101 -0
- package/.claude/skills/components/expanding-panel.md +156 -0
- package/.claude/skills/components/eyebrow-text.md +25 -0
- package/.claude/skills/components/hero-text.md +25 -0
- package/.claude/skills/components/layout-grid-by-cols.md +147 -0
- package/.claude/skills/components/layout-row.md +35 -0
- package/.claude/skills/components/link-text.md +33 -0
- package/.claude/skills/components/page-hero-highlights.md +224 -0
- package/.claude/skills/components/services-card.md +28 -0
- package/.claude/skills/components/services-section.md +25 -0
- package/.claude/skills/components/stepper-list.md +227 -0
- package/.claude/skills/css-grid-max-width-gutters.md +67 -0
- package/.claude/skills/index.md +14 -3
- package/.claude/skills/storybook-add-story.md +60 -0
- package/.claude/skills/testing-add-unit-test.md +56 -0
- package/app/assets/styles/setup/01.config/index.css +0 -1
- package/app/assets/styles/setup/03.theming/default/_dark.css +2 -2
- package/app/assets/styles/setup/04.elements/forms/02.typography.css +1 -0
- package/app/assets/styles/setup/05.typography/02.utility-classes/_font-classes-page-link.css +14 -14
- package/app/assets/styles/setup/index.css +0 -1
- package/app/components/01.atoms/card/CardCore.vue +92 -0
- package/app/components/01.atoms/card/stories/CardCore.stories.ts +132 -0
- package/app/components/01.atoms/card/tests/CardCore.spec.ts +207 -0
- package/app/components/01.atoms/card/tests/__snapshots__/CardCore.spec.ts.snap +43 -0
- package/app/components/01.atoms/content-wrappers/content-columns-2/ContentColumns2.vue +51 -0
- package/app/components/01.atoms/content-wrappers/content-columns-2/stories/ContentColumns2.stories.ts +110 -0
- package/app/components/01.atoms/content-wrappers/content-columns-2/tests/ContentColumns2.spec.ts +105 -0
- package/app/components/01.atoms/content-wrappers/content-columns-2/tests/__snapshots__/ContentColumns2.spec.ts.snap +14 -0
- package/app/components/01.atoms/content-wrappers/content-width/ContentWidth.vue +88 -0
- package/app/components/01.atoms/content-wrappers/content-width/stories/ContentWidth.stories.ts +362 -0
- package/app/components/01.atoms/content-wrappers/content-width/tests/ContentWidth.spec.ts +132 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +71 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +219 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +174 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/LayoutGridByWidth.vue +70 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/stories/LayoutGridByWidth.stories.ts +220 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/LayoutGridByWidth.spec.ts +174 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByWidth.spec.ts.snap +36 -0
- package/app/components/01.atoms/text-blocks/eyebrow-text/stories/EyebrowText.stories.ts +1 -1
- package/app/components/01.atoms/text-blocks/hero-text/stories/HeroText.stories.ts +1 -1
- package/app/components/01.atoms/text-blocks/link-text/stories/LinkText.stories.ts +1 -1
- package/app/components/02.molecules/contact-section/stories/ContactSection.stories.ts +5 -0
- package/app/components/02.molecules/contact-section/tests/ContactSection.spec.ts +15 -0
- package/app/components/02.molecules/contact-section/tests/ContactSection.vue +25 -17
- package/app/components/{accordian → 02.molecules/expandable/accordian}/stories/AccordianCore.stories.ts +1 -1
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +245 -0
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +351 -0
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +38 -0
- package/app/components/02.molecules/navigation/navigation-horizontal/NavigationHorizontal.vue +139 -0
- package/app/components/02.molecules/navigation/navigation-horizontal/NavigationHorizontalAdvanced.vue +172 -0
- package/app/components/02.molecules/profile-section/ProfileSection.vue +2 -3
- package/app/components/02.molecules/profile-section/tests/ProfileSection.spec.ts +2 -2
- package/app/components/02.molecules/stepper-list/StepperList.vue +131 -92
- package/app/components/02.molecules/stepper-list/stories/StepperList.stories.ts +31 -0
- package/app/components/02.molecules/stepper-list/tests/StepperList.spec.ts +24 -0
- package/app/components/02.molecules/stepper-list/tests/__snapshots__/StepperList.spec.ts.snap +22 -9
- package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +782 -0
- package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +233 -0
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +226 -0
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +69 -0
- package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +1 -1
- package/app/components/03.organisms/services/services-grids/ServicesSectionGrid.vue +1 -1
- package/app/components/03.organisms/services/services-section/ServicesSection.vue +2 -3
- package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +239 -0
- package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +404 -0
- package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +198 -0
- package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +19 -0
- package/app/components/container-glow/ContainerGlowCore.vue +20 -27
- package/app/components/forms/input-button/InputButtonCore.vue +105 -104
- package/app/components/glowing-border/stories/GlowingBorder.stories.ts +21 -21
- package/app/composables/useAriaLabelledById.ts +13 -0
- package/app/layouts/default.vue +8 -3
- package/app/pages/forms/examples/buttons/index.vue +6 -6
- package/app/pages/forms/examples/material/checkbox-radio-panels.vue +3 -3
- package/app/pages/forms/examples/material/text-fields.vue +607 -610
- package/app/pages/page-hero-highlights.vue +81 -0
- package/app/pages/ui/{display-card.vue → card-core.vue} +15 -15
- package/app/pages/ui/contact-section.vue +1 -1
- package/app/pages/ui/container-glow.vue +1 -1
- package/app/pages/ui/content-width.vue +126 -0
- package/app/pages/ui/glowing-border.vue +9 -9
- package/app/pages/ui/navigation/navigation-horizontal.vue +493 -0
- package/app/pages/ui/services/services-section/[slug].vue +3 -1
- package/package.json +2 -2
- package/app/assets/styles/setup/01.config/_basic-resets.css +0 -9
- package/app/components/content-columns/TwoColumns.vue +0 -59
- package/app/components/content-columns/stories/TwoColumns.stories.ts +0 -561
- package/app/components/content-containers/ContentContainer.vue +0 -89
- package/app/components/content-containers/stories/ContentContainer.stories.ts +0 -465
- package/app/components/content-grid/ContentGrid.vue +0 -85
- package/app/components/display-card/DisplayCard.vue +0 -122
- package/app/components/image-galleries/SliderGallery.vue +0 -786
- package/app/pages/ui/content-container.vue +0 -112
- /package/app/components/{accordian → 02.molecules/expandable/accordian}/AccordianCore.vue +0 -0
- /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/AccordianCore.spec.ts +0 -0
- /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -0
- /package/app/components/{expanding-panel → 02.molecules/expandable/expanding-panel}/ExpandingPanel.vue +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# ExpandingPanel Component
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`ExpandingPanel` is a single expand/collapse panel built on the native `<details>`/`<summary>` element. It animates open/close via a CSS grid-template-rows trick, supports `v-model` for controlled state, and can be locked open with `forceOpened`. Multiple panels can be grouped into a native accordion by sharing the same `name` prop (see `AccordianCore`).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Props reference
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Default | Notes |
|
|
12
|
+
|------|------|---------|-------|
|
|
13
|
+
| `name` | `string` | `useId()` | Identifies the panel. Used in ARIA attributes (`id-{name}-trigger`, `id-{name}-content`). If omitted, a unique id is generated automatically. |
|
|
14
|
+
| `animationDuration` | `number` | `400` | Expand/collapse transition duration in milliseconds. Pass `0` to disable animation. |
|
|
15
|
+
| `forceOpened` | `boolean` | `false` | When `true`, the panel is always open. The toggle icon is hidden and clicks do not close the panel. |
|
|
16
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.expanding-panel` element. |
|
|
17
|
+
|
|
18
|
+
## Model
|
|
19
|
+
|
|
20
|
+
| Model | Type | Default | Notes |
|
|
21
|
+
|-------|------|---------|-------|
|
|
22
|
+
| `v-model` | `boolean` | `false` | Controls open/closed state. Bind to a `ref<boolean>` to manage state externally. |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Slots
|
|
27
|
+
|
|
28
|
+
| Slot | Purpose |
|
|
29
|
+
|------|---------|
|
|
30
|
+
| `#summary` | Content rendered inside the clickable `<summary>` row (label area). |
|
|
31
|
+
| `#icon` | Custom toggle icon. Defaults to a `bi:caret-down-fill` icon that flips on open. Hidden when `forceOpened` is `true`. |
|
|
32
|
+
| `#content` | Content revealed when the panel is open. Can contain any markup. |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Usage examples
|
|
37
|
+
|
|
38
|
+
### Basic uncontrolled panel
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<ExpandingPanel name="delivery">
|
|
42
|
+
<template #summary>
|
|
43
|
+
<span>Delivery & Returns</span>
|
|
44
|
+
</template>
|
|
45
|
+
<template #content>
|
|
46
|
+
<p>Free standard delivery on orders over £50.</p>
|
|
47
|
+
</template>
|
|
48
|
+
</ExpandingPanel>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Controlled via v-model
|
|
52
|
+
|
|
53
|
+
```vue
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
const isOpen = ref(false);
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<ExpandingPanel name="faq-1" v-model="isOpen">
|
|
60
|
+
<template #summary><span>What is your returns policy?</span></template>
|
|
61
|
+
<template #content><p>You can return any item within 30 days.</p></template>
|
|
62
|
+
</ExpandingPanel>
|
|
63
|
+
<button @click="isOpen = !isOpen">Toggle externally</button>
|
|
64
|
+
</template>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Force opened (always visible, no toggle)
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<ExpandingPanel name="notice" :force-opened="true">
|
|
71
|
+
<template #summary><strong>Important notice</strong></template>
|
|
72
|
+
<template #content>
|
|
73
|
+
<p>This panel cannot be collapsed.</p>
|
|
74
|
+
</template>
|
|
75
|
+
</ExpandingPanel>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Custom icon
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<ExpandingPanel name="custom-icon">
|
|
82
|
+
<template #summary><span>Section title</span></template>
|
|
83
|
+
<template #icon>
|
|
84
|
+
<svg width="12" height="12" viewBox="0 0 12 12">
|
|
85
|
+
<path d="M6 9L1 3h10z" fill="currentColor" />
|
|
86
|
+
</svg>
|
|
87
|
+
</template>
|
|
88
|
+
<template #content><p>Content here.</p></template>
|
|
89
|
+
</ExpandingPanel>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Slow animation
|
|
93
|
+
|
|
94
|
+
```vue
|
|
95
|
+
<ExpandingPanel name="slow" :animation-duration="800">
|
|
96
|
+
<template #summary><span>Slow panel</span></template>
|
|
97
|
+
<template #content><p>Opens and closes over 800 ms.</p></template>
|
|
98
|
+
</ExpandingPanel>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## ARIA / accessibility
|
|
104
|
+
|
|
105
|
+
The component wires ARIA automatically from the `name` prop:
|
|
106
|
+
|
|
107
|
+
| Element | Attribute | Value |
|
|
108
|
+
|---------|-----------|-------|
|
|
109
|
+
| `<summary>` | `id` | `id-{name}-trigger` |
|
|
110
|
+
| `<summary>` | `aria-controls` | `id-{name}-content` |
|
|
111
|
+
| `<summary>` | `aria-expanded` | `true` / `false` |
|
|
112
|
+
| content div | `id` | `id-{name}-content` |
|
|
113
|
+
| content div | `aria-labelledby` | `id-{name}-trigger` |
|
|
114
|
+
| content div | `role` | `region` |
|
|
115
|
+
|
|
116
|
+
Always supply a meaningful `name` prop when using multiple panels on the same page to avoid duplicate IDs.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Local style override scaffold
|
|
121
|
+
|
|
122
|
+
When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
|
|
123
|
+
|
|
124
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
125
|
+
|
|
126
|
+
```vue
|
|
127
|
+
<ExpandingPanel name="my-item" :style-class-passthrough="['my-panel']">
|
|
128
|
+
...
|
|
129
|
+
</ExpandingPanel>
|
|
130
|
+
|
|
131
|
+
<style>
|
|
132
|
+
/* ─── ExpandingPanel local overrides ───────────────────────────────
|
|
133
|
+
Colours, borders, geometry only — do not override behaviour.
|
|
134
|
+
Delete this block if no overrides are needed.
|
|
135
|
+
─────────────────────────────────────────────────────────────────── */
|
|
136
|
+
.expanding-panel {
|
|
137
|
+
&.my-panel {
|
|
138
|
+
/* Border */
|
|
139
|
+
/* border-block-end: 1px solid currentColor; */
|
|
140
|
+
|
|
141
|
+
/* Summary row geometry */
|
|
142
|
+
/* .expanding-panel-details .expanding-panel-summary { padding-block: 1.2rem; } */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Notes
|
|
151
|
+
|
|
152
|
+
- The open/close animation uses `grid-template-rows: 0fr → 1fr` — no JS height measurement needed.
|
|
153
|
+
- When `forceOpened` is `true`, `open` stays `true` regardless of `v-model`, but `v-model` still updates internally on clicks (useful if you later set `forceOpened` back to `false`).
|
|
154
|
+
- Group panels into a native accordion (only one open at a time) by passing the same `name` to multiple panels or use `AccordianCore` which handles this automatically.
|
|
155
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
156
|
+
- File: `app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue`
|
|
@@ -77,6 +77,31 @@ Text is always `text-transform: uppercase` — do not pass pre-uppercased string
|
|
|
77
77
|
|
|
78
78
|
Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `.eyebrow-text`.
|
|
79
79
|
|
|
80
|
+
## Local style override scaffold
|
|
81
|
+
|
|
82
|
+
When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
|
|
83
|
+
|
|
84
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<EyebrowText :style-class-passthrough="['my-eyebrow']" text-content="Our Services" />
|
|
88
|
+
|
|
89
|
+
<style>
|
|
90
|
+
/* ─── EyebrowText local overrides ──────────────────────────────────
|
|
91
|
+
Colours, borders, geometry only — do not override behaviour.
|
|
92
|
+
Delete this block if no overrides are needed.
|
|
93
|
+
─────────────────────────────────────────────────────────────────── */
|
|
94
|
+
.eyebrow-text {
|
|
95
|
+
&.my-eyebrow {
|
|
96
|
+
/* Colours */
|
|
97
|
+
/* --colour-text-eyebrow: var(--brand-accent); */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> **Note:** Font size is controlled via the `fontSize` prop and theme tokens `--eyebrow-text-large/medium/small` — define these at theme level, not as local overrides.
|
|
104
|
+
|
|
80
105
|
## Notes
|
|
81
106
|
|
|
82
107
|
- Component is auto-imported in Nuxt — no import needed.
|
|
@@ -103,6 +103,31 @@ Key CSS custom properties:
|
|
|
103
103
|
- `--colour-text-accent` — colour applied to `.accent` spans and the icon
|
|
104
104
|
- `--hero-text-{scale}` — font size per scale value
|
|
105
105
|
|
|
106
|
+
## Local style override scaffold
|
|
107
|
+
|
|
108
|
+
When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
|
|
109
|
+
|
|
110
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
111
|
+
|
|
112
|
+
```vue
|
|
113
|
+
<HeroText :style-class-passthrough="['my-hero']" tag="h1" :text-content="[...]" />
|
|
114
|
+
|
|
115
|
+
<style>
|
|
116
|
+
/* ─── HeroText local overrides ─────────────────────────────────────
|
|
117
|
+
Colours, borders, geometry only — do not override behaviour.
|
|
118
|
+
Delete this block if no overrides are needed.
|
|
119
|
+
─────────────────────────────────────────────────────────────────── */
|
|
120
|
+
.hero-text {
|
|
121
|
+
&.my-hero {
|
|
122
|
+
/* Colours */
|
|
123
|
+
/* --colour-text-accent: var(--brand-primary); */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</style>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **Note:** Font size is controlled via the `fontSize` prop and theme tokens `--hero-text-display/title/heading/subheading/label` — define these at theme level, not as local overrides.
|
|
130
|
+
|
|
106
131
|
## Notes
|
|
107
132
|
|
|
108
133
|
- Text segments are trimmed and a trailing space is automatically appended between segments in horizontal axis — do not manually pad `text` values.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# LayoutGridByCols Component
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`LayoutGridByCols` is a CSS grid layout wrapper that arranges content into a fixed number of equal-width columns. It uses **named dynamic slots** — the component renders whatever slots the consumer passes, in the order they appear. It collapses to a single column below a configurable breakpoint.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Slot pattern
|
|
10
|
+
|
|
11
|
+
Pass any number of slots with any names. The component renders each one in document order inside the grid.
|
|
12
|
+
|
|
13
|
+
```vue
|
|
14
|
+
<LayoutGridByCols :column-count="3">
|
|
15
|
+
<template #item-0><ServicesCard /></template>
|
|
16
|
+
<template #item-1><ServicesCard /></template>
|
|
17
|
+
<template #item-2><ServicesCard /></template>
|
|
18
|
+
</LayoutGridByCols>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
When filling from a data array, use a dynamic slot name in a `v-for`:
|
|
22
|
+
|
|
23
|
+
```vue
|
|
24
|
+
<LayoutGridByCols :column-count="3">
|
|
25
|
+
<template v-for="(item, i) in items" #[`item-${i}`] :key="i">
|
|
26
|
+
<ServicesCard :data="item" />
|
|
27
|
+
</template>
|
|
28
|
+
</LayoutGridByCols>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Props reference
|
|
34
|
+
|
|
35
|
+
> **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:column-count`, `:single-col-below`, `:style-class-passthrough`.
|
|
36
|
+
|
|
37
|
+
| Prop (template form) | Type | Default | Notes |
|
|
38
|
+
|------|------|---------|-------|
|
|
39
|
+
| `:column-count` | `2 \| 3 \| 4 \| 5 \| 6` | `2` | Number of equal-width columns above `single-col-below`. Minimum is 2. |
|
|
40
|
+
| `:gap` | `string` | `"1rem"` | Any valid CSS length or shorthand. |
|
|
41
|
+
| `:single-col-below` | `string` | `"768px"` | Container width below which the grid collapses to a single column. |
|
|
42
|
+
| `tag` | `"div" \| "section"` | `"div"` | Use `"section"` for semantic page regions. |
|
|
43
|
+
| `label` | `string` | `""` | Required when `tag="section"` — rendered as a visually-hidden `<p>` linked via `aria-labelledby`. |
|
|
44
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root element. |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Usage examples
|
|
49
|
+
|
|
50
|
+
### Two-column grid (default)
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<LayoutGridByCols>
|
|
54
|
+
<template #left>
|
|
55
|
+
<p>Left cell</p>
|
|
56
|
+
</template>
|
|
57
|
+
<template #right>
|
|
58
|
+
<p>Right cell</p>
|
|
59
|
+
</template>
|
|
60
|
+
</LayoutGridByCols>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Three-column card grid
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<LayoutGridByCols :column-count="3" gap="2rem">
|
|
67
|
+
<template #card-a><ServicesCard title="Card A" /></template>
|
|
68
|
+
<template #card-b><ServicesCard title="Card B" /></template>
|
|
69
|
+
<template #card-c><ServicesCard title="Card C" /></template>
|
|
70
|
+
</LayoutGridByCols>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Section with accessible label
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<LayoutGridByCols tag="section" label="Our team" :column-count="4" gap="1.6rem">
|
|
77
|
+
<template #alice><TeamCard name="Alice" /></template>
|
|
78
|
+
<template #bob><TeamCard name="Bob" /></template>
|
|
79
|
+
<template #carol><TeamCard name="Carol" /></template>
|
|
80
|
+
<template #dan><TeamCard name="Dan" /></template>
|
|
81
|
+
</LayoutGridByCols>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Data-driven with v-for
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<LayoutGridByCols :column-count="3">
|
|
88
|
+
<template v-for="(item, i) in items" #[`item-${i}`] :key="i">
|
|
89
|
+
<Card :data="item" />
|
|
90
|
+
</template>
|
|
91
|
+
</LayoutGridByCols>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Accessibility
|
|
97
|
+
|
|
98
|
+
- When `tag="section"`, the root element automatically receives `aria-labelledby` pointing to a visually-hidden `<p>` with the `label` value.
|
|
99
|
+
- Always provide a meaningful `label` when using `tag="section"`.
|
|
100
|
+
- When `tag="div"`, no label or ARIA attributes are added.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Responsive behaviour
|
|
105
|
+
|
|
106
|
+
Uses **CSS container queries** (`container-type: inline-size`) — responds to its own container width, not the viewport.
|
|
107
|
+
|
|
108
|
+
- **Below `singleColBelow`**: single-column stacked layout.
|
|
109
|
+
- **At or above `singleColBelow`**: `columnCount`-column grid.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Local style override scaffold
|
|
114
|
+
|
|
115
|
+
When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
|
|
116
|
+
|
|
117
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<LayoutGridByCols :style-class-passthrough="['my-grid']" :column-count="3">
|
|
121
|
+
...
|
|
122
|
+
</LayoutGridByCols>
|
|
123
|
+
|
|
124
|
+
<style>
|
|
125
|
+
/* ─── LayoutGridByCols local overrides ─────────────────────────────
|
|
126
|
+
Colours, borders, geometry only — do not override behaviour.
|
|
127
|
+
Delete this block if no overrides are needed.
|
|
128
|
+
─────────────────────────────────────────────────────────────────── */
|
|
129
|
+
.layout-grid-by-cols {
|
|
130
|
+
&.my-grid {
|
|
131
|
+
/* Colours */
|
|
132
|
+
/* background: var(--brand-surface); */
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</style>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> **Note:** `gap` and `column-count` are prop-driven — use the props rather than CSS overrides for layout changes.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Notes
|
|
143
|
+
|
|
144
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
145
|
+
- `column-count` is clamped to a minimum of 2 internally.
|
|
146
|
+
- `gap` accepts any CSS value including compound values like `"1rem 2rem"`.
|
|
147
|
+
- Slot names can be anything — semantic or indexed. Document order determines render order.
|
|
@@ -196,6 +196,41 @@ Override in consuming app to adjust all track sizes globally:
|
|
|
196
196
|
|
|
197
197
|
---
|
|
198
198
|
|
|
199
|
+
## Local style override scaffold
|
|
200
|
+
|
|
201
|
+
When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
|
|
202
|
+
|
|
203
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
204
|
+
|
|
205
|
+
```vue
|
|
206
|
+
<LayoutRow variant="content" :style-class-passthrough="['my-row']">
|
|
207
|
+
...
|
|
208
|
+
</LayoutRow>
|
|
209
|
+
|
|
210
|
+
<style>
|
|
211
|
+
/* ─── LayoutRow local overrides ────────────────────────────────────
|
|
212
|
+
Colours, borders, geometry only — do not override behaviour.
|
|
213
|
+
Delete this block if no overrides are needed.
|
|
214
|
+
─────────────────────────────────────────────────────────────────── */
|
|
215
|
+
.layout-row {
|
|
216
|
+
&.my-row {
|
|
217
|
+
/* Track widths — for this instance only */
|
|
218
|
+
/* --content-max-width: 1200px; */
|
|
219
|
+
/* --popout-max-width: 1600px; */
|
|
220
|
+
/* --inset-content-max-width: 900px; */
|
|
221
|
+
/* --minimum-content-padding: 2rem; */
|
|
222
|
+
|
|
223
|
+
/* Colours */
|
|
224
|
+
/* background: var(--brand-surface); */
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
</style>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
> **Note:** Track width custom properties set here affect only this instance. For site-wide changes, set them at `:root` or theme level.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
199
234
|
## Notes
|
|
200
235
|
|
|
201
236
|
- Auto-imported in Nuxt — no import needed.
|
|
@@ -93,6 +93,39 @@ Key CSS custom properties — define these in your consuming app to control appe
|
|
|
93
93
|
|
|
94
94
|
Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `.link-text`.
|
|
95
95
|
|
|
96
|
+
## Local style override scaffold
|
|
97
|
+
|
|
98
|
+
When consuming this component, scaffold a style block using `styleClassPassthrough`. Delete the block if unused.
|
|
99
|
+
|
|
100
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
101
|
+
|
|
102
|
+
```vue
|
|
103
|
+
<LinkText :style-class-passthrough="['my-link']" to="/path" link-text="Learn More" />
|
|
104
|
+
|
|
105
|
+
<style>
|
|
106
|
+
/* ─── LinkText local overrides ─────────────────────────────────────
|
|
107
|
+
Colours, borders, geometry only — do not override behaviour.
|
|
108
|
+
Delete this block if no overrides are needed.
|
|
109
|
+
─────────────────────────────────────────────────────────────────── */
|
|
110
|
+
.link-text {
|
|
111
|
+
&.my-link {
|
|
112
|
+
/* Colours */
|
|
113
|
+
/* --link-text-colour: var(--brand-primary); */
|
|
114
|
+
/* --link-text-colour-hover: var(--brand-primary-dark); */
|
|
115
|
+
|
|
116
|
+
/* Typography */
|
|
117
|
+
/* --link-text-font-size: 1.6rem; */
|
|
118
|
+
/* --link-text-decoration: underline; */
|
|
119
|
+
/* --link-text-decoration-hover: none; */
|
|
120
|
+
/* --link-text-underline-offset: 0.3em; */
|
|
121
|
+
|
|
122
|
+
/* Geometry */
|
|
123
|
+
/* --link-text-gap: 0.6em; */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</style>
|
|
127
|
+
```
|
|
128
|
+
|
|
96
129
|
## Notes
|
|
97
130
|
|
|
98
131
|
- Component is auto-imported in Nuxt — no import needed.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# PageHeroHighlights Component
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`PageHeroHighlights` is a page-level layout template that creates a "hero + highlights strip" grid. It has:
|
|
6
|
+
|
|
7
|
+
- A **header zone** — full edge-to-edge background, content drives the row height
|
|
8
|
+
- A **highlights strip** — straddles the header/content boundary (overlaps both), sits above via `z-index`
|
|
9
|
+
- A **content zone** — background fills behind the highlights strip, actual content sits below it
|
|
10
|
+
|
|
11
|
+
The layout uses a 4-row CSS Grid with `subgrid` — no `translate`, negative margins, or absolute positioning.
|
|
12
|
+
|
|
13
|
+
## Props
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
| ----------------------- | ------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `tag` | `"div" \| "section" \| "main"` | `"div"` | Root element tag |
|
|
18
|
+
| `highlightsEqualWidths` | `boolean` | `false` | Equal-width grid columns for highlight items |
|
|
19
|
+
| `highlightsJustify` | `"start" \| "center" \| "end" \| "space-between" \| "space-around"` | `"start"` | Alignment of highlight items along the main axis |
|
|
20
|
+
| `maxWidth` | `string` | `undefined` | Cap the central content column (e.g. `"1064px"`). Gutters grow to enforce the constraint; below this width they hold at `16px`. |
|
|
21
|
+
| `contentAlign` | `"start" \| "center"` | `"center"` | When `maxWidth` is set: `"center"` grows gutters equally; `"start"` pins content to the left with a fixed `16px` left gutter. |
|
|
22
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root element |
|
|
23
|
+
|
|
24
|
+
## Slots
|
|
25
|
+
|
|
26
|
+
| Slot | Slot props | Purpose |
|
|
27
|
+
| ------------ | ----------------------- | ----------------------------------- |
|
|
28
|
+
| `header` | `{ headingId: string }` | Header zone — text, title, subtitle |
|
|
29
|
+
| `highlights` | — | Highlight cards in the strip |
|
|
30
|
+
| `content` | — | Page body content below the strip |
|
|
31
|
+
|
|
32
|
+
## Basic usage
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<PageHeroHighlights>
|
|
36
|
+
<template #header>
|
|
37
|
+
<p class="page-heading-1">Dashboard</p>
|
|
38
|
+
<p class="page-body-normal">Overview of your account activity.</p>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<template #highlights>
|
|
42
|
+
<div class="card">Total Revenue: £24,500</div>
|
|
43
|
+
<div class="card">Active Users: 1,284</div>
|
|
44
|
+
<div class="card">Open Tasks: 37</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<template #content>
|
|
48
|
+
<p class="page-heading-2">Recent Activity</p>
|
|
49
|
+
</template>
|
|
50
|
+
</PageHeroHighlights>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Composition with other library components
|
|
54
|
+
|
|
55
|
+
Each slot accepts any content, but these library components are natural fits:
|
|
56
|
+
|
|
57
|
+
| Slot | Component | Notes |
|
|
58
|
+
| ------------- | --------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `#header` | `HeroText` | Heading with accent text, icon, and configurable size — wires `headingId` for `aria-labelledby` |
|
|
60
|
+
| `#highlights` | `ServicesCard` (×n) | Portrait cards with image, title, description, and CTA slot |
|
|
61
|
+
| `#highlights` | `LayoutGridByCols` wrapping cards | When you want a responsive column grid rather than a single row of cards |
|
|
62
|
+
| `#content` | Any content component | Below the straddle — safe to use `LayoutRow`, `ServicesSection`, etc. |
|
|
63
|
+
|
|
64
|
+
Example with `HeroText` in the header slot:
|
|
65
|
+
|
|
66
|
+
```vue
|
|
67
|
+
<PageHeroHighlights tag="section" max-width="1064px">
|
|
68
|
+
<template #header="{ headingId }">
|
|
69
|
+
<HeroText :heading-id="headingId" text="Welcome back" accent-text="Simon" />
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<template #highlights>
|
|
73
|
+
<ServicesCard v-for="item in highlights" :key="item.id" v-bind="item">
|
|
74
|
+
<template #actions>
|
|
75
|
+
<LinkText :href="item.href">View</LinkText>
|
|
76
|
+
</template>
|
|
77
|
+
</ServicesCard>
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<template #content>
|
|
81
|
+
<!-- page body -->
|
|
82
|
+
</template>
|
|
83
|
+
</PageHeroHighlights>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## With aria-labelledby (section tag)
|
|
87
|
+
|
|
88
|
+
When `tag="section"`, `aria-labelledby` is set automatically. Wire the heading id via the scoped slot prop:
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<PageHeroHighlights tag="section">
|
|
92
|
+
<template #header="{ headingId }">
|
|
93
|
+
<h1 :id="headingId" class="page-heading-1">Dashboard</h1>
|
|
94
|
+
</template>
|
|
95
|
+
...
|
|
96
|
+
</PageHeroHighlights>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
See [component-aria-landmark.md](../component-aria-landmark.md) for the full pattern.
|
|
100
|
+
|
|
101
|
+
## Equal-width highlights
|
|
102
|
+
|
|
103
|
+
By default, highlight items size to their content (`flex-wrap`). Pass `:highlights-equal-widths="true"` to switch to a grid where all items share equal column widths:
|
|
104
|
+
|
|
105
|
+
```vue
|
|
106
|
+
<PageHeroHighlights :highlights-equal-widths="true">
|
|
107
|
+
...
|
|
108
|
+
</PageHeroHighlights>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Constraining the central column width
|
|
112
|
+
|
|
113
|
+
Pass `max-width` to cap the content column. The gutters grow to enforce it — full-bleed backgrounds are unaffected and `subgrid` continues to work. Use `content-align` to pin to the left or centre:
|
|
114
|
+
|
|
115
|
+
```vue
|
|
116
|
+
<!-- Centred, capped at 1064px -->
|
|
117
|
+
<PageHeroHighlights max-width="1064px" content-align="center">...</PageHeroHighlights>
|
|
118
|
+
|
|
119
|
+
<!-- Left-pinned, capped at 1064px (right side takes remaining space) -->
|
|
120
|
+
<PageHeroHighlights max-width="1064px" content-align="start">...</PageHeroHighlights>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
See [css-grid-max-width-gutters.md](../css-grid-max-width-gutters.md) for the full pattern explanation.
|
|
124
|
+
|
|
125
|
+
## Local style override scaffold
|
|
126
|
+
|
|
127
|
+
When consuming this component, scaffold a style block using `styleClassPassthrough`. The block below lists every available CSS custom property — update the values you need and delete the rest.
|
|
128
|
+
|
|
129
|
+
See [component-local-style-override.md](../component-local-style-override.md) for the full pattern.
|
|
130
|
+
|
|
131
|
+
```vue
|
|
132
|
+
<PageHeroHighlights :style-class-passthrough="['my-page-hero']">
|
|
133
|
+
...
|
|
134
|
+
</PageHeroHighlights>
|
|
135
|
+
|
|
136
|
+
<style>
|
|
137
|
+
/* ─── PageHeroHighlights local overrides ────────────────────────────
|
|
138
|
+
Update values as needed. Delete tokens you are not overriding.
|
|
139
|
+
─────────────────────────────────────────────────────────────────── */
|
|
140
|
+
.page-hero-highlights {
|
|
141
|
+
&.my-page-hero {
|
|
142
|
+
/* Header zone */
|
|
143
|
+
/* --header-row-background-colour: darkblue; */
|
|
144
|
+
|
|
145
|
+
/* Highlights strip */
|
|
146
|
+
/* --highlights-row-item-gap: 1rem; */
|
|
147
|
+
/* --highlights-row-initial-item-offset: 1.2rem; */
|
|
148
|
+
|
|
149
|
+
/* Highlight cards */
|
|
150
|
+
/* --highlight-rows-gap: 1.2rem; */
|
|
151
|
+
/* --highlight-title-height: 1fr; */ /* see: highlight-title-baseline prop */
|
|
152
|
+
/* --highlight-padding-block-start: 1.2rem; */
|
|
153
|
+
/* --highlight-padding: 1.2rem; */
|
|
154
|
+
/* --highlight-background-color: white; */
|
|
155
|
+
/* --highlight-border: 1px solid black; */
|
|
156
|
+
/* --highlight-border-radius: 8px; */
|
|
157
|
+
/* --highlight-color: black; */
|
|
158
|
+
|
|
159
|
+
/* Content zone */
|
|
160
|
+
/* --content-row-background-color: var(--slate-01); */ /* transparent */
|
|
161
|
+
/* --content-row-start-gap: 1.2rem; */
|
|
162
|
+
/* --content-row-end-gap: 1.2rem; */
|
|
163
|
+
|
|
164
|
+
/* Content slot decorative border */
|
|
165
|
+
/* --content-slot-margin-block-start: 2.4rem; */
|
|
166
|
+
/* --content-slot-margin: var(--highlights-row-initial-item-offset); */
|
|
167
|
+
/* --content-slot-background-color: var(--slate-00); */
|
|
168
|
+
/* --content-slot-border: 1px solid var(--slate-06); */
|
|
169
|
+
/* --content-slot-border-radius: 0.8rem; */
|
|
170
|
+
/* --content-slot-outline: 1px solid var(--slate-02); */
|
|
171
|
+
|
|
172
|
+
/* When using :highlight-title-baseline="true" */
|
|
173
|
+
/* &.highlight-title-baseline { */
|
|
174
|
+
/* --highlight-title-height: 4rem; */ /* proportional value preferred */
|
|
175
|
+
/* --highlight-padding-block-start: 0; */
|
|
176
|
+
/* } */
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
</style>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
> **Note:** The minimum gutter width (`16px`) and layout behaviour are not overridable via CSS custom properties. Use the `max-width` and `content-align` props to control column constraints.
|
|
183
|
+
|
|
184
|
+
## Grid structure (reference)
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
col: [gutter] [centre] [gutter]
|
|
188
|
+
row1: header content (height driven by slot)
|
|
189
|
+
row2: highlights top half ← highlights spans rows 2–3, z-index: 1
|
|
190
|
+
row3: highlights bottom half
|
|
191
|
+
row4: page content (never underflows highlights)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`.header-row` spans cols 1–3, rows 1–2 (edge-to-edge bg). `.header-slot` is placed in row 1 only.
|
|
195
|
+
`.content-row` spans cols 1–3, rows 3–4 (bg fills behind highlights; `.content-slot` is placed in row 4 only). The decorative border behind `.content-slot` is rendered via `.content-row:before` — there is no separate DOM element for it.
|
|
196
|
+
|
|
197
|
+
## Layout pitfall: do not use `grid-template-rows: subgrid` inside `.highlights-row`
|
|
198
|
+
|
|
199
|
+
The `.highlights-row` element spans rows 2–3 of the parent grid (the "straddle"). If you add an inner grid to `.highlights-row` (e.g. to extend `equal-widths` behaviour) and include `grid-template-rows: subgrid`, auto-placed items will only occupy row 1 of the subgrid (= parent row 2). Parent row 3 collapses to 0-height, destroying the straddle effect — `.content-row` appears immediately below the highlights instead of overlapping it.
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
/* ❌ — breaks the straddle when items are auto-placed by column flow */
|
|
203
|
+
&.equal-widths {
|
|
204
|
+
display: grid;
|
|
205
|
+
grid-template-rows: subgrid; /* row 3 collapses */
|
|
206
|
+
grid-auto-columns: 1fr;
|
|
207
|
+
grid-auto-flow: column;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ✅ — single implicit row; items stretch to fill combined height of rows 2–3 */
|
|
211
|
+
&.equal-widths {
|
|
212
|
+
display: grid;
|
|
213
|
+
grid-auto-columns: 1fr;
|
|
214
|
+
grid-auto-flow: column;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Notes
|
|
219
|
+
|
|
220
|
+
- Component is auto-imported in Nuxt — no import needed.
|
|
221
|
+
- Lives in `app/components/04.templates/page-hero-highlights/`.
|
|
222
|
+
- Storybook title: `"Templates/PageHeroHighlights"`.
|
|
223
|
+
- **Minimum gutter is fixed at `16px`** — it is baked into the `gridColumns` computed and cannot be overridden by a CSS custom property. If a consumer needs a different minimum (e.g. `24px`), it requires a prop or a fork of the component.
|
|
224
|
+
- **`contentAlign` has no effect without `maxWidth`** — both sides always hold `16px` when `maxWidth` is not set.
|