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
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
- "app/**/*.css"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Overriding srcdev-nuxt-components styles
|
|
8
|
+
|
|
9
|
+
Override a layer component through its public tokens (`--component-name-*`, listed in its
|
|
10
|
+
`CONSUMER-STYLING.md`) set on an ancestor you own. Never wrap override `<style>` blocks in a named
|
|
11
|
+
`@layer`, never set its private `--_` tokens, and override colours and geometry only, never
|
|
12
|
+
behaviour (`display`, `visibility`, `pointer-events`, `z-index`, animations). Applies in apps that
|
|
13
|
+
extend the layer, not to a component's own styles inside srcdev-nuxt-components.
|
|
14
|
+
|
|
15
|
+
Why: unlayered CSS always beats the layer's `@layer components`, while a named layer can be ordered
|
|
16
|
+
below it by SSR-inlined chunks; private tokens can change in any release.
|
|
17
|
+
Detail: skill `component-local-style-override.md` (srcdev-nuxt-components).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
- "app/**/*.css"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# No `*/` inside CSS comments
|
|
8
|
+
|
|
9
|
+
Never join token names with a bare slash in a CSS comment (`--_form-*/--_scheme-icon-font-size`);
|
|
10
|
+
write "and" or space the slash.
|
|
11
|
+
|
|
12
|
+
Why: `*/` closes the comment early and yields a misleading "Unclosed string" error lines later.
|
|
13
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
- "app/**/*.css"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Native CSS nesting, no Sass concatenation
|
|
8
|
+
|
|
9
|
+
Never write `&__child` or `&-modifier`. Use `& .block__child` or a top-level `.block__child {}` rule.
|
|
10
|
+
Nest child rules inside the component's root block.
|
|
11
|
+
|
|
12
|
+
Why: it's Sass syntax; esbuild turns `&__foo` into `:is(__foo)`, which silently matches nothing.
|
|
13
|
+
Detail: skill `css-nesting-conventions.md` (srcdev-nuxt-components).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `:deep()` only in `<style scoped>`
|
|
7
|
+
|
|
8
|
+
Never use `:deep()` in an unscoped `<style>` block (srcdev-nuxt-components' own styles are never
|
|
9
|
+
scoped). There, reach a child component with a plain descendant selector (`& .input-button`), or
|
|
10
|
+
better, set the child's public tokens on an ancestor. In a `<style scoped>` block it works as normal.
|
|
11
|
+
|
|
12
|
+
Why: outside `<style scoped>` `:deep()` stays as an unknown pseudo-class and the browser silently
|
|
13
|
+
drops the whole rule.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
- "app/**/*.css"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Text on dark surfaces uses `--theme-on-surface`
|
|
8
|
+
|
|
9
|
+
Pair a dark/bold surface (`--theme-surface-hover`, `--theme-surface-inverted`) with
|
|
10
|
+
`--theme-on-surface`, never `--theme-text` or `--theme-text-inverted`.
|
|
11
|
+
|
|
12
|
+
Why: those text tokens assume a light surface, so the pairing renders dark-on-dark in light mode.
|
|
13
|
+
Detail: skill `theming-partial-override.md` (srcdev-nuxt-components).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
- "app/**/*.css"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Load-bearing CSS must work in WebKit / Safari 16
|
|
8
|
+
|
|
9
|
+
Never rely on a new CSS feature (`::details-content`, `@starting-style`, `appearance: base-select`,
|
|
10
|
+
anchor positioning, ...) for anything load-bearing unless Safari 16 has a working path; decorative
|
|
11
|
+
extras may degrade. Prefer routing to a proven alternative implementation over a CSS-only
|
|
12
|
+
`@supports` patch.
|
|
13
|
+
|
|
14
|
+
Why: WebKit silently drops unsupported rules, and Chrome's device emulation is still Blink so it
|
|
15
|
+
can't show the gap.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.spec.ts"
|
|
4
|
+
- "**/*.stories.ts"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Import Vue APIs explicitly in tests and stories
|
|
8
|
+
|
|
9
|
+
Always import `nextTick`, `computed`, `ref`, etc. from `"vue"` in `.spec.ts` and `.stories.ts` files.
|
|
10
|
+
|
|
11
|
+
Why: auto-imports only apply inside components.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# NuxtImg needs numeric width and height
|
|
7
|
+
|
|
8
|
+
Always give `NuxtImg` numeric `width` and `height` props, roughly the largest rendered pixel width
|
|
9
|
+
(pass a size prop through when it varies). Never `width="100%"`. CSS sizes the displayed image.
|
|
10
|
+
|
|
11
|
+
Why: without them the Vercel provider requests a width it doesn't allow (`w=1536`), and a
|
|
12
|
+
non-numeric width makes it request its largest screen width for every image.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Where srcdev-nuxt-components skills live
|
|
2
|
+
|
|
3
|
+
A skill named as "skill `x.md` (srcdev-nuxt-components)" is at
|
|
4
|
+
`.claude/skills/srcdev-nuxt-components/x.md`. Per-component usage docs are in its `components/`
|
|
5
|
+
subfolder (`.claude/skills/srcdev-nuxt-components/components/<component-name>.md`). Inside the
|
|
6
|
+
srcdev-nuxt-components repo itself, the same files are directly under `.claude/skills/`.
|
|
7
|
+
|
|
8
|
+
Why: the layer's rules name skills rather than linking a path, so they read the same in the
|
|
9
|
+
library repo and in consuming apps.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.stories.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Story args stay reactive
|
|
7
|
+
|
|
8
|
+
Never destructure, copy (`ref(args.x)`) or `watch` `args` at setup time. Strip non-prop extra args
|
|
9
|
+
inside a `computed` before `v-bind`, and type the meta as `Meta<StoryArgs>` when it has extra args.
|
|
10
|
+
|
|
11
|
+
Why: setup runs once, so copied args and watchers miss control changes, and unstripped extras become
|
|
12
|
+
unknown HTML attributes. Detail: skill `storybook-add-story.md` (srcdev-nuxt-components).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.stories.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Register every component a story template uses
|
|
7
|
+
|
|
8
|
+
Every component in a story's `template` string, including ones placed in the component's slots, must
|
|
9
|
+
be listed in that story's `components: {}`.
|
|
10
|
+
|
|
11
|
+
Why: components aren't auto-registered in story templates; unregistered ones render as unknown tags.
|
|
12
|
+
Detail: skill `storybook-add-story.md` (srcdev-nuxt-components).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
- "app/composables/**/*.ts"
|
|
5
|
+
- "app/utils/**/*.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Vue and Nuxt auto-imports
|
|
9
|
+
|
|
10
|
+
Never import `ref`, `reactive`, `computed`, `watch`, `nextTick`, `useState`, `useRoute`, `useRouter`
|
|
11
|
+
or anything from `~/composables/` in app code. Import only `type`s and external packages.
|
|
12
|
+
|
|
13
|
+
Why: Nuxt auto-imports them; manual imports are noise and drift from the rest of the codebase.
|
|
14
|
+
Test and story files are the exception (see `explicit-vue-imports.md`).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# No self-closing non-void elements
|
|
7
|
+
|
|
8
|
+
Always write explicit closing tags on non-void elements and slots: `<slot name="x"></slot>`,
|
|
9
|
+
`<span></span>`. Never `<slot name="x" />`.
|
|
10
|
+
|
|
11
|
+
Why: ESLint `vue/html-self-closing` rejects it.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Props pattern
|
|
7
|
+
|
|
8
|
+
Always declare props as `interface Props` + `withDefaults(defineProps<Props>(), {...})`, with factory
|
|
9
|
+
functions for array/object defaults. Never use options-style `defineProps({ x: { type, default } })`.
|
|
10
|
+
Never declare `class` or `style` as props; hide them from Storybook controls via `argTypes` instead.
|
|
11
|
+
|
|
12
|
+
Why: typed props are the standard across srcdev-nuxt-components and its apps, and a
|
|
13
|
+
`class`/`style` prop breaks Vue's automatic `$attrs` inheritance.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
- "app/composables/**/*.ts"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Never derive state from slots in a computed
|
|
8
|
+
|
|
9
|
+
Never read `useSlots()` inside a `computed()`. Use a plain function called from the template
|
|
10
|
+
(`v-if="hasFooter()"`, `:aria-describedby="ariaDescribedby()"`), including anything derived from it.
|
|
11
|
+
Test by toggling a `v-if`'d slot in a host component after mount.
|
|
12
|
+
|
|
13
|
+
Why: the slots object isn't reactive, so the computed caches its first value and leaves split state
|
|
14
|
+
when a slot is added or removed after mount.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Video src goes on a `<source>` child
|
|
7
|
+
|
|
8
|
+
Never bind `:src` on a `<video>`. Use `<source :src="src">` inside it, `:key="src"` on the video, and
|
|
9
|
+
an explicit `videoEl.load()` on mount and src change.
|
|
10
|
+
|
|
11
|
+
Why: Vue patching `:src` on client-side navigation silently skips the fetch.
|
|
12
|
+
Detail: skill `vue-video-autoplay.md` (srcdev-nuxt-components).
|
|
@@ -15,7 +15,7 @@ A component passes when its Component Ledger row (`node .claude/component-ledger
|
|
|
15
15
|
`.claude/component-ledger/audit.json`) shows `score: 5`, a real tier, and `variants`, `legacy_props`,
|
|
16
16
|
`story_args_bug`, `eslint_issues`, `redundant_priv_tokens`, `styling_doc_outdated` and `missing_stress_story`
|
|
17
17
|
all `false`.
|
|
18
|
-
The ledger can't see items 9, 10, 11 and 14, so check those by hand.
|
|
18
|
+
The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
19
19
|
|
|
20
20
|
## The checklist
|
|
21
21
|
|
|
@@ -101,6 +101,10 @@ The ledger can't see items 9, 10, 11 and 14, so check those by hand.
|
|
|
101
101
|
If other components render this one inside them (it's a child or building block of theirs),
|
|
102
102
|
each story gets a short note that explains the relationship and links to the parent's story
|
|
103
103
|
(`storybook-add-story.md`, "Link to the stories of components that use this one").
|
|
104
|
+
Sample copy in stories, and examples in the skill doc, use placeholders (a name like "Alex
|
|
105
|
+
Morgan", a town like "Northtown"), never a real client's name, town, business or site: the
|
|
106
|
+
stories and docs ship in the public package. **Existing component:** check for, and replace, any
|
|
107
|
+
copy lifted from a consumer app (`ProfileSection` had both).
|
|
104
108
|
6a. **Storybook Controls-panel reactivity** — check every story `Template`/`render` function in
|
|
105
109
|
the component's `stories/*.stories.ts` files for this bug: `@storybook/vue3` mounts the story
|
|
106
110
|
component **once** and, on every Controls-panel change, mutates the same reactive `args`
|
|
@@ -134,7 +138,9 @@ The ledger can't see items 9, 10, 11 and 14, so check those by hand.
|
|
|
134
138
|
descriptions, body text) a line-clamp token, `--<component>-<element>-line-clamp`, which
|
|
135
139
|
covers ellipsis too (`1` is single-line ellipsis). Default it to `none` unless the layout
|
|
136
140
|
clearly needs a cap, and never clamp text that must stay readable (legal or attribution text,
|
|
137
|
-
link text that names its destination).
|
|
141
|
+
link text that names its destination). Only text the component renders itself (props and data) needs a token: text that arrives
|
|
142
|
+
through a slot is the consumer's own markup, so they clamp it directly and the component adds
|
|
143
|
+
no token for it (`ProfileSection` is all slots and has none). Shape and rules: `theming-component-token-pattern.md`,
|
|
138
144
|
"Line-clamp tokens". Give each line-clamp token a Controls-panel select in the stories (set on
|
|
139
145
|
a wrapper `:style`, stripped from the component's args), labelled as a story-only CSS token so
|
|
140
146
|
nobody mistakes it for a prop: `storybook-add-story.md`, "Label token controls as story-only".
|
|
@@ -143,6 +149,21 @@ The ledger can't see items 9, 10, 11 and 14, so check those by hand.
|
|
|
143
149
|
with nothing to stress (animation wrappers, decorative containers that only take a slot and
|
|
144
150
|
tag/variant props, Storybook tooling) are exempt: they are listed in `stressStoryExempt` in
|
|
145
151
|
`.claude/component-ledger/build.mjs`; add a new one there rather than writing a padding story.
|
|
152
|
+
6c. **Storybook docs page** — every story file gets a generated Docs page: `tags: ["autodocs"]` is
|
|
153
|
+
set globally in `.storybook/preview.ts` (since 2026-10-14), so don't repeat it in the meta.
|
|
154
|
+
Open the component's Docs page and check that it reads well:
|
|
155
|
+
- A `parameters.docs.description.component` in the meta says what the component is for and
|
|
156
|
+
names its main props/slots/events. Per-story `docs.description.story` text explains what
|
|
157
|
+
that story shows (the Docs page is the only place either is visible).
|
|
158
|
+
- The props table is accurate. Every story-only arg has a `description` and a category, and
|
|
159
|
+
CSS-token args are labelled story-only (6b).
|
|
160
|
+
- Every story renders inline. A story that misbehaves on the shared page (fixed/sticky
|
|
161
|
+
overlays, `layout: "fullscreen"` canvases, dialogs, toasts, scroll effects, heavy stress
|
|
162
|
+
data) gets `parameters.docs.story: { inline: false, height: "<px>" }` so it renders in its
|
|
163
|
+
own iframe. Don't drop it from the page.
|
|
164
|
+
- A story file that isn't a component reference (Storybook tooling, a file that already has an
|
|
165
|
+
attached `.mdx` page) opts out with `tags: ["!autodocs"]`. `ColourRamps.stories.ts` does this.
|
|
166
|
+
See `storybook-add-story.md`, "Docs page".
|
|
146
167
|
7. **Skill doc** — create or update `.claude/skills/components/<component-name>.md`, following the
|
|
147
168
|
pattern of an existing one. A labelled `<Name>Field` wrapper (or any other wrapper) is documented
|
|
148
169
|
in its control's doc under a "Variants" section, not in a doc of its own
|
|
@@ -222,3 +243,17 @@ The ledger can't see items 9, 10, 11 and 14, so check those by hand.
|
|
|
222
243
|
- `NuxtImg` gets explicit `width` and `height`; a `<video>` uses a `<source :src>` child with
|
|
223
244
|
`:key` and `.load()` (pitfall #12).
|
|
224
245
|
- No `#components` import (breaks Storybook); use `resolveComponent()`.
|
|
246
|
+
- Layout breakpoints respond to the component's own width (`container: <name> / inline-size` on the
|
|
247
|
+
root, `@container <name> (min-width: …)`), not the viewport, so it still works in a narrow
|
|
248
|
+
column. **Existing component:** when converting a viewport `@media` breakpoint, grep consumer
|
|
249
|
+
repos for `@media` overrides of the same element (they now switch at a different point) and list
|
|
250
|
+
the edits in private memory. Note that an inline-size container can't size itself from its
|
|
251
|
+
content: say so in `CONSUMER-STYLING.md` (`ProfileSection`).
|
|
252
|
+
- Grid tracks that hold text are `minmax(0, …)` so an unbroken string can't widen the grid.
|
|
253
|
+
- No `:deep()` in the component's `<style>`: these blocks are never scoped, so Vue leaves it
|
|
254
|
+
as-is and the browser drops the whole rule (pitfall #27). Use a descendant selector, or set
|
|
255
|
+
the child component's public tokens on an ancestor.
|
|
256
|
+
- No `font-size` (or font-size token default) under `1rem`. `html` is `62.5%`, so `1rem` is
|
|
257
|
+
10px, and values like `0.75rem`/`0.9rem` are almost always copied from a 16px-base design
|
|
258
|
+
and render at 7.5-9px. `PricingCard` had both (fixed 2026-10-15); grep
|
|
259
|
+
`font-size:\s*(var\([^,]+,\s*)?0?\.[0-9]+rem` to find them.
|
|
@@ -58,7 +58,7 @@ stripping `Core`.
|
|
|
58
58
|
|
|
59
59
|
1. `git mv` the `.vue` files, their stories and tests (and the skill doc if it's named after the old
|
|
60
60
|
control name).
|
|
61
|
-
2. `sed` the old names across `app/`, `.claude/`, `.vscode/`, `MIGRATION.md` and `
|
|
61
|
+
2. `sed` the old names across `app/`, `.claude/`, `.vscode/`, `MIGRATION.md` and `CLAUDE.md`:
|
|
62
62
|
component names, `import` paths, story titles/`*StoryArgs` interfaces, `describe()` names, and the
|
|
63
63
|
root/base DOM class (update `CONSUMER-STYLING.md` and test selectors that use it).
|
|
64
64
|
3. Add a "Renamed YYYY-MM-DD" note at the top of the skill doc listing old → new names and classes.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# DisplayAvatar Component
|
|
2
2
|
|
|
3
|
+
> **Changed 2026-10-14:** size is a `data-size` attribute, not a bare class (`.display-avatar.lg` → `.display-avatar[data-size="lg"]`). New `image-size` prop (default `112`): `NuxtImg` used to get `width="100%"`, which @nuxt/image ignores, so the Vercel provider requested its largest screen width for every avatar. A broken image now falls back to the initials, initials are emoji-safe, and long fallback text is clipped inside the circle. With `chip`, the status dot was drawn at the avatar's diameter instead of its configured size: avatar and chip both declared a private `--_size` on the same root element and the avatar's size rules won. The avatar's private tokens are now `--_avatar-size`/`--_avatar-font-size`. Content (both slots too) now sits in a full-size `.display-avatar-surface` that carries the background, so the chip's cutout covers the whole avatar instead of landing on the initials span and cutting into the letters.
|
|
4
|
+
|
|
3
5
|
> **Changed 2026-10-08:** new `status`/`status-label` props, forwarded to the chip. Chip status used to be an `online`/`idle`/`dnd` class through `style-class-passthrough`, which no longer colours the dot.
|
|
4
6
|
|
|
5
7
|
## Overview
|
|
@@ -18,10 +20,11 @@
|
|
|
18
20
|
| `src` | `string` | — | Image URL. Renders `NuxtImg` when set; fallback text otherwise. |
|
|
19
21
|
| `alt` | `string` | — | Alt text for the image; also used to derive initials. |
|
|
20
22
|
| `text` | `string` | — | Override the auto-derived initials with an explicit string. |
|
|
21
|
-
| `size` | `"xs" \| "s" \| "md" \| "lg" \| "xl"` | `"md"` |
|
|
23
|
+
| `size` | `"xs" \| "s" \| "md" \| "lg" \| "xl"` | `"md"` | Sets `data-size`, which picks width, height and font-size. |
|
|
22
24
|
| `chip` | `boolean \| DisplayChipConfig` | — | Add a status chip. `true` uses defaults; pass a config object to customise. |
|
|
23
25
|
| `status` | `DisplayChipStatus` | — | Chip status colour (`offline`/`online`/`idle`/`dnd`), forwarded to `DisplayChip`. Ignored without `chip`; the chip defaults to `offline`. |
|
|
24
26
|
| `status-label` | `string` | — | Screen-reader text for the chip status (e.g. "Online"), forwarded to `DisplayChip`. Ignored without `chip`. |
|
|
27
|
+
| `:image-size` | `number` | `112` | Pixel width and height requested from `@nuxt/image` (1x; 2x is double). Raise it if you resize the avatar past `xl` with `--display-avatar-size`. |
|
|
25
28
|
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root element. |
|
|
26
29
|
|
|
27
30
|
### Size dimensions
|
|
@@ -45,24 +48,32 @@ Each step is a token (`--display-avatar-size-md`, `--display-avatar-font-size-md
|
|
|
45
48
|
| `default` | Replaces the auto image/fallback content entirely. |
|
|
46
49
|
| `icon` | Appended inside the avatar (e.g. an icon overlay over the image). |
|
|
47
50
|
|
|
51
|
+
Both slots render inside `.display-avatar-surface`, a full-size element that carries the background. With `chip`, that surface is what gets the cutout mask.
|
|
52
|
+
|
|
48
53
|
---
|
|
49
54
|
|
|
50
55
|
## Fallback text logic
|
|
51
56
|
|
|
52
57
|
When `src` is not set, a `<span>` renders the fallback value:
|
|
53
58
|
|
|
54
|
-
1. `text` prop — used as-is if
|
|
55
|
-
2. `alt` initials — first character
|
|
59
|
+
1. `text` prop — used as-is if it has any non-whitespace content.
|
|
60
|
+
2. `alt` initials — the first character (a whole grapheme, so emoji and flags stay intact) of the first two words. Extra whitespace is ignored.
|
|
56
61
|
3. Empty string — if neither is set.
|
|
57
62
|
|
|
58
63
|
When `alt` is set, the initials are `aria-hidden` and the full `alt` is rendered as `sr-only` text, so a screen reader announces "Jane Smith" rather than "J S". With only `text`, the text is read as-is.
|
|
59
64
|
|
|
65
|
+
If the image fails to load, the initials fallback shows instead (an empty circle when there is no `alt`). A new `src` tries the image again.
|
|
66
|
+
|
|
67
|
+
The fallback is one line, clipped to the circle: keep `text` short (1–3 characters).
|
|
68
|
+
|
|
60
69
|
With `src`, the image uses `alt`, or an empty (decorative) `alt` when none is given. Pass `alt` unless the name is already visible next to the avatar.
|
|
61
70
|
|
|
62
71
|
```
|
|
63
72
|
alt="John Doe" → "JD"
|
|
64
73
|
alt="Alice" → "A"
|
|
65
74
|
alt="Alice Bob C" → "AB"
|
|
75
|
+
alt="🦄 Unicorn" → "🦄U"
|
|
76
|
+
alt=" Jane Smith " → "JS"
|
|
66
77
|
text="?" → "?"
|
|
67
78
|
```
|
|
68
79
|
|
|
@@ -226,6 +226,7 @@ See [display-avatar.md](./display-avatar.md) for the full API.
|
|
|
226
226
|
- `shape` must match the actual shape of the slot content — the position maths differs between `circle` (radius-based) and `square` (clamped corner-aware).
|
|
227
227
|
- `config` values are geometric inputs to CSS `calc(cos())` / `calc(sin())` expressions. Pass them as strings with units (`"12px"`, `"45deg"`), not plain numbers.
|
|
228
228
|
- The chip dot is rendered via `::after` pseudo-element; icon and label sit above it at `z-index: 2`.
|
|
229
|
-
- The mask cutout is applied to all direct children of `.display-chip` except `.display-chip-icon`, `.display-chip-label` and the `.sr-only` status text
|
|
229
|
+
- The mask cutout is applied to all direct children of `.display-chip` except `.display-chip-icon`, `.display-chip-label` and the `.sr-only` status text. The mask is positioned in percentages of each child's own box, so slot exactly one host element that fills the chip and carries its own background (an `<img>`, or a full-size wrapper around text). A small text span as a direct child gets the notch cut into its letters, and a background set on `.display-chip` itself is never cut. `DisplayAvatar` wraps its content in a full-size `.display-avatar-surface` for this reason.
|
|
230
|
+
- A component that renders `DisplayChip` as its own root (as `DisplayAvatar` does) shares that element, so it must not declare the chip's private tokens (`--_size`, `--_offset`, ...) for its own values: they collide on the same element. `DisplayAvatar` uses `--_avatar-size`.
|
|
230
231
|
- The `StressTest` story shows the worst-case labels and config values.
|
|
231
232
|
- `DisplayChipConfig` and `DisplayChipProps` are both exported from the layer types. Use `DisplayChipConfig` when passing geometry values (the `config` prop). Use `DisplayChipProps` only if you need to pass the full component prop set (e.g. when building a wrapper component).
|
|
@@ -132,8 +132,8 @@ replaced.
|
|
|
132
132
|
- Survives hostile data (see the `StressTest` story): long and unbroken text wraps inside its card,
|
|
133
133
|
reviewer names are cut to 2 lines (`--google-reviews-author-line-clamp`),
|
|
134
134
|
initials take whole characters (emoji-safe), star fill is clamped to 0–5, review text renders as
|
|
135
|
-
plain text. A broken photo URL
|
|
136
|
-
|
|
135
|
+
plain text. A broken photo URL falls back to the reviewer's initials (`DisplayAvatar`'s image-error
|
|
136
|
+
fallback).
|
|
137
137
|
|
|
138
138
|
## Variants
|
|
139
139
|
|