srcdev-nuxt-components 9.6.1 → 9.6.3
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/consumer-overrides.md +17 -0
- package/.claude/rules/consumer/css-comment-slash.md +13 -0
- package/.claude/rules/consumer/css-native-nesting.md +13 -0
- package/.claude/rules/consumer/css-no-deep.md +13 -0
- package/.claude/rules/consumer/css-rem-base.md +11 -0
- package/.claude/rules/consumer/css-surface-text-pairing.md +13 -0
- package/.claude/rules/consumer/css-webkit-support.md +15 -0
- package/.claude/rules/consumer/explicit-vue-imports.md +11 -0
- package/.claude/rules/consumer/nuxt-img-dimensions.md +12 -0
- package/.claude/rules/consumer/srcdev-skills-location.md +9 -0
- package/.claude/rules/consumer/story-args.md +12 -0
- package/.claude/rules/consumer/story-register-components.md +12 -0
- package/.claude/rules/consumer/vue-auto-imports.md +14 -0
- package/.claude/rules/consumer/vue-no-self-closing.md +11 -0
- package/.claude/rules/consumer/vue-prop-hyphenation.md +11 -0
- package/.claude/rules/consumer/vue-props-pattern.md +13 -0
- package/.claude/rules/consumer/vue-slots-no-computed.md +14 -0
- package/.claude/rules/consumer/vue-video-source.md +12 -0
- package/.claude/skills/component-compliance-checklist.md +37 -2
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/display-avatar.md +14 -3
- package/.claude/skills/components/display-chip.md +2 -1
- package/.claude/skills/components/google-reviews.md +2 -2
- package/.claude/skills/components/pricing-card.md +68 -84
- package/.claude/skills/components/profile-section.md +59 -21
- package/.claude/skills/components/social-icons-list.md +2 -0
- package/.claude/skills/index.md +13 -6
- package/.claude/skills/new-app-scaffold.md +4 -2
- package/.claude/skills/setup-postinstall.md +5 -4
- package/.claude/skills/storybook-add-story.md +38 -0
- package/.claude/skills/theming-component-token-pattern.md +94 -0
- package/.claude/skills/theming-partial-override.md +1 -1
- package/.vscode/srcdev-component-display-avatar.code-snippets +7 -0
- package/.vscode/srcdev-component-pricing-card.code-snippets +46 -84
- package/.vscode/srcdev-component-profile-section.code-snippets +27 -4
- package/README.md +5 -5
- package/app/components/01.atoms/animations/scroll-reveal-frame/stories/ScrollRevealFrame.stories.ts +2 -0
- package/app/components/01.atoms/animations/scroll-reveal-image/stories/ScrollRevealImage.stories.ts +2 -0
- package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +4 -0
- package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +2 -0
- package/app/components/02.molecules/cookie-consent-banner/stories/CookieConsentBanner.stories.ts +2 -0
- package/app/components/02.molecules/display-avatar/CONSUMER-STYLING.md +26 -10
- package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +75 -34
- package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +84 -3
- package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +85 -13
- package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +3 -3
- package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +7 -2
- package/app/components/02.molecules/input-copy/stories/InputCopy.stories.ts +0 -1
- package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +96 -149
- package/app/components/02.molecules/pricing-card/PricingCard.vue +84 -44
- package/app/components/02.molecules/pricing-card/stories/PricingCard.stories.ts +241 -104
- package/app/components/02.molecules/pricing-card/tests/PricingCard.spec.ts +149 -123
- package/app/components/02.molecules/profile-section/CONSUMER-STYLING.md +103 -13
- package/app/components/02.molecules/profile-section/ProfileSection.vue +123 -23
- package/app/components/02.molecules/profile-section/stories/ProfileSection.stories.ts +353 -115
- package/app/components/02.molecules/profile-section/tests/ProfileSection.spec.ts +144 -11
- package/app/components/02.molecules/profile-section/tests/__snapshots__/ProfileSection.spec.ts.snap +6 -11
- package/app/components/02.molecules/social-icons-list/CONSUMER-STYLING.md +6 -2
- package/app/components/02.molecules/social-icons-list/SocialIconsList.vue +1 -0
- package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +4 -0
- package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +4 -0
- package/app/components/03.organisms/google-reviews/tests/__snapshots__/GoogleReviewCard.spec.ts.snap +1 -1
- package/app/components/03.organisms/google-reviews/tests/__snapshots__/GoogleReviews.spec.ts.snap +6 -6
- package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabel.vue +1 -1
- package/app/composables/useAriaDescribedById.ts +1 -1
- package/package.json +2 -1
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
`PricingCard` renders a
|
|
5
|
+
`PricingCard` (`app/components/02.molecules/pricing-card/`) renders a pricing plan card: plan name,
|
|
6
|
+
price, billing period, description, feature list and a call-to-action button. `is-highlighted`
|
|
7
|
+
adds a "Most Popular" badge plus a scaled, emphasised border; `ribbon-text` adds a diagonal corner
|
|
8
|
+
ribbon. The CTA can be replaced through the `#cta` slot. Styling tokens:
|
|
9
|
+
`app/components/02.molecules/pricing-card/CONSUMER-STYLING.md`.
|
|
6
10
|
|
|
7
11
|
---
|
|
8
12
|
|
|
@@ -10,34 +14,42 @@
|
|
|
10
14
|
|
|
11
15
|
> **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:style-class-passthrough`.
|
|
12
16
|
|
|
13
|
-
| Prop (template form) | Type
|
|
14
|
-
| -------------------------- |
|
|
15
|
-
| `tag` | `"div" \| "section" \| "article"`
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
| `:
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
| `:
|
|
23
|
-
| `:
|
|
24
|
-
|
|
|
17
|
+
| Prop (template form) | Type | Default | Notes |
|
|
18
|
+
| -------------------------- | -------------------------------------- | ---------------- | ----- |
|
|
19
|
+
| `tag` | `"div" \| "section" \| "article"` | `"article"` | Root element. Always labelled by the plan name heading (`aria-labelledby`). |
|
|
20
|
+
| `heading-tag` | `"h2" \| "h3" \| "h4" \| "h5" \| "h6"` | `"h3"` | Heading level for the plan name; pick to fit the page outline. |
|
|
21
|
+
| `plan-name` | `string` | (required) | Plan name. |
|
|
22
|
+
| `:price` | `number` | (required) | Whole numbers show as-is, fractional ones to two decimals (`9.5` → `9.50`). A non-finite value shows nothing. |
|
|
23
|
+
| `currency-symbol` | `string` | `"$"` | Literal prefix; no locale formatting. Pass `""` for none. |
|
|
24
|
+
| `billing-period` | `string` | `"one-time"` | Shown after the price; `""` hides it. |
|
|
25
|
+
| `description` | `string` | `undefined` | Short plan description. |
|
|
26
|
+
| `:features` | `string[]` | `[]` | Rendered as a checkmark list (checkmark hidden from screen readers). |
|
|
27
|
+
| `:is-highlighted` | `boolean` | `false` | Badge, highlight border/shadow, and `scale(1.05)`. |
|
|
28
|
+
| `badge-text` | `string` | `"Most Popular"` | Badge copy; pass a translated string for i18n. |
|
|
29
|
+
| `cta-text` | `string` | `"Get started"` | Default button label. |
|
|
30
|
+
| `:cta-disabled` | `boolean` | `false` | Sets the default button to `readonly`/`aria-disabled`. |
|
|
31
|
+
| `ribbon-text` | `string` | `undefined` | Corner ribbon copy; unset for no ribbon. Caps at two lines (`--pricing-card-ribbon-line-clamp`). |
|
|
32
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the root. |
|
|
33
|
+
|
|
34
|
+
All visitor-facing copy (`badge-text`, `cta-text`, `billing-period`) is a prop, so pass strings from
|
|
35
|
+
your own i18n solution.
|
|
25
36
|
|
|
26
37
|
---
|
|
27
38
|
|
|
28
39
|
## Events
|
|
29
40
|
|
|
30
|
-
| Event
|
|
31
|
-
|
|
|
32
|
-
| `select` | `planName: string` | Emitted when the CTA
|
|
41
|
+
| Event | Payload | Notes |
|
|
42
|
+
| -------- | ------------------ | ----- |
|
|
43
|
+
| `select` | `planName: string` | Emitted when the default CTA is clicked, or when a `#cta` slot calls `onSelect`. |
|
|
33
44
|
|
|
34
45
|
---
|
|
35
46
|
|
|
36
47
|
## Slots
|
|
37
48
|
|
|
38
|
-
###
|
|
49
|
+
### `#features`
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
Replaces the default `<li>` list. Your content goes inside the component's `<ul>`, so render `<li>`
|
|
52
|
+
elements. `--pricing-card-feature-line-clamp` and the checkmark only apply to the built-in list.
|
|
41
53
|
|
|
42
54
|
```vue
|
|
43
55
|
<PricingCard plan-name="Pro" :price="99">
|
|
@@ -47,97 +59,57 @@ Override the default feature list rendering. Receives no slot scope.
|
|
|
47
59
|
</PricingCard>
|
|
48
60
|
```
|
|
49
61
|
|
|
50
|
-
###
|
|
62
|
+
### `#cta`
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
Replaces the default `InputButton`. Slot scope:
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
- `
|
|
56
|
-
- `
|
|
57
|
-
- `
|
|
58
|
-
- `onSelect: () => void` — callback to trigger `select` event
|
|
66
|
+
- `ctaText: string` — from `cta-text`
|
|
67
|
+
- `isDisabled: boolean` — from `cta-disabled`
|
|
68
|
+
- `planName: string`
|
|
69
|
+
- `onSelect: () => void` — emits `select`
|
|
59
70
|
|
|
60
71
|
```vue
|
|
61
72
|
<PricingCard plan-name="Pro" :price="99" @select="handleSelect">
|
|
62
73
|
<template #cta="{ ctaText, isDisabled, onSelect }">
|
|
63
|
-
<InputButton
|
|
64
|
-
:button-text="ctaText"
|
|
65
|
-
variant="secondary"
|
|
66
|
-
theme="custom"
|
|
67
|
-
:readonly="isDisabled"
|
|
68
|
-
@click="onSelect"
|
|
69
|
-
/>
|
|
74
|
+
<InputButton :button-text="ctaText" variant="secondary" :readonly="isDisabled" @click="onSelect" />
|
|
70
75
|
</template>
|
|
71
76
|
</PricingCard>
|
|
72
77
|
```
|
|
73
78
|
|
|
74
79
|
---
|
|
75
80
|
|
|
76
|
-
##
|
|
81
|
+
## Styling
|
|
77
82
|
|
|
78
|
-
All `--pricing-card-*` tokens
|
|
83
|
+
All `--pricing-card-*` tokens are listed in `CONSUMER-STYLING.md`. The common ones:
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
- `--pricing-card-
|
|
82
|
-
- `--pricing-card-
|
|
83
|
-
- `--pricing-card-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
- `--pricing-card-padding`, `--pricing-card-border-radius`, `--pricing-card-background`
|
|
86
|
+
- `--pricing-card-highlight-border`, `--pricing-card-highlight-scale`
|
|
87
|
+
- `--pricing-card-name-line-clamp`, `--pricing-card-description-line-clamp`, `--pricing-card-feature-line-clamp` (default `none`)
|
|
88
|
+
- `--pricing-card-feature-icon-color` (checkmark colour)
|
|
89
|
+
|
|
90
|
+
The default CTA is styled with `InputButton`'s own tokens (`--input-button-primary-surface`, etc.),
|
|
91
|
+
set on the card or an ancestor. There are no `--pricing-card-cta-*` colour tokens: the old ones never
|
|
92
|
+
applied and were removed 2026-10-14. `--pricing-card-cta-align` still controls CTA alignment.
|
|
86
93
|
|
|
87
94
|
---
|
|
88
95
|
|
|
89
|
-
## Usage
|
|
96
|
+
## Usage examples
|
|
90
97
|
|
|
91
|
-
### Single card
|
|
98
|
+
### Single card
|
|
92
99
|
|
|
93
100
|
```vue
|
|
94
101
|
<PricingCard
|
|
95
102
|
plan-name="Basic"
|
|
96
103
|
:price="29.99"
|
|
97
|
-
|
|
104
|
+
currency-symbol="£"
|
|
105
|
+
billing-period="per month"
|
|
98
106
|
description="Perfect for getting started"
|
|
99
107
|
:features="['Feature 1', 'Feature 2', 'Feature 3']"
|
|
100
|
-
cta-text="Get started"
|
|
101
|
-
@select="handleSelect"
|
|
102
|
-
/>
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Highlighted "Most Popular" card
|
|
106
|
-
|
|
107
|
-
```vue
|
|
108
|
-
<PricingCard
|
|
109
|
-
plan-name="Professional"
|
|
110
|
-
:price="79.99"
|
|
111
|
-
billing-period="monthly"
|
|
112
|
-
:is-highlighted="true"
|
|
113
|
-
:features="['All Basic features', 'Priority support', 'Advanced analytics']"
|
|
114
108
|
@select="handleSelect"
|
|
115
109
|
/>
|
|
116
110
|
```
|
|
117
111
|
|
|
118
|
-
###
|
|
119
|
-
|
|
120
|
-
```vue
|
|
121
|
-
<PricingCard
|
|
122
|
-
plan-name="Enterprise"
|
|
123
|
-
:price="199.99"
|
|
124
|
-
billing-period="monthly"
|
|
125
|
-
description="Everything you need"
|
|
126
|
-
:features="['Unlimited access', 'Dedicated support', 'Custom integrations']"
|
|
127
|
-
@select="handleSelect"
|
|
128
|
-
>
|
|
129
|
-
<template #cta="{ ctaText, isDisabled, onSelect }">
|
|
130
|
-
<InputButton
|
|
131
|
-
:button-text="ctaText"
|
|
132
|
-
variant="primary"
|
|
133
|
-
:readonly="isDisabled"
|
|
134
|
-
@click="onSelect"
|
|
135
|
-
/>
|
|
136
|
-
</template>
|
|
137
|
-
</PricingCard>
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Three-column grid with token customization
|
|
112
|
+
### Comparison grid
|
|
141
113
|
|
|
142
114
|
```vue
|
|
143
115
|
<div class="pricing-grid">
|
|
@@ -148,16 +120,28 @@ All `--pricing-card-*` tokens can be overridden at global, page, or instance sco
|
|
|
148
120
|
:price="plan.price"
|
|
149
121
|
:is-highlighted="plan.id === 'pro'"
|
|
150
122
|
:features="plan.features"
|
|
123
|
+
heading-tag="h2"
|
|
151
124
|
@select="handleSelect"
|
|
152
|
-
style="--pricing-card-padding: 2.5rem; --pricing-card-cta-bg: var(--brand-primary)"
|
|
153
125
|
/>
|
|
154
126
|
</div>
|
|
155
127
|
```
|
|
156
128
|
|
|
129
|
+
```css
|
|
130
|
+
.pricing-grid {
|
|
131
|
+
display: grid;
|
|
132
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
|
|
133
|
+
gap: 2.4rem;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
157
137
|
---
|
|
158
138
|
|
|
159
139
|
## Notes
|
|
160
140
|
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
-
|
|
141
|
+
- The highlight scale is a `transform`, so it doesn't change layout size. In a stacked single
|
|
142
|
+
column set `--pricing-card-highlight-scale: 1`, or the highlighted card overhangs.
|
|
143
|
+
- The badge sits centred on the top border (`translate: 0 -50%`), so leave some space above the
|
|
144
|
+
card (grid gap or padding).
|
|
145
|
+
- The plan name gets `--pricing-card-ribbon-clearance` (`6rem`) of right padding while a ribbon is
|
|
146
|
+
shown, so long names don't run underneath it.
|
|
147
|
+
- Stories: `Molecules/Pricing Card`, including `Stress Test (Worst-Case Data)`.
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ProfileSection
|
|
3
|
-
description: ProfileSection molecule — props, dynamic profile-info slots, eyebrowText/heroText/profileLinks slots, layout, accessibility
|
|
3
|
+
description: ProfileSection molecule — props, dynamic profile-info slots, eyebrowText/heroText/profileLinks slots, container-query layout, accessibility
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# ProfileSection
|
|
8
8
|
|
|
9
|
+
> **Changed 2026-10-14 (breaking):** New `headerPlacement` prop (`above`, `picture-bottom`, `picture-top`) can put the header over the bottom or top of the picture. `profileInfoCount` removed — only the `profile-info-N` slots you give are rendered (it used to pad with empty blocks when none were given). The two-column switch is a container query on the component's own width, not a viewport `@media` query, and both columns are `minmax(0, …)`. The `<header>` is only rendered when a header slot is given. Highlight colours go through new `--profile-section-*` tokens. An invalid `profilePicture.width`/`height` (0, negative, `NaN`) falls back to 828×1104.
|
|
10
|
+
>
|
|
9
11
|
> **Classes renamed 2026-09-27:** `.picture` → `.profile-section-picture`, `.profile-picture` → `.profile-section-image`, `.profile-info` → `.profile-section-info`, `.profile-info-content` → `.profile-section-info-content`, `.profile-info-block` → `.profile-section-info-block`, `.profile-links` → `.profile-section-links`.
|
|
10
12
|
|
|
11
13
|
## Overview
|
|
12
14
|
|
|
13
|
-
`ProfileSection` is a molecule that renders a practitioner/author profile: a header area (eyebrow + heading), a profile picture, and a flexible set of bio/info blocks alongside optional profile links. It is landmark-aware — when `tag` is `section
|
|
15
|
+
`ProfileSection` is a molecule that renders a practitioner/author profile: a header area (eyebrow + heading), a profile picture, and a flexible set of bio/info blocks alongside optional profile links. It is landmark-aware — when `tag` is `section` or `article`, the root element automatically gets `aria-labelledby` wired to the heading inside the `#heroText` slot (**you must bind `headingId` yourself, and a console warning fires if you forget** — see [component-aria-landmark.md](../component-aria-landmark.md)). `tag="main"` is never auto-labelled.
|
|
14
16
|
|
|
15
17
|
## Props
|
|
16
18
|
|
|
17
19
|
| Prop | Type | Default | Description |
|
|
18
20
|
|------|------|---------|-------------|
|
|
19
|
-
| `profilePicture` | `ProfilePicture` (`{ src; alt; width?; height? }`, exported from the layer types) | — | **Required.** Rendered via `NuxtImg
|
|
21
|
+
| `profilePicture` | `ProfilePicture` (`{ src; alt; width?; height? }`, exported from the layer types) | — | **Required.** Rendered via `NuxtImg`. `width`/`height` default to `828`/`1104` (3:4, a Vercel-allowed width), and a missing or invalid value (0, negative, `NaN`, `Infinity`) uses the default. |
|
|
20
22
|
| `tag` | `"div" \| "section" \| "article" \| "main"` | `"div"` | HTML element rendered as the root. |
|
|
21
|
-
| `
|
|
23
|
+
| `headerPlacement` | `"above" \| "picture-bottom" \| "picture-top"` | `"above"` | Where the `#eyebrowText`/`#heroText` header goes: above the grid, or over the bottom or top of the picture on a scrim. Template form: `header-placement`. |
|
|
22
24
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
23
25
|
|
|
24
26
|
## Slots
|
|
@@ -28,17 +30,19 @@ type: reference
|
|
|
28
30
|
| `#eyebrowText` | Optional eyebrow label above the heading. Use `EyebrowText`. |
|
|
29
31
|
| `#heroText` | Heading slot. Receives `headingId` as a slot prop — bind it to the heading's `id` for accessible `aria-labelledby`. |
|
|
30
32
|
| `#profile-info-1` … `#profile-info-N` | Bio / info blocks. Provide as many numbered slots as needed. They are rendered in ascending numeric order. |
|
|
31
|
-
| `#profileLinks` | Links / actions shown at the bottom-right of the info column (e.g. social icons, booking CTA). |
|
|
33
|
+
| `#profileLinks` | Links / actions shown at the bottom-right of the info column (e.g. social icons, booking CTA). Wraps onto more lines when there are many. |
|
|
34
|
+
|
|
35
|
+
The `<header class="profile-section-header">` is only rendered when `#eyebrowText` or `#heroText` is given. It renders above the grid, or inside the picture when `header-placement` is `picture-bottom` or `picture-top`.
|
|
32
36
|
|
|
33
37
|
## Basic usage
|
|
34
38
|
|
|
35
39
|
```vue
|
|
36
40
|
<ProfileSection
|
|
37
41
|
tag="section"
|
|
38
|
-
:profile-picture="{ src: '/images/profile/
|
|
42
|
+
:profile-picture="{ src: '/images/profile/alex.jpg', alt: 'Alex Morgan, hair stylist' }"
|
|
39
43
|
>
|
|
40
44
|
<template #eyebrowText>
|
|
41
|
-
<EyebrowText font-size="large" text="About
|
|
45
|
+
<EyebrowText font-size="large" text-content="About Alex" />
|
|
42
46
|
</template>
|
|
43
47
|
|
|
44
48
|
<template #heroText="{ headingId }">
|
|
@@ -47,8 +51,8 @@ type: reference
|
|
|
47
51
|
:id="headingId"
|
|
48
52
|
font-size="title"
|
|
49
53
|
:text-content="[
|
|
50
|
-
{ text: '
|
|
51
|
-
{ text: '
|
|
54
|
+
{ text: 'Alex Morgan', styleClass: 'normal' },
|
|
55
|
+
{ text: 'Hair stylist', styleClass: 'accent' },
|
|
52
56
|
]"
|
|
53
57
|
/>
|
|
54
58
|
</template>
|
|
@@ -58,7 +62,7 @@ type: reference
|
|
|
58
62
|
</template>
|
|
59
63
|
|
|
60
64
|
<template #profile-info-2>
|
|
61
|
-
<p>
|
|
65
|
+
<p class="location">Based in <span class="highlight">Northtown</span>…</p>
|
|
62
66
|
</template>
|
|
63
67
|
|
|
64
68
|
<template #profileLinks>
|
|
@@ -69,11 +73,12 @@ type: reference
|
|
|
69
73
|
|
|
70
74
|
## Dynamic profile-info slots
|
|
71
75
|
|
|
72
|
-
The `profile-info-N` slots are discovered at runtime by filtering `useSlots()` for keys matching `/^profile-info-\d
|
|
76
|
+
The `profile-info-N` slots are discovered at runtime by filtering `useSlots()` for keys matching `/^profile-info-\d+$/`, and rendered in ascending numeric order.
|
|
73
77
|
|
|
74
78
|
- If you provide `#profile-info-1`, `#profile-info-2`, `#profile-info-3` — all three render in order.
|
|
75
|
-
- If you provide no
|
|
76
|
-
- Gaps are supported — you can provide `#profile-info-1` and `#profile-info-3` without `#profile-info-2`;
|
|
79
|
+
- If you provide none, no info blocks render.
|
|
80
|
+
- Gaps are supported — you can provide `#profile-info-1` and `#profile-info-3` without `#profile-info-2`; `#profile-info-12` sorts after `#profile-info-2`.
|
|
81
|
+
- Discovery runs every render, so slots added or removed after mount (a `v-if` on the slot template) appear and disappear.
|
|
77
82
|
|
|
78
83
|
## heroText slot prop
|
|
79
84
|
|
|
@@ -89,10 +94,41 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
89
94
|
|
|
90
95
|
## Layout
|
|
91
96
|
|
|
92
|
-
- **
|
|
93
|
-
- **768px
|
|
94
|
-
-
|
|
95
|
-
-
|
|
97
|
+
- The root is a size container named `profile-section`. Layout follows **the component's own width**, not the viewport.
|
|
98
|
+
- **Under 768px wide**: single column — picture stacked above info, `--profile-section-gap` (`2rem`).
|
|
99
|
+
- **768px wide or more**: two columns — `minmax(0, var(--profile-section-picture-width))` (`38.4rem`) picture column, `minmax(0, 1fr)` info column, `--profile-section-gap-wide` (`4rem`) gap.
|
|
100
|
+
- Being an inline-size container, the root can't size itself from its content: give it a width in a flex row or other shrink-to-fit context.
|
|
101
|
+
- The root sets `overflow-wrap: anywhere`, so unbroken text and URLs in any slot wrap rather than widening the grid.
|
|
102
|
+
- Picture frame (`.profile-section-picture`): `--profile-section-picture-aspect-ratio` (`3 / 4`), `--profile-section-picture-border-radius` (`0.8rem`), `overflow: hidden`. The `NuxtImg` fills the frame with `object-fit: cover`. Full token list: `CONSUMER-STYLING.md` in the component folder.
|
|
103
|
+
- Profile links: `flex-wrap: wrap; align-items: end; justify-content: flex-end` — right-aligned to the bottom of the info column.
|
|
104
|
+
- To change the picture column width, set `--profile-section-picture-width`; don't override `grid-template-columns` in a viewport `@media` query, which no longer lines up with the component's own switch.
|
|
105
|
+
|
|
106
|
+
## Header in picture
|
|
107
|
+
|
|
108
|
+
With `header-placement="picture-bottom"` or `"picture-top"`, the `<header>` renders inside `.profile-section-picture`, before the image in the DOM (so it is read first), and sits at the bottom or top of the frame on a scrim that fades away from that edge. It follows ServicesCard's `titles-within-image-wrapper`:
|
|
109
|
+
|
|
110
|
+
- Scrim and text come from `--profile-section-picture-header-*` tokens: scrim colour (`#000`) and opacity (`75%`), a solid stop (`0%`, how far the scrim stays full strength before fading) and a target colour (`transparent`, what it fades into, set it to roughly the photo's colour where the fade ends so it blends instead of leaving a grey band), text colour (`contrast-color()` of the scrim colour where supported, else `#fff`), eyebrow colour, padding (`2.4rem`), gap (`0.4rem`). Full list in `CONSUMER-STYLING.md`.
|
|
111
|
+
- The frame drops `overflow: hidden` in this mode, so its aspect ratio is a minimum: a long header grows the frame rather than being clipped. The image is absolutely positioned and still fills the frame.
|
|
112
|
+
- `aria-labelledby` still points at the heading.
|
|
113
|
+
- Drop spacing utilities like `mb-20` from the heading in this mode; the header's padding handles spacing.
|
|
114
|
+
|
|
115
|
+
```vue
|
|
116
|
+
<ProfileSection tag="section" header-placement="picture-bottom" :profile-picture="picture">
|
|
117
|
+
<template #eyebrowText>
|
|
118
|
+
<EyebrowText tag="p" font-size="medium" text-content="About Alex" />
|
|
119
|
+
</template>
|
|
120
|
+
<template #heroText="{ headingId }">
|
|
121
|
+
<HeroText :id="headingId" tag="h2" font-size="heading" :text-content="[{ text: 'Alex Morgan', styleClass: 'normal' }]" />
|
|
122
|
+
</template>
|
|
123
|
+
...
|
|
124
|
+
</ProfileSection>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Two stories show themes built only from tokens: `Header In Picture (Dark, Bottom)`, ServicesCard-style, and `Header In Picture (Light, Top)`. A light scrim needs a dark `--profile-section-picture-header-text-colour`; `contrast-color()` picks one where supported, but set it for older browsers. `--profile-section-info-text-colour` sets the info column's text colour for a themed background.
|
|
128
|
+
|
|
129
|
+
## Content hooks
|
|
130
|
+
|
|
131
|
+
A `.highlight` inside an element with class `location` gets `--profile-section-highlight-colour` (default `var(--colour-text-accent)`); inside `services` it gets `--profile-section-link-colour` / `--profile-section-link-colour-hover` (defaults `var(--colour-link-default)` / `var(--colour-link-hover)`). Both use `--profile-section-highlight-font-weight` (`600`). The `location`/`services` class can sit on the `<p>` itself or a wrapper.
|
|
96
132
|
|
|
97
133
|
## CSS notes
|
|
98
134
|
|
|
@@ -106,8 +142,6 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
106
142
|
}
|
|
107
143
|
```
|
|
108
144
|
|
|
109
|
-
- Accent-coloured text within `.location` or `.services` info blocks can use the `.highlight` class for `var(--colour-text-accent)` or `var(--colour-link-default)` colouring respectively.
|
|
110
|
-
|
|
111
145
|
## Consumer styling scaffold
|
|
112
146
|
|
|
113
147
|
```vue
|
|
@@ -121,6 +155,8 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
121
155
|
<style>
|
|
122
156
|
.profile-section {
|
|
123
157
|
&.my-profile {
|
|
158
|
+
--profile-section-picture-width: 32rem;
|
|
159
|
+
|
|
124
160
|
.profile-section-header {
|
|
125
161
|
margin-block-end: 3.2rem;
|
|
126
162
|
}
|
|
@@ -131,6 +167,8 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
131
167
|
|
|
132
168
|
## Notes
|
|
133
169
|
|
|
134
|
-
- `NuxtImg` is used for the profile picture — provide a real `src` path. A
|
|
135
|
-
-
|
|
170
|
+
- `NuxtImg` is used for the profile picture — provide a real `src` path. A broken image shows its alt text inside the 3:4 frame.
|
|
171
|
+
- Pass `width`/`height` in `profilePicture` when your image isn't 3:4 at 828px wide, so IPX/Vercel request the right size.
|
|
172
|
+
- The `With Social Icons` story shows a `SocialIconsList` in `#profileLinks`; size it with `--social-icons-list-icon-size` and `--social-icons-list-gap` on your own wrapper.
|
|
173
|
+
- The `StressTest` story covers hostile content (broken image, unbroken text, RTL, out-of-order slots, many links) at every canvas width.
|
|
136
174
|
- Component is auto-imported in Nuxt — no import needed.
|
|
@@ -57,6 +57,8 @@ import type { ISocialIcon } from "srcdev-nuxt-components";
|
|
|
57
57
|
| YouTube | `logos:youtube-icon` | `https://www.youtube.com/@` |
|
|
58
58
|
| TikTok | `logos:tiktok-icon` | `https://www.tiktok.com/@` |
|
|
59
59
|
|
|
60
|
+
`logos:instagram-icon`, `logos:x` and other dark brand icons vanish on a dark background, and their colours can't be changed. On a dark background use a mono icon such as `mdi:instagram` (any set that draws in `currentColor`) and colour it with `--social-icons-list-icon-colour` (default `currentColor`). Installed mono Instagram icons: `mdi:instagram`, `bi:instagram`, `lucide:instagram`, `akar-icons:instagram-fill`, `radix-icons:instagram-logo`.
|
|
61
|
+
|
|
60
62
|
---
|
|
61
63
|
|
|
62
64
|
## Usage example
|
package/.claude/skills/index.md
CHANGED
|
@@ -7,10 +7,16 @@ Step-by-step guides for repeatable development tasks in this project.
|
|
|
7
7
|
Copy skills into your project with:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components
|
|
10
|
+
rm -rf .claude/skills/srcdev-nuxt-components && mkdir -p .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Skills land in `.claude/skills/srcdev-nuxt-components/` — safe to re-run
|
|
13
|
+
Skills land in `.claude/skills/srcdev-nuxt-components/` — safe to re-run: it replaces only that folder, so skills dropped or renamed upstream are removed and your own skills are never touched.
|
|
14
|
+
|
|
15
|
+
The layer also ships a curated set of Claude Code rules in `.claude/rules/consumer/`. `npm run setup:claude` (see `setup-postinstall.md`) copies skills and rules together; to copy the rules by hand:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
rm -rf .claude/rules/srcdev-nuxt-components && mkdir -p .claude/rules/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/rules/consumer/. .claude/rules/srcdev-nuxt-components
|
|
19
|
+
```
|
|
14
20
|
|
|
15
21
|
## Structure
|
|
16
22
|
|
|
@@ -32,7 +38,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
32
38
|
├── theming-override-default.md — replace the entire default theme with a custom palette (set --theme-hue/--theme-chroma)
|
|
33
39
|
├── theming-partial-override.md — override a specific colour token category (palette, buttons, inputs) without a full theme replacement
|
|
34
40
|
├── theming-form-geometry-tokens.md — non-colour form/button/input tokens (padding, border-radius, gaps): full inventory, why partial override doesn't need duplicating the source files
|
|
35
|
-
├── theming-component-token-pattern.md — standard shape for overridable component colours: public token + inline fallback, no bare var()/no private-only indirection; the three tiers (theme slots / cross-component families / component-scoped); line-clamp tokens for overflowing text; rollout status
|
|
41
|
+
├── theming-component-token-pattern.md — standard shape for overridable component colours: public token + inline fallback, no bare var()/no private-only indirection; the three tiers (theme slots / cross-component families / component-scoped); line-clamp tokens for overflowing text; image-overlay scrim tokens; rollout status
|
|
36
42
|
├── theming-status-tokens.md — global --status-* colours (success/warning/danger/info/neutral: solid + surface + text), which components read them, how to override once for the whole library
|
|
37
43
|
├── theming-dark-mode.md — add a dark scheme in a consumer app (layer is light-only): enable plumbing, full dark token set without light-dark(), light-dark() alternative, light-only pinning
|
|
38
44
|
├── colour-scheme-disable.md — disable light/dark scheme support in a consumer app
|
|
@@ -97,7 +103,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
97
103
|
├── expanding-panel.md — ExpandingPanel v-model, forceOpened, contentIsOnTop overlay mode, slots (summary/icon/content), ARIA wiring, CSS token API
|
|
98
104
|
├── content-docs.md — ContentDocs docs-page shell: prop-driven docsNav/docsPageNav (not slots), DocsNavItem icons, container-width breakpoint behaviour, shared/per-side CSS token API
|
|
99
105
|
├── glass-panel.md — GlassPanel: frosted-glass container, light-only token defaults in-component (light-dark() block removed from _default.css, no built-in dark glass), highlight behind content, section/article labelled via headingId slot prop, CSS token API
|
|
100
|
-
├── pricing-card.md — PricingCard:
|
|
106
|
+
├── pricing-card.md — PricingCard: plan card with highlight badge, corner ribbon, feature list, #cta slot, heading-tag, line-clamp tokens
|
|
101
107
|
├── price-list.md — PriceList: service/menu price columns (dl rows), PriceListData type, headingTag/fromLabel props, CSS token API
|
|
102
108
|
├── opening-hours.md — OpeningHours: per-day data auto-grouped into ranges, split sessions, 24h/appointment, exceptions, Intl formatting, today highlight, JSON-LD, CSS token API
|
|
103
109
|
├── input-copy.md — InputCopy: readonly copy-to-clipboard input, visual feedback, Clipboard API, accessibility, CSS token API
|
|
@@ -108,12 +114,13 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
108
114
|
├── wipe-away-vertical.md — WipeAwayVertical: scroll-driven vertical wipe-away effect, pure-CSS grid overlay (no JS), indexed dynamic slots, sticky-centering-via-calc gotcha, CSS tokens
|
|
109
115
|
├── marquee-scroller.md — MarqueeScroller: infinite logo/badge scroller, per-item dynamic slots (marqueeData id), hover/focus/keyboard pause, reduced-motion, CSS tokens
|
|
110
116
|
├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
|
|
111
|
-
├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names, CSS tokens
|
|
117
|
+
├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names (dark ones vanish on dark backgrounds; mono alternatives), CSS tokens incl. --social-icons-list-icon-colour
|
|
112
118
|
├── display-qr-code.md — DisplayQrCode: QR code SVG from a string value, colour/size/variant/radius props, currentColor default
|
|
113
119
|
├── capture-qr-code.md — CaptureQrCode: live camera scanner, error state, visibility/route/KeepAlive lifecycle, media stream cleanup
|
|
114
120
|
├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
|
|
115
121
|
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-gap/min-col-size-{small,default,large} tokens, is-responsive, semantic tag + aria
|
|
116
|
-
├── display-avatar.md — DisplayAvatar (02.molecules): circular avatar with image/initials fallback (alt announced), size scale tokens, --display-avatar-* colours, chip badge, icon slot
|
|
122
|
+
├── display-avatar.md — DisplayAvatar (02.molecules): circular avatar with image/initials fallback (alt announced), broken-image fallback, data-size scale tokens, image-size, --display-avatar-* colours, chip badge, icon slot
|
|
123
|
+
├── profile-section.md — ProfileSection (02.molecules): picture + numbered profile-info-N slots, eyebrow/hero header (above, or over the picture top/bottom on a scrim via header-placement), profileLinks, container-query two-column layout, .location/.services highlight tokens
|
|
117
124
|
├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping TripleToggleSwitch, wired to useSettingsStore, labels/icons props, small sizing variant
|
|
118
125
|
├── card-core.md — CardCore: generic card container, dynamic named slots as rows, 4 variants, blurred backdrop layer, full CSS token API
|
|
119
126
|
├── action-menu.md — ActionMenu + ActionMenuItem: ellipsis trigger + anchored popover menu, indexed item-{n} slots, link/button items, full CSS token API
|
|
@@ -44,7 +44,7 @@ to inform the CLAUDE.md and CSS tokens.
|
|
|
44
44
|
"dev": "nuxt dev",
|
|
45
45
|
"generate": "nuxt generate",
|
|
46
46
|
"preview": "nuxt preview",
|
|
47
|
-
"setup:claude": "mkdir -p .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components",
|
|
47
|
+
"setup:claude": "rm -rf .claude/skills/srcdev-nuxt-components .claude/rules/srcdev-nuxt-components && mkdir -p .claude/skills/srcdev-nuxt-components .claude/rules/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/rules/consumer/. .claude/rules/srcdev-nuxt-components",
|
|
48
48
|
"postinstall": "NUXT_STANDALONE=true nuxt prepare && npm run setup:claude",
|
|
49
49
|
"lint": "eslint .",
|
|
50
50
|
"lint:fix": "eslint . --fix"
|
|
@@ -71,7 +71,9 @@ to inform the CLAUDE.md and CSS tokens.
|
|
|
71
71
|
|
|
72
72
|
> **`setup:claude` script**: `mkdir -p` is required — the destination folder won't exist on a
|
|
73
73
|
> fresh clone. The trailing `/.` on the source path copies contents into the destination rather
|
|
74
|
-
> than nesting a `skills` subfolder inside it.
|
|
74
|
+
> than nesting a `skills` subfolder inside it. The leading `rm -rf` clears only the two
|
|
75
|
+
> `srcdev-nuxt-components` folders, so skills and rules dropped or renamed upstream don't linger
|
|
76
|
+
> (a stale rule stays loaded); the app's own `.claude/skills/` and `.claude/rules/` are untouched.
|
|
75
77
|
|
|
76
78
|
### 3. Create `nuxt.config.ts`
|
|
77
79
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Prevent common "forgot to run after install" problems in a consuming app:
|
|
6
6
|
|
|
7
7
|
1. `nuxt prepare` — generates Nuxt type declarations. Skipping it causes TypeScript errors after install or package updates.
|
|
8
|
-
2. `npm run setup:claude` — copies the latest `srcdev-nuxt-components` skills into `.claude/skills/srcdev-nuxt-components/`. Skipping it leaves Claude working from stale skill docs after a package update.
|
|
8
|
+
2. `npm run setup:claude` — copies the latest `srcdev-nuxt-components` skills into `.claude/skills/srcdev-nuxt-components/` and its consumer rules into `.claude/rules/srcdev-nuxt-components/`. Skipping it leaves Claude working from stale skill docs and rules after a package update.
|
|
9
9
|
|
|
10
10
|
Additionally, `srcdev-nuxt-components` automatically copies VSCode snippet files (`.code-snippets`) to your `.vscode/` folder during its own postinstall, so components' code snippets are available immediately.
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ A consumer app's `postinstall` script runs both automatically after every `npm i
|
|
|
17
17
|
|
|
18
18
|
```json
|
|
19
19
|
"scripts": {
|
|
20
|
-
"setup:claude": "mkdir -p .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components",
|
|
20
|
+
"setup:claude": "rm -rf .claude/skills/srcdev-nuxt-components .claude/rules/srcdev-nuxt-components && mkdir -p .claude/skills/srcdev-nuxt-components .claude/rules/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/rules/consumer/. .claude/rules/srcdev-nuxt-components",
|
|
21
21
|
"postinstall": "node node_modules/srcdev-nuxt-components/scripts/copy-snippets.mjs && nuxt prepare && npm run setup:claude"
|
|
22
22
|
}
|
|
23
23
|
```
|
|
@@ -26,7 +26,7 @@ This postinstall hook does three things in order:
|
|
|
26
26
|
|
|
27
27
|
1. Copies VSCode snippets from the layer to your `.vscode/` folder
|
|
28
28
|
2. Runs `nuxt prepare` to generate type declarations
|
|
29
|
-
3. Runs `setup:claude` to copy skills into `.claude/skills/srcdev-nuxt-components/`
|
|
29
|
+
3. Runs `setup:claude` to copy skills into `.claude/skills/srcdev-nuxt-components/` and rules into `.claude/rules/srcdev-nuxt-components/`
|
|
30
30
|
|
|
31
31
|
### 2. Check whether your app needs an env flag for nuxt prepare
|
|
32
32
|
|
|
@@ -49,6 +49,7 @@ From this point on, `npm install` and `npm ci` trigger both steps automatically.
|
|
|
49
49
|
## Notes
|
|
50
50
|
|
|
51
51
|
- **Snippet copy must run in consumer app**: npm doesn't invoke postinstall hooks for dependencies. Each consumer app must explicitly call `scripts/copy-snippets.mjs` in its own postinstall to copy VSCode snippets to `.vscode/`. The layer's postinstall is used when the layer runs standalone, but consumers are responsible for their own `.vscode/` folder.
|
|
52
|
-
- Skills land in `.claude/skills/srcdev-nuxt-components/` — safe to re-run
|
|
52
|
+
- Skills land in `.claude/skills/srcdev-nuxt-components/` and rules in `.claude/rules/srcdev-nuxt-components/` — safe to re-run. The script deletes and recreates only those two folders, so anything dropped or renamed upstream is removed (a stale rule would otherwise stay loaded forever); your own project's skills and rules are never touched.
|
|
53
|
+
- Only the layer's `.claude/rules/consumer/` set ships. The rest of its rules govern the library's own source (naming, ledger, demo pages) and would be wrong in an app.
|
|
53
54
|
- `postinstall` also fires on `npm ci`, so CI environments get the skills and snippets too if they have a `.claude/` directory in the project.
|
|
54
55
|
- If you do not want `postinstall` running in CI, guard it: `"postinstall": "[ \"$CI\" = \"true\" ] || (node node_modules/srcdev-nuxt-components/scripts/copy-snippets.mjs && nuxt prepare && npm run setup:claude)"`.
|
|
@@ -268,6 +268,8 @@ export const Default: Story = {
|
|
|
268
268
|
Key points:
|
|
269
269
|
|
|
270
270
|
- `computed` is **not** auto-imported in `.ts` story files — import it explicitly from `"vue"`.
|
|
271
|
+
- Components aren't auto-registered in a story's `template` string either. Every component used there, including ones you put in the component's slots (`EyebrowText`, `HeroText`, `SocialIconsList`, ...), must be imported and listed in `components: {}`. Otherwise Vue renders it as an empty unknown element (`<eyebrowtext>`) with no error. ProfileSection's header and social-icons stories rendered blank this way until 2026-10-14.
|
|
272
|
+
- Props in a story's `template` string aren't type-checked either. An invalid value renders silently with no warning: ProfileSection's stories passed `font-size="large"` to `HeroText`, which only has `display`/`title`/`heading`/`subheading`/`label`, so the heading fell back to unstyled text. Check every literal prop value against the component's `interface Props`.
|
|
271
273
|
- Extra args must be stripped before `v-bind` — spreading unknown keys onto a component makes them unknown HTML attributes.
|
|
272
274
|
- CSS custom properties set via `:style` on the component root are picked up by `var()` in the component's scoped CSS.
|
|
273
275
|
|
|
@@ -285,6 +287,29 @@ prop and try to pass it. Make it unmistakable:
|
|
|
285
287
|
|
|
286
288
|
`PriceList.stories.ts` and `GoogleReviews.stories.ts` (line-clamp controls) follow this.
|
|
287
289
|
|
|
290
|
+
## Docs page
|
|
291
|
+
|
|
292
|
+
`.storybook/preview.ts` sets `tags: ["autodocs"]` globally (2026-10-14), so every story file gets a
|
|
293
|
+
generated Docs page and the meta doesn't need the tag. Before that, only two files opted in, and
|
|
294
|
+
the earlier nightly `@nuxtjs/storybook` didn't render Docs pages at all, so a lot of existing
|
|
295
|
+
`docs.description` text had never been visible. Check the page whenever you add or change a story
|
|
296
|
+
(compliance checklist item 6c):
|
|
297
|
+
|
|
298
|
+
- `parameters.docs.description.component` in the meta: what the component is for and its main
|
|
299
|
+
props, slots and events. `docs.description.story` on a story: what that story shows.
|
|
300
|
+
- The Docs page renders every story inline in one document. Overlays (`position: fixed`), dialogs,
|
|
301
|
+
toasts, fullscreen canvases and scroll-driven effects break or overlap there. Render them in
|
|
302
|
+
their own iframe instead:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
parameters: {
|
|
306
|
+
docs: { story: { inline: false, height: "480px" } },
|
|
307
|
+
},
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
- Opt a file out with `tags: ["!autodocs"]` only when it isn't a component reference (tooling, or
|
|
311
|
+
a file with its own attached `.mdx` page, like `ColourRamps.stories.ts` with `SetupGuide.mdx`).
|
|
312
|
+
|
|
288
313
|
## Link to the stories of components that use this one
|
|
289
314
|
|
|
290
315
|
When the component you're working on is a building block of other components (it's rendered
|
|
@@ -360,6 +385,19 @@ story: "See the [CookieConsentBanner](?path=/story/molecules-cookieconsentbanner
|
|
|
360
385
|
controls that make sense (hide irrelevant ones with `Story.argTypes = { x: { table: { disable: true } } }`),
|
|
361
386
|
or, for a fixed showcase (comparisons, all-themes grids, composition examples), add
|
|
362
387
|
`controls: { disable: true }` to that story's `parameters`.
|
|
388
|
+
- **Sidebar order** comes from the `storySort` function in `.storybook/preview.ts`: top-level
|
|
389
|
+
groups in tier order, everything below them alphabetical, entries within one component in file
|
|
390
|
+
order with Docs first. Storybook doesn't run that function from the compiled file. It extracts
|
|
391
|
+
the function's source and `eval`s it as plain JavaScript, so any TypeScript inside it
|
|
392
|
+
(annotations, `as` casts) or a reference to something outside its body fails the whole preview
|
|
393
|
+
build with `SyntaxError: Unexpected token ':'` (found 2026-10-15). It stays annotation-free
|
|
394
|
+
under a `// @ts-expect-error`. Check an edit without starting Storybook:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
node --input-type=module -e 'import { getStorySortParameter } from "storybook/internal/csf-tools"; import fs from "node:fs"; console.log(typeof getStorySortParameter(fs.readFileSync(".storybook/preview.ts", "utf8")));'
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`function` means it parses; a syntax error is the same one Storybook would hit.
|
|
363
401
|
|
|
364
402
|
## Scroll/animation-driven effects need surrounding chrome, not just the bare component
|
|
365
403
|
|