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
|
@@ -42,6 +42,52 @@ overrides (2026-08-25):
|
|
|
42
42
|
Tier 1 is the one deliberate exception. Everything else gets a real component-prefixed name with
|
|
43
43
|
an inline fallback, even if that fallback happens to be a Tier 1 or Tier 2 token underneath.
|
|
44
44
|
|
|
45
|
+
## Public vs private tokens
|
|
46
|
+
|
|
47
|
+
Rule: `.claude/rules/css-public-private-tokens.md`.
|
|
48
|
+
|
|
49
|
+
The public/private decision is about **consumer relevance, not reuse
|
|
50
|
+
count**. A value a consumer could plausibly want to customise — colours, spacing, sizes, radii,
|
|
51
|
+
durations, and any per-state variant of these (a hover outline width, a focus border colour) — is
|
|
52
|
+
a **public token**, consumed directly at its point of use: `border-radius: var(--component-name-border-radius, 0.5rem)`.
|
|
53
|
+
This holds even when it's only used once — single-use is not a reason to demote a genuinely
|
|
54
|
+
consumer-facing value to private. Per-state variants get their own public token that falls back
|
|
55
|
+
to the resting one, rather than being derived privately: `--component-name-outline-width-hover`
|
|
56
|
+
falling back to `--component-name-outline-width`, not a private var with no override hook.
|
|
57
|
+
|
|
58
|
+
A value with **no plausible consumer relevance** — genuine internal implementation detail, e.g.
|
|
59
|
+
something composed from a `v-bind()` JS value, or an intermediate that exists purely to avoid
|
|
60
|
+
repeating a computation — is a **private token**, always prefixed `--_x`. Prefix it even when
|
|
61
|
+
it's used only once: the underscore is a grep-able, at-a-glance "this is not public API" signal,
|
|
62
|
+
and that signal shouldn't depend on how many places the value happens to appear.
|
|
63
|
+
|
|
64
|
+
```css
|
|
65
|
+
/* ✅ Single-use, consumer-facing — still a public token, consumed directly */
|
|
66
|
+
.component-name {
|
|
67
|
+
border-radius: var(--component-name-border-radius, 0.5rem);
|
|
68
|
+
transition: all var(--component-name-transition-duration, 300ms) ease;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ✅ Resting/hover pair — both public, hover falls back to resting; --_ only wraps the
|
|
72
|
+
genuinely-shared intermediate so the fallback chain isn't repeated */
|
|
73
|
+
.component-name {
|
|
74
|
+
--_border-colour: var(--component-name-border-colour, transparent);
|
|
75
|
+
border-color: var(--_border-colour);
|
|
76
|
+
|
|
77
|
+
&:hover {
|
|
78
|
+
border-color: var(--component-name-border-colour-hover, var(--_border-colour));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ✅ Private — internal only, no consumer would ever want to override this directly */
|
|
83
|
+
.component-name {
|
|
84
|
+
--_computed-offset: calc(v-bind(dynamicWidth) * 0.5px);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
CLAUDE.md pitfall #20 is the concrete case (`ServicesCard`'s private layer wrapped public tokens with
|
|
89
|
+
no composition or state-swap behind them: pure pass-through duplication, not genuine privacy).
|
|
90
|
+
|
|
45
91
|
## Naming convention
|
|
46
92
|
|
|
47
93
|
`--{component-kebab-name}-{variant-if-any}-{property}[-state]`
|
|
@@ -83,6 +129,54 @@ Prefer this over a prop: line count is presentation, and CSS handles it without
|
|
|
83
129
|
layout around it.
|
|
84
130
|
- Document it in `CONSUMER-STYLING.md` with its default and the `1`/`none` options.
|
|
85
131
|
|
|
132
|
+
## Text over an image: scrim tokens
|
|
133
|
+
|
|
134
|
+
Any component that puts text over a photo (a header in a picture, a card title on its image) gets
|
|
135
|
+
the same scrim token set, so a consumer can match the scrim to their photos. `ProfileSection`'s
|
|
136
|
+
`header-placement="picture-top|picture-bottom"` is the reference; `ServicesCard`'s
|
|
137
|
+
`titles-within-image-wrapper` has the older subset (colour and whole-background override only) and
|
|
138
|
+
should gain the rest when it's next touched.
|
|
139
|
+
|
|
140
|
+
| Token | Default | Purpose |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `--{c}-scrim-colour` | `#000` | Colour the scrim starts from at the image's edge |
|
|
143
|
+
| `--{c}-scrim-opacity` | `75%` | Its opacity at the start |
|
|
144
|
+
| `--{c}-scrim-solid-stop` | `0%` | How far it stays full strength before fading, so the text sits on a clean background |
|
|
145
|
+
| `--{c}-scrim-target-colour` | `transparent` | What it fades into: set to roughly the photo's colour where the fade ends, so there's no grey band |
|
|
146
|
+
| `--{c}-scrim` | the gradient | Whole `background` override; `none` removes it |
|
|
147
|
+
| `--{c}-text-colour` | `contrast-color(<scrim colour>)`, `#fff` without support | Text on the scrim |
|
|
148
|
+
|
|
149
|
+
```css
|
|
150
|
+
.component-header {
|
|
151
|
+
--_scrim-colour: var(--component-scrim-colour, #000);
|
|
152
|
+
--_scrim-start-colour: color-mix(in oklab, var(--_scrim-colour) var(--component-scrim-opacity, 75%), transparent);
|
|
153
|
+
--_scrim-solid-stop: var(--component-scrim-solid-stop, 0%);
|
|
154
|
+
--_scrim-target-colour: var(--component-scrim-target-colour, transparent);
|
|
155
|
+
|
|
156
|
+
color: var(--component-text-colour, #fff);
|
|
157
|
+
background: var(--component-scrim, linear-gradient(to top, rgb(0 0 0 / 0.75), rgb(0 0 0 / 0.75) var(--_scrim-solid-stop), var(--_scrim-target-colour)));
|
|
158
|
+
|
|
159
|
+
@supports (color: color-mix(in oklab, red, transparent)) {
|
|
160
|
+
background: var(--component-scrim, linear-gradient(to top, var(--_scrim-start-colour), var(--_scrim-start-colour) var(--_scrim-solid-stop), var(--_scrim-target-colour)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@supports (color: contrast-color(red)) {
|
|
164
|
+
color: var(--component-text-colour, contrast-color(var(--_scrim-colour)));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- The `@supports` fallback is for Safari before 16.2 (no `color-mix()`): it starts from 75% black
|
|
170
|
+
whatever the scrim colour, but keeps the solid stop and target colour.
|
|
171
|
+
- `contrast-color()` reads the scrim colour, not the photo. Document that a consumer who removes the
|
|
172
|
+
scrim must set the text colour.
|
|
173
|
+
- Round only the corners on the image's edge (`border-end-*-radius: inherit` for a bottom scrim),
|
|
174
|
+
or an opaque target colour shows rounded inner corners.
|
|
175
|
+
- If the text can be long, don't clip it: let the frame grow (absolutely position the image and
|
|
176
|
+
keep `overflow` visible so `aspect-ratio` acts as a minimum).
|
|
177
|
+
- Give the story a light and a dark theme built only from these tokens, each with a photo whose
|
|
178
|
+
edge suits the scrim.
|
|
179
|
+
|
|
86
180
|
## Multi-level fallback chains are fine
|
|
87
181
|
|
|
88
182
|
A component that wants to *look like* another component's default (rather than duplicating a
|
|
@@ -47,7 +47,7 @@ affects buttons, inputs, prompts, and toasts simultaneously:
|
|
|
47
47
|
| `--theme-border-focus` | `--colour-theme-4` / `3` | Focused border |
|
|
48
48
|
| `--theme-ring` | `--colour-theme-1` / `9` | Focus ring (outline) |
|
|
49
49
|
| `--theme-on-surface` | `--colour-theme-0` | Text/icon on filled/inverted surface (fixed light value in both modes — pair this with any dark-in-light-mode surface, not `--theme-text`) |
|
|
50
|
-
| `--theme-text` | `--colour-theme-9` / `2` | Text on page, outline element text (assumes a *light* surface — don't pair with `--theme-surface-hover`/`-inverted`, see pitfall #13 in `
|
|
50
|
+
| `--theme-text` | `--colour-theme-9` / `2` | Text on page, outline element text (assumes a *light* surface — don't pair with `--theme-surface-hover`/`-inverted`, see pitfall #13 in `CLAUDE.md`) |
|
|
51
51
|
| `--theme-text-inverted` | `--colour-theme-0` / `7` | Text on `--theme-surface-inverted` (fixed to a light value in light mode as of 2026-08-03 — was `--colour-theme-9`, identical to the surface it sits on) |
|
|
52
52
|
|
|
53
53
|
Input-specific tokens:
|
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
"<DisplayAvatar src=\"${1:/images/profile.jpg}\" alt=\"${2:Jane Smith}\" size=\"${3|md,xs,s,lg,xl|}\" />"
|
|
7
7
|
]
|
|
8
8
|
},
|
|
9
|
+
"SRCDEV DisplayAvatar Large Image": {
|
|
10
|
+
"description": "DisplayAvatar: image avatar resized beyond the scale, with a matching image-size so it stays sharp",
|
|
11
|
+
"scope": "vue,html",
|
|
12
|
+
"body": [
|
|
13
|
+
"<DisplayAvatar src=\"${1:/images/profile.jpg}\" alt=\"${2:Jane Smith}\" :image-size=\"${3:240}\" style=\"--display-avatar-size: ${4:12rem}\" />"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
9
16
|
"SRCDEV DisplayAvatar Initials": {
|
|
10
17
|
"description": "DisplayAvatar: initials derived from alt (alt is announced, initials hidden from screen readers)",
|
|
11
18
|
"scope": "vue,html",
|
|
@@ -4,97 +4,65 @@
|
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
6
|
"<PricingCard",
|
|
7
|
-
"
|
|
7
|
+
" plan-name=\"$1Single Site\"",
|
|
8
8
|
" :price=\"$2199\"",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
" '$7Feature 3',",
|
|
15
|
-
" ]\"",
|
|
16
|
-
" @select=\"$8handleSelect\"",
|
|
9
|
+
" currency-symbol=\"$3£\"",
|
|
10
|
+
" billing-period=\"$4one-time\"",
|
|
11
|
+
" description=\"$5Perfect for freelancers and solo practitioners.\"",
|
|
12
|
+
" :features=\"['$6Feature 1', '$7Feature 2', '$8Feature 3']\"",
|
|
13
|
+
" @select=\"$9handleSelect\"",
|
|
17
14
|
"/>"
|
|
18
15
|
]
|
|
19
16
|
},
|
|
20
17
|
"SRCDEV PricingCard Highlighted": {
|
|
21
|
-
"description": "PricingCard with 'Most Popular' highlight
|
|
18
|
+
"description": "PricingCard with 'Most Popular' highlight",
|
|
22
19
|
"scope": "vue,html",
|
|
23
20
|
"body": [
|
|
24
21
|
"<PricingCard",
|
|
25
|
-
"
|
|
22
|
+
" plan-name=\"$1Agency\"",
|
|
26
23
|
" :price=\"$2499\"",
|
|
27
|
-
"
|
|
24
|
+
" billing-period=\"$3one-time\"",
|
|
28
25
|
" description=\"$4Best for growing teams.\"",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
" ]\"",
|
|
35
|
-
" @select=\"$8handleSelect\"",
|
|
36
|
-
">",
|
|
37
|
-
" <template #cta=\"{ ctaText, isDisabled, onSelect }\">",
|
|
38
|
-
" <InputButton",
|
|
39
|
-
" :button-text=\"ctaText\"",
|
|
40
|
-
" variant=\"$9primary\"",
|
|
41
|
-
" :readonly=\"isDisabled\"",
|
|
42
|
-
" @click=\"onSelect\"",
|
|
43
|
-
" />",
|
|
44
|
-
" </template>",
|
|
45
|
-
"</PricingCard>"
|
|
26
|
+
" is-highlighted",
|
|
27
|
+
" badge-text=\"$5Most Popular\"",
|
|
28
|
+
" :features=\"['$6Feature 1', '$7Feature 2', '$8Feature 3']\"",
|
|
29
|
+
" @select=\"$9handleSelect\"",
|
|
30
|
+
"/>"
|
|
46
31
|
]
|
|
47
32
|
},
|
|
48
33
|
"SRCDEV PricingCard Grid": {
|
|
49
|
-
"description": "
|
|
34
|
+
"description": "Pricing comparison grid from a plans array",
|
|
50
35
|
"scope": "vue,html",
|
|
51
36
|
"body": [
|
|
52
|
-
"<div class=\"
|
|
53
|
-
" <PricingCard",
|
|
54
|
-
" planName=\"Single Site\"",
|
|
55
|
-
" :price=\"$1199\"",
|
|
56
|
-
" billingPeriod=\"one-time\"",
|
|
57
|
-
" description=\"$2For freelancers.\"",
|
|
58
|
-
" :features=\"['$3Feature 1', '$4Feature 2']\"",
|
|
59
|
-
" @select=\"handleSelect\"",
|
|
60
|
-
" />",
|
|
61
|
-
" <PricingCard",
|
|
62
|
-
" planName=\"Agency\"",
|
|
63
|
-
" :price=\"$5499\"",
|
|
64
|
-
" billingPeriod=\"one-time\"",
|
|
65
|
-
" description=\"$6For growing teams.\"",
|
|
66
|
-
" :is-highlighted=\"true\"",
|
|
67
|
-
" :features=\"['$7Feature 1', '$8Feature 2', '$9Feature 3']\"",
|
|
68
|
-
" @select=\"handleSelect\"",
|
|
69
|
-
" />",
|
|
37
|
+
"<div class=\"$1pricing-grid\">",
|
|
70
38
|
" <PricingCard",
|
|
71
|
-
"
|
|
72
|
-
" :
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
" :
|
|
76
|
-
"
|
|
39
|
+
" v-for=\"plan in $2plans\"",
|
|
40
|
+
" :key=\"plan.id\"",
|
|
41
|
+
" :plan-name=\"plan.name\"",
|
|
42
|
+
" :price=\"plan.price\"",
|
|
43
|
+
" :description=\"plan.description\"",
|
|
44
|
+
" :features=\"plan.features\"",
|
|
45
|
+
" :is-highlighted=\"plan.id === '$3pro'\"",
|
|
46
|
+
" heading-tag=\"$4h2\"",
|
|
47
|
+
" @select=\"$5handleSelect\"",
|
|
77
48
|
" />",
|
|
78
49
|
"</div>"
|
|
79
50
|
]
|
|
80
51
|
},
|
|
81
52
|
"SRCDEV PricingCard Custom Button": {
|
|
82
|
-
"description": "PricingCard with custom button
|
|
53
|
+
"description": "PricingCard with custom button via #cta slot",
|
|
83
54
|
"scope": "vue,html",
|
|
84
55
|
"body": [
|
|
85
56
|
"<PricingCard",
|
|
86
|
-
"
|
|
57
|
+
" plan-name=\"$1Premium\"",
|
|
87
58
|
" :price=\"$2799\"",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
" :features=\"['$5Feature 1', '$6Feature 2']\"",
|
|
91
|
-
" @select=\"$7handleSelect\"",
|
|
59
|
+
" :features=\"['$3Feature 1', '$4Feature 2']\"",
|
|
60
|
+
" @select=\"$5handleSelect\"",
|
|
92
61
|
">",
|
|
93
62
|
" <template #cta=\"{ ctaText, isDisabled, onSelect }\">",
|
|
94
63
|
" <InputButton",
|
|
95
64
|
" :button-text=\"ctaText\"",
|
|
96
|
-
" variant=\"$
|
|
97
|
-
" theme=\"$9default\"",
|
|
65
|
+
" variant=\"$6secondary\"",
|
|
98
66
|
" :readonly=\"isDisabled\"",
|
|
99
67
|
" @click=\"onSelect\"",
|
|
100
68
|
" />",
|
|
@@ -107,17 +75,12 @@
|
|
|
107
75
|
"scope": "vue,html",
|
|
108
76
|
"body": [
|
|
109
77
|
"<PricingCard",
|
|
110
|
-
"
|
|
78
|
+
" plan-name=\"$1Annual\"",
|
|
111
79
|
" :price=\"$249\"",
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
" '$6Feature 1',",
|
|
117
|
-
" '$7Feature 2',",
|
|
118
|
-
" '$8Feature 3',",
|
|
119
|
-
" ]\"",
|
|
120
|
-
" @select=\"$9handleSelect\"",
|
|
80
|
+
" billing-period=\"$3per year\"",
|
|
81
|
+
" ribbon-text=\"$4EARLYBIRD50 · 50% off\"",
|
|
82
|
+
" :features=\"['$5Feature 1', '$6Feature 2']\"",
|
|
83
|
+
" @select=\"$7handleSelect\"",
|
|
121
84
|
"/>"
|
|
122
85
|
]
|
|
123
86
|
},
|
|
@@ -127,21 +90,20 @@
|
|
|
127
90
|
"body": [
|
|
128
91
|
".$1pricing-page {",
|
|
129
92
|
" .pricing-card {",
|
|
130
|
-
" --pricing-card-
|
|
131
|
-
" --pricing-card-
|
|
132
|
-
" --pricing-card-
|
|
133
|
-
" --pricing-card-
|
|
93
|
+
" --pricing-card-background: $2;",
|
|
94
|
+
" --pricing-card-border-radius: $3;",
|
|
95
|
+
" --pricing-card-padding: $4;",
|
|
96
|
+
" --pricing-card-gap: $5;",
|
|
134
97
|
" --pricing-card-name-color: $6;",
|
|
135
|
-
" --pricing-card-
|
|
136
|
-
" --pricing-card-
|
|
137
|
-
" --pricing-card-
|
|
138
|
-
" --
|
|
139
|
-
" --pricing-card-ribbon-text: $11;",
|
|
98
|
+
" --pricing-card-description-line-clamp: $7;",
|
|
99
|
+
" --pricing-card-feature-icon-color: $8;",
|
|
100
|
+
" --pricing-card-ribbon-bg: $9;",
|
|
101
|
+
" --input-button-primary-surface: $10;",
|
|
140
102
|
"",
|
|
141
103
|
" &.is-highlighted {",
|
|
142
|
-
" --pricing-card-highlight-border: $
|
|
143
|
-
" --pricing-card-highlight-
|
|
144
|
-
" --pricing-card-
|
|
104
|
+
" --pricing-card-highlight-border: $11;",
|
|
105
|
+
" --pricing-card-highlight-scale: $12;",
|
|
106
|
+
" --pricing-card-badge-bg: $13;",
|
|
145
107
|
" }",
|
|
146
108
|
" }",
|
|
147
109
|
"}"
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
"body": [
|
|
6
6
|
"<ProfileSection",
|
|
7
7
|
" tag=\"section\"",
|
|
8
|
-
" :profile-picture=\"{ src: '$
|
|
8
|
+
" :profile-picture=\"{ src: '$2', alt: '$3' }\"",
|
|
9
9
|
">",
|
|
10
10
|
" <template #eyebrowText>",
|
|
11
|
-
" <EyebrowText text-content=\"$
|
|
11
|
+
" <EyebrowText text-content=\"$4\" />",
|
|
12
12
|
" </template>",
|
|
13
13
|
" <template #heroText=\"{ headingId }\">",
|
|
14
|
-
" <HeroText :id=\"headingId\" tag=\"h2\" :text-content=\"[{ text: '$
|
|
14
|
+
" <HeroText :id=\"headingId\" tag=\"h2\" :text-content=\"[{ text: '$5' }]\" />",
|
|
15
15
|
" </template>",
|
|
16
16
|
" <template #profile-info-1>",
|
|
17
|
-
" <p>$
|
|
17
|
+
" <p>$6</p>",
|
|
18
18
|
" </template>",
|
|
19
19
|
" <template #profile-info-2>",
|
|
20
20
|
" <p>$6</p>",
|
|
@@ -22,6 +22,27 @@
|
|
|
22
22
|
"</ProfileSection>"
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
|
+
"SRCDEV ProfileSection Header In Picture": {
|
|
26
|
+
"description": "ProfileSection with the eyebrow and heading over the bottom or top of the picture, on a scrim",
|
|
27
|
+
"scope": "vue,html",
|
|
28
|
+
"body": [
|
|
29
|
+
"<ProfileSection",
|
|
30
|
+
" tag=\"section\"",
|
|
31
|
+
" header-placement=\"${1|picture-bottom,picture-top|}\"",
|
|
32
|
+
" :profile-picture=\"{ src: '$2', alt: '$3' }\"",
|
|
33
|
+
">",
|
|
34
|
+
" <template #eyebrowText>",
|
|
35
|
+
" <EyebrowText text-content=\"$4\" />",
|
|
36
|
+
" </template>",
|
|
37
|
+
" <template #heroText=\"{ headingId }\">",
|
|
38
|
+
" <HeroText :id=\"headingId\" tag=\"h2\" :text-content=\"[{ text: '$5' }]\" />",
|
|
39
|
+
" </template>",
|
|
40
|
+
" <template #profile-info-1>",
|
|
41
|
+
" <p>$6</p>",
|
|
42
|
+
" </template>",
|
|
43
|
+
"</ProfileSection>"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
25
46
|
"SRCDEV ProfileSection CSS Override": {
|
|
26
47
|
"description": "CSS override scaffold for ProfileSection tokens",
|
|
27
48
|
"scope": "css",
|
|
@@ -30,6 +51,8 @@
|
|
|
30
51
|
" --profile-section-picture-width: $2;",
|
|
31
52
|
" --profile-section-gap-wide: $3;",
|
|
32
53
|
" --profile-section-picture-border-radius: $4;",
|
|
54
|
+
" --profile-section-highlight-colour: $5;",
|
|
55
|
+
" --profile-section-link-colour: $6;",
|
|
33
56
|
"}"
|
|
34
57
|
]
|
|
35
58
|
}
|
package/README.md
CHANGED
|
@@ -25,12 +25,12 @@ defineNuxtConfig({
|
|
|
25
25
|
|
|
26
26
|
## Claude Code Skills
|
|
27
27
|
|
|
28
|
-
This package ships Claude Code skills
|
|
28
|
+
This package ships Claude Code skills (reference docs for components and development tasks) and a curated set of Claude Code rules (short constraints for working with the layer: CSS nesting, overrides, props, NuxtImg, Storybook args) in the `.claude/` directory.
|
|
29
29
|
|
|
30
30
|
To make them available in your project, add this script to your `package.json`:
|
|
31
31
|
|
|
32
32
|
```json
|
|
33
|
-
"setup:claude": "cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components"
|
|
33
|
+
"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"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Then run it after install:
|
|
@@ -39,7 +39,7 @@ Then run it after install:
|
|
|
39
39
|
npm run setup:claude
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Skills are copied into `.claude/skills/srcdev-nuxt-components/` so they never conflict with or overwrite
|
|
42
|
+
Skills are copied into `.claude/skills/srcdev-nuxt-components/` and rules into `.claude/rules/srcdev-nuxt-components/`, so they never conflict with or overwrite your own project's. Re-running the script after a package update is safe: it replaces only those two `srcdev-nuxt-components` folders, so skills and rules dropped or renamed upstream are removed too, and your own skills and rules are never touched.
|
|
43
43
|
|
|
44
44
|
### Automate with postinstall (recommended)
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ To ensure skills are always up to date, VSCode snippets are installed, and `nuxt
|
|
|
47
47
|
|
|
48
48
|
```json
|
|
49
49
|
"scripts": {
|
|
50
|
-
"setup:claude": "mkdir -p .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components",
|
|
50
|
+
"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",
|
|
51
51
|
"postinstall": "node node_modules/srcdev-nuxt-components/scripts/copy-snippets.mjs && nuxt prepare && npm run setup:claude"
|
|
52
52
|
}
|
|
53
53
|
```
|
|
@@ -56,7 +56,7 @@ To ensure skills are always up to date, VSCode snippets are installed, and `nuxt
|
|
|
56
56
|
|
|
57
57
|
1. `copy-snippets.mjs` — copies VSCode snippets (`.code-snippets` files) from the layer to your `.vscode/` folder
|
|
58
58
|
2. `nuxt prepare` — generates Nuxt type declarations
|
|
59
|
-
3. `setup:claude` — copies skills into `.claude/skills/srcdev-nuxt-components/`
|
|
59
|
+
3. `setup:claude` — copies skills into `.claude/skills/srcdev-nuxt-components/` and rules into `.claude/rules/srcdev-nuxt-components/`
|
|
60
60
|
|
|
61
61
|
> **Note**: The snippet copy must run from your consumer app's postinstall, not the layer's. npm doesn't invoke postinstall hooks for dependencies, so each consumer app is responsible for copying snippets into its own `.vscode/` folder. Don't set `SRCDEV_STANDALONE` in a consumer app: that flag is only for running this layer on its own (it enables dev-only modules and the ramp watcher).
|
|
62
62
|
|
package/app/components/01.atoms/animations/scroll-reveal-frame/stories/ScrollRevealFrame.stories.ts
CHANGED
|
@@ -25,7 +25,9 @@ const meta: Meta<typeof ScrollRevealFrame> = {
|
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
27
|
parameters: {
|
|
28
|
+
// Own iframe on the Docs page: the effect depends on page scroll.
|
|
28
29
|
docs: {
|
|
30
|
+
story: { inline: false, height: "600px" },
|
|
29
31
|
description: {
|
|
30
32
|
component:
|
|
31
33
|
"Generic clipping frame that pans any slot content vertically as it scrolls through the viewport — driven entirely by CSS Scroll-driven Animations. No scroll listeners, no JS. Use this when you need the parallax reveal with arbitrary content (grids, video, etc.). For a single NuxtImg with optimisation and focal-point control, use ScrollRevealImage instead.",
|
package/app/components/01.atoms/animations/scroll-reveal-image/stories/ScrollRevealImage.stories.ts
CHANGED
|
@@ -50,7 +50,9 @@ const meta: Meta<typeof ScrollRevealImage> = {
|
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
52
|
parameters: {
|
|
53
|
+
// Own iframe on the Docs page: the effect depends on page scroll.
|
|
53
54
|
docs: {
|
|
55
|
+
story: { inline: false, height: "600px" },
|
|
54
56
|
description: {
|
|
55
57
|
component:
|
|
56
58
|
"Single-image parallax reveal built on ScrollRevealFrame. Adds NuxtImg optimisation (src, alt, intrinsic dimensions) and a horizontal focal-point prop (focalX). For arbitrary slot content — grids, video, etc. — use ScrollRevealFrame directly.",
|
package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts
CHANGED
|
@@ -45,6 +45,10 @@ export default {
|
|
|
45
45
|
showOverlay: true,
|
|
46
46
|
overlayText: "A quiet moment.",
|
|
47
47
|
},
|
|
48
|
+
parameters: {
|
|
49
|
+
// Own iframe on the Docs page: the effect depends on page scroll and vh heights.
|
|
50
|
+
docs: { story: { inline: false, height: "600px" } },
|
|
51
|
+
},
|
|
48
52
|
} as Meta<StoryArgs>;
|
|
49
53
|
|
|
50
54
|
const Template: StoryFn<StoryArgs> = (args) => ({
|
package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts
CHANGED
|
@@ -20,7 +20,9 @@ const meta: Meta<typeof WipeAwayVertical> = {
|
|
|
20
20
|
styleClassPassthrough: { table: { disable: true } },
|
|
21
21
|
},
|
|
22
22
|
parameters: {
|
|
23
|
+
// Own iframe on the Docs page: the effect depends on page scroll and vh heights.
|
|
23
24
|
docs: {
|
|
25
|
+
story: { inline: false, height: "600px" },
|
|
24
26
|
description: {
|
|
25
27
|
component:
|
|
26
28
|
"Scroll-driven vertical wipe effect: each sticky panel wipes away to reveal the next as its paired scrolling section crosses the viewport. Pure CSS — a single-column grid overlays `.sticky-items-container` across all `.scrolling-section` rows via `animation-timeline: view()`, so there's no scroll listener or JS measurement involved. Consumers supply `stickyItem-{n}` slots for each of `itemCount` panels, and `scrollingItem-{n}` slots for only the first `itemCount - 1` — the last panel has nothing left to reveal, so it has no wipe animation or scrolling section of its own, just a small built-in trailing buffer to release cleanly. The sticky panel's own visible height is `--wipe-away-vertical-height` (default `100vh`) — leave the root's own `height` unset (`auto`) so it sizes itself from its children. Browsers without `animation-timeline: view()` support (via `@supports not (...)`) just get the sticky items stacked in normal document flow instead.",
|
package/app/components/02.molecules/cookie-consent-banner/stories/CookieConsentBanner.stories.ts
CHANGED
|
@@ -40,7 +40,9 @@ export default {
|
|
|
40
40
|
styleClassPassthrough: [],
|
|
41
41
|
},
|
|
42
42
|
parameters: {
|
|
43
|
+
// Own iframe on the Docs page: the banner is fixed to the viewport.
|
|
43
44
|
docs: {
|
|
45
|
+
story: { inline: false, height: "400px" },
|
|
44
46
|
description: {
|
|
45
47
|
// Drives itself from the real useCookieConsent()/useCookie() state, so
|
|
46
48
|
// once accepted/rejected in this browser it stays hidden across story
|
|
@@ -6,13 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
| Token | Default | Controls |
|
|
8
8
|
|---|---|---|
|
|
9
|
-
| `--display-avatar-size` | the size
|
|
10
|
-
| `--display-avatar-font-size` | the size
|
|
9
|
+
| `--display-avatar-size` | the `size` step's value | Diameter, overriding the `size` prop's scale (one instance or a whole area) |
|
|
10
|
+
| `--display-avatar-font-size` | the `size` step's value | Initials/text size, overriding the scale |
|
|
11
11
|
| `--display-avatar-size-xs` / `-s` / `-md` / `-lg` / `-xl` | `2.4rem` / `3.2rem` / `4rem` / `4.8rem` / `5.6rem` | The diameter each `size` value maps to |
|
|
12
12
|
| `--display-avatar-font-size-xs` / `-s` / `-md` / `-lg` / `-xl` | `1.2rem` / `1.4rem` / `1.6rem` / `1.8rem` / `2rem` | The text size each `size` value maps to |
|
|
13
13
|
|
|
14
|
-
A custom `size` string (anything other than `xs`–`xl`) is
|
|
15
|
-
step, so it renders at `md` unless you set `--display-avatar-size` for it
|
|
14
|
+
A custom `size` string (anything other than `xs`–`xl`) is set as `data-size` but maps to no
|
|
15
|
+
scale step, so it renders at `md` unless you set `--display-avatar-size` for it (target it with
|
|
16
|
+
`.display-avatar[data-size="<your-size>"]`).
|
|
17
|
+
|
|
18
|
+
If you size an image avatar beyond the scale, raise the `image-size` prop (default `112`, the
|
|
19
|
+
pixel width and height requested from `@nuxt/image`) to match, or the image is upscaled and blurry.
|
|
16
20
|
|
|
17
21
|
### Appearance
|
|
18
22
|
|
|
@@ -30,21 +34,33 @@ step, so it renders at `md` unless you set `--display-avatar-size` for it.
|
|
|
30
34
|
> faint in light mode; it now uses the `--theme-*` surface and text tokens, so it follows light
|
|
31
35
|
> and dark mode.
|
|
32
36
|
|
|
33
|
-
Private tokens (not public API): `--
|
|
37
|
+
Private tokens (not public API): `--_avatar-size` and `--_avatar-font-size`, which each size step sets from its
|
|
34
38
|
scale token.
|
|
35
39
|
|
|
36
40
|
## State hooks
|
|
37
41
|
|
|
38
|
-
-
|
|
39
|
-
- Classes: `.display-avatar` (root), `.display-avatar-
|
|
40
|
-
|
|
42
|
+
- `data-size` on the root: `xs`, `s`, `md`, `lg`, `xl` (or your custom `size` string).
|
|
43
|
+
- Classes: `.display-avatar` (root: size, font size, radius), `.display-avatar-surface` (fills the root,
|
|
44
|
+
carries the background and text colour, and holds all content), `.display-avatar-image` (the image), `.display-avatar-fallback`
|
|
45
|
+
(the initials or `text`; one line, clipped to the circle), `.display-avatar-icon` (opt-in, for
|
|
46
|
+
your `#icon` slot content).
|
|
41
47
|
- With `chip`, the root is `DisplayChip`, so it also carries `.display-chip` and its
|
|
42
48
|
`data-shape`/`data-status` attributes (status comes from the `status` prop). See `display-chip`
|
|
43
49
|
for its tokens.
|
|
44
50
|
|
|
51
|
+
> **Changed 2026-10-14**: the size was a bare class on the root (`.display-avatar.lg`), which a
|
|
52
|
+
> consumer's own `.lg`/`.xl` utility classes could match. Replace `.display-avatar.<size>` with
|
|
53
|
+
> `.display-avatar[data-size="<size>"]`. Overlong fallback text is now clipped inside the circle, the
|
|
54
|
+
> avatar no longer shrinks in a flex row, and a broken image falls back to the initials.
|
|
55
|
+
> With `chip`, the status dot is its configured size again (it had picked up the avatar's diameter
|
|
56
|
+
> through a shared private `--_size` name). Content now sits in a full-size
|
|
57
|
+
> `.display-avatar-surface`, which carries the background: the chip's cutout used to land on the
|
|
58
|
+
> small initials span (cutting into the letters) and never reached the background. If you set
|
|
59
|
+
> `background` directly on `.display-avatar`, use `--display-avatar-background` instead.
|
|
60
|
+
>
|
|
45
61
|
> **Changed 2026-10-08**: chip status is the `status` prop (forwarded to `DisplayChip` as
|
|
46
62
|
> `data-status`), not an `online`/`idle`/`dnd` class through `style-class-passthrough`.
|
|
47
|
-
|
|
63
|
+
>
|
|
48
64
|
> **Changed 2026-09-27**: `.avatar-image` → `.display-avatar-image` and `.avatar-icon` →
|
|
49
65
|
> `.display-avatar-icon`.
|
|
50
66
|
|
|
@@ -56,7 +72,7 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
56
72
|
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the
|
|
57
73
|
component need `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
58
74
|
|
|
59
|
-
**Caveat:** the size
|
|
75
|
+
**Caveat:** the size steps set `--_avatar-size`/`--_avatar-font-size` on the avatar itself, so changing the
|
|
60
76
|
scale (`--display-avatar-size-md` etc.) from an ancestor works, but a one-off diameter should use
|
|
61
77
|
`--display-avatar-size`, which beats the scale wherever it's set.
|
|
62
78
|
|