srcdev-nuxt-components 9.0.4 → 9.0.6

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.
@@ -0,0 +1,12 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch",
5
+ "mcp__ide__getDiagnostics",
6
+ "WebFetch(domain:drafts.csswg.org)",
7
+ "Bash(grep '\"\"typescript\"\"' package.json)",
8
+ "Bash(grep '\"\"@vue/language-server\\\\|@volar\\\\|vue-tsc\"\"' package.json)",
9
+ "Bash(npx nuxi prepare)"
10
+ ]
11
+ }
12
+ }
@@ -0,0 +1,83 @@
1
+ # EyebrowText Component
2
+
3
+ ## Overview
4
+
5
+ `EyebrowText` renders a short uppercase label — typically used above a heading to provide context or categorisation. It outputs a single string with configurable tag, size, and colour via CSS custom properties.
6
+
7
+ ## Before Adding to a Page
8
+
9
+ Always ask the user for the following before placing the component:
10
+
11
+ 1. **`textContent`** — what is the label text? (passed as-is — do not pre-uppercase)
12
+ 2. **`fontSize`** — which size? (`large` | `medium` | `small`) — default is `medium`
13
+ 3. **`tag`** — does it need to be inline? If yes, use `span`. Otherwise `p` or `div` (default).
14
+ 4. **`styleClassPassthrough`** — any extra classes to add? (layout, spacing, custom styling hooks)
15
+
16
+ Do not assume placeholder text or default content.
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Required |
21
+ |------|------|---------|----------|
22
+ | `textContent` | `string` | — | yes |
23
+ | `tag` | `"p" \| "div" \| "span"` | `"div"` | no |
24
+ | `fontSize` | `"large" \| "medium" \| "small"` | `"medium"` | no |
25
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
26
+
27
+ ## Basic Usage
28
+
29
+ ```vue
30
+ <EyebrowText textContent="Interior Design" />
31
+ ```
32
+
33
+ ## With Tag and Size
34
+
35
+ Use `tag="p"` for semantic paragraph context, or `tag="span"` when inline. Choose `fontSize` to match surrounding hierarchy.
36
+
37
+ ```vue
38
+ <EyebrowText
39
+ tag="p"
40
+ fontSize="small"
41
+ textContent="Featured Collection"
42
+ />
43
+ ```
44
+
45
+ ## Paired with HeroText
46
+
47
+ EyebrowText is commonly placed directly above a `HeroText` heading:
48
+
49
+ ```vue
50
+ <EyebrowText tag="p" textContent="Our Services" />
51
+ <HeroText
52
+ tag="h2"
53
+ :textContent="[
54
+ { text: 'Crafted with', styleClass: 'normal' },
55
+ { text: 'Care', styleClass: 'accent' },
56
+ ]"
57
+ />
58
+ ```
59
+
60
+ ## Font Size Scale
61
+
62
+ | Value | CSS variable |
63
+ |-------|-------------|
64
+ | `large` | `--eyebrow-text-large` |
65
+ | `medium` | `--eyebrow-text-medium` |
66
+ | `small` | `--eyebrow-text-small` |
67
+
68
+ Define these CSS custom properties in your consuming app to control sizes.
69
+
70
+ ## Styling
71
+
72
+ Key CSS custom properties:
73
+
74
+ - `--colour-text-eyebrow` — text colour (defaults to an accent/muted tone)
75
+
76
+ Text is always `text-transform: uppercase` — do not pass pre-uppercased strings, as this makes content harder to edit and search.
77
+
78
+ Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `.eyebrow-text`.
79
+
80
+ ## Notes
81
+
82
+ - Component is auto-imported in Nuxt — no import needed.
83
+ - `tag` defaults to `"div"` which is block-level. Use `"span"` when the eyebrow must be inline within a flow of text.
@@ -0,0 +1,110 @@
1
+ # HeroText Component
2
+
3
+ ## Overview
4
+
5
+ `HeroText` renders a styled heading with support for mixed text styling (normal/accent segments), configurable font size, layout axis, and an optional icon. It uses Playfair Display font with italic variation for accent segments.
6
+
7
+ ## Before Adding to a Page
8
+
9
+ Always ask the user for the following before placing the component:
10
+
11
+ 1. **`tag`** — what heading level? (`h1` | `h2` | `h3` | `h4` | `h5` | `h6`)
12
+ 2. **Text segments** — for each segment, what is the text and should it be `normal` (default) or `accent` (italic, `--colour-text-accent` colour)?
13
+ 3. **`axis`** — should segments sit inline (`horizontal`, default) or stack in a column (`vertical`)?
14
+ 4. **`fontSize`** — which size? (`display` | `title` | `heading` | `subheading` | `label`) — default is `title`
15
+ 5. **`styleClassPassthrough`** — any extra classes to add? (layout, spacing, custom styling hooks)
16
+
17
+ Do not assume placeholder text or default content.
18
+
19
+ ## Props
20
+
21
+ | Prop | Type | Default | Required |
22
+ |------|------|---------|----------|
23
+ | `tag` | `"h1" \| "h2" \| "h3" \| "h4" \| "h5" \| "h6"` | — | yes |
24
+ | `textContent` | `TextConfig[]` | — | yes |
25
+ | `id` | `string` | `undefined` | no |
26
+ | `axis` | `"horizontal" \| "vertical"` | `"horizontal"` | no |
27
+ | `fontSize` | `"display" \| "title" \| "heading" \| "subheading" \| "label"` | `"title"` | no |
28
+ | `icon` | `string` | `undefined` | no |
29
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
30
+
31
+ ### TextConfig
32
+
33
+ ```ts
34
+ interface TextConfig {
35
+ text: string;
36
+ styleClass?: "normal" | "accent";
37
+ }
38
+ ```
39
+
40
+ - `"normal"` — default unstyled text
41
+ - `"accent"` — italic, coloured with `--colour-text-accent`
42
+
43
+ ## Basic Usage
44
+
45
+ ```vue
46
+ <HeroText
47
+ tag="h1"
48
+ :textContent="[
49
+ { text: 'Designing', styleClass: 'normal' },
50
+ { text: 'Artistry', styleClass: 'accent' },
51
+ { text: 'at Home', styleClass: 'normal' },
52
+ ]"
53
+ />
54
+ ```
55
+
56
+ ## With Icon
57
+
58
+ `icon` accepts any icon name from `@nuxt/icon` (e.g. Iconify identifiers).
59
+
60
+ ```vue
61
+ <HeroText
62
+ tag="h2"
63
+ icon="lucide:sparkles"
64
+ fontSize="heading"
65
+ :textContent="[{ text: 'Featured', styleClass: 'accent' }]"
66
+ />
67
+ ```
68
+
69
+ ## Vertical Axis
70
+
71
+ Use `axis="vertical"` to stack text segments in a column instead of a row.
72
+
73
+ ```vue
74
+ <HeroText
75
+ tag="h1"
76
+ axis="vertical"
77
+ fontSize="display"
78
+ :textContent="[
79
+ { text: 'Modern', styleClass: 'normal' },
80
+ { text: 'Living', styleClass: 'accent' },
81
+ ]"
82
+ />
83
+ ```
84
+
85
+ ## Font Size Scale
86
+
87
+ | Value | CSS variable |
88
+ |-------|-------------|
89
+ | `display` | `--hero-text-display` |
90
+ | `title` | `--hero-text-title` |
91
+ | `heading` | `--hero-text-heading` |
92
+ | `subheading` | `--hero-text-subheading` |
93
+ | `label` | `--hero-text-label` |
94
+
95
+ Define these CSS custom properties in your consuming app to control sizes.
96
+
97
+ ## Styling
98
+
99
+ Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `.hero-text`.
100
+
101
+ Key CSS custom properties:
102
+
103
+ - `--colour-text-accent` — colour applied to `.accent` spans and the icon
104
+ - `--hero-text-{scale}` — font size per scale value
105
+
106
+ ## Notes
107
+
108
+ - Text segments are trimmed and a trailing space is automatically appended between segments in horizontal axis — do not manually pad `text` values.
109
+ - The icon is sized to match the font size. At `subheading` scale it is explicitly capped at `0.75 * --hero-text-subheading`.
110
+ - Component is auto-imported in Nuxt — no import needed.
@@ -0,0 +1,101 @@
1
+ # LinkText Component
2
+
3
+ ## Overview
4
+
5
+ `LinkText` renders a `NuxtLink` styled as a standalone text link, with optional left and/or right icon slots. Icons are vertically centred with the label text. Intended as a CTA-style link — distinct from `InputButtonCore` (which is a button/link hybrid with button styling).
6
+
7
+ ## Props
8
+
9
+ | Prop | Type | Default | Required |
10
+ |------|------|---------|----------|
11
+ | `to` | `string` | — | yes |
12
+ | `linkText` | `string` | — | yes |
13
+ | `external` | `boolean` | `false` | no |
14
+ | `target` | `string` | `undefined` | no |
15
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
16
+
17
+ ## Slots
18
+
19
+ | Slot | Purpose |
20
+ |------|---------|
21
+ | `left` | Icon rendered before the label |
22
+ | `right` | Icon rendered after the label |
23
+
24
+ Both slots are optional. If neither is provided, no icon wrapper elements are rendered.
25
+
26
+ ## Basic Usage
27
+
28
+ ```vue
29
+ <LinkText to="/about" linkText="About Us" />
30
+ ```
31
+
32
+ ## With Left Icon (back/previous pattern)
33
+
34
+ ```vue
35
+ <LinkText to="/blog" linkText="Back to Blog">
36
+ <template #left>
37
+ <Icon name="lucide:arrow-left" />
38
+ </template>
39
+ </LinkText>
40
+ ```
41
+
42
+ ## With Right Icon (forward/more pattern)
43
+
44
+ ```vue
45
+ <LinkText to="/services" linkText="Learn More">
46
+ <template #right>
47
+ <Icon name="lucide:arrow-right" />
48
+ </template>
49
+ </LinkText>
50
+ ```
51
+
52
+ ## Both Icons
53
+
54
+ ```vue
55
+ <LinkText to="/collection" linkText="Explore">
56
+ <template #left>
57
+ <Icon name="lucide:sparkles" />
58
+ </template>
59
+ <template #right>
60
+ <Icon name="lucide:arrow-right" />
61
+ </template>
62
+ </LinkText>
63
+ ```
64
+
65
+ ## External Link
66
+
67
+ ```vue
68
+ <LinkText
69
+ to="https://example.com"
70
+ linkText="Visit Site"
71
+ :external="true"
72
+ target="_blank"
73
+ >
74
+ <template #right>
75
+ <Icon name="lucide:external-link" />
76
+ </template>
77
+ </LinkText>
78
+ ```
79
+
80
+ ## Styling
81
+
82
+ Key CSS custom properties — define these in your consuming app to control appearance:
83
+
84
+ | Property | Default | Purpose |
85
+ |----------|---------|---------|
86
+ | `--link-text-colour` | `currentColor` | Link text/icon colour |
87
+ | `--link-text-colour-hover` | `currentColor` | Colour on hover/focus |
88
+ | `--link-text-gap` | `0.4em` | Gap between icon and label |
89
+ | `--link-text-font-size` | `inherit` | Font size |
90
+ | `--link-text-decoration` | `underline` | Text decoration |
91
+ | `--link-text-decoration-hover` | `none` | Text decoration on hover |
92
+ | `--link-text-underline-offset` | `0.2em` | Underline offset |
93
+
94
+ Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `.link-text`.
95
+
96
+ ## Notes
97
+
98
+ - Component is auto-imported in Nuxt — no import needed.
99
+ - NuxtLink handles both internal (`/path`) and external (`https://...`) URLs automatically. Only set `external: true` if you need to force external handling on an internal-looking path.
100
+ - The label is always rendered inside `.link-text__label` — target this class if you need to style the text independently of the icons.
101
+ - DOM order is always: left icon → label → right icon.
@@ -0,0 +1,54 @@
1
+ # Skills
2
+
3
+ Step-by-step guides for repeatable development tasks in this project.
4
+
5
+ ## For consuming apps
6
+
7
+ Copy skills into your project with:
8
+
9
+ ```bash
10
+ cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components
11
+ ```
12
+
13
+ Skills land in `.claude/skills/srcdev-nuxt-components/` — safe to re-run without overwriting your own skills.
14
+
15
+ ## Structure
16
+
17
+ Each skill is a single markdown file named `<area>-<task>.md`.
18
+
19
+ ```
20
+ .claude/skills/
21
+ ├── index.md — this file
22
+ ├── storybook-add-story.md — create a Storybook story for a component
23
+ ├── storybook-add-font.md — add a new font to Storybook
24
+ ├── testing-add-unit-test.md — create a Vitest unit test with snapshots
25
+ ├── testing-add-playwright.md — create a Playwright visual regression test
26
+ ├── theming-override-default.md — override the default theme with a custom colour scale
27
+ └── components/
28
+ ├── eyebrow-text.md — EyebrowText props, usage patterns, styling
29
+ ├── hero-text.md — HeroText props, usage patterns, styling
30
+ └── link-text.md — LinkText props, slots, usage patterns, styling
31
+ ```
32
+
33
+ ## Skill file template
34
+
35
+ ```md
36
+ # <Title>
37
+
38
+ ## Overview
39
+ Brief description of what this skill does and why it exists.
40
+
41
+ ## Prerequisites
42
+ What needs to be in place before starting (optional section).
43
+
44
+ ## Steps
45
+
46
+ ### 1. <Step name>
47
+ ...
48
+
49
+ ### 2. <Step name>
50
+ ...
51
+
52
+ ## Notes
53
+ Edge cases, gotchas, or links to related files (optional section).
54
+ ```
@@ -0,0 +1,101 @@
1
+ # Adding a New Font to Storybook
2
+
3
+ ## Overview
4
+
5
+ `@nuxt/fonts` is disabled in Storybook (via `process.env.STORYBOOK` check in `nuxt.config.ts`).
6
+ Fonts are served instead via `.storybook/fonts.css`, which is imported in `.storybook/preview.ts`.
7
+ Font files live in `.storybook/public/_fonts/` and are served as static assets via `staticDirs: ["./public"]` in `.storybook/main.ts`.
8
+
9
+ ## Steps
10
+
11
+ ### 1. Check what weights/styles exist
12
+
13
+ `@nuxt/fonts` caches font metadata here after a `nuxt build` or `nuxt dev`:
14
+
15
+ ```url
16
+ node_modules/.cache/nuxt/fonts/meta/bunny/<hash>/bunny/<FontName>-<hash>-data.json
17
+ ```
18
+
19
+ Open the matching JSON file — it lists every available `weight`, `style`, and `src.url`.
20
+ The bunny CDN URL pattern is predictable:
21
+
22
+ ```url
23
+ https://fonts.bunny.net/<font-slug>/files/<font-slug>-<subset>-<weight>-<style>.woff2
24
+ ```
25
+
26
+ Example for Playfair Display latin, 700 normal:
27
+
28
+ ```url
29
+ https://fonts.bunny.net/playfair-display/files/playfair-display-latin-700-normal.woff2
30
+ ```
31
+
32
+ Common subsets: `latin`, `latin-ext`, `cyrillic`, `vietnamese`. For Storybook, `latin` only is sufficient.
33
+
34
+ ### 2. Create the font directory
35
+
36
+ ```url
37
+ .storybook/public/_fonts/<font-slug>/
38
+ ```
39
+
40
+ ### 3. Download the font files
41
+
42
+ Run this from the project root, substituting `<font-slug>` and the weights that actually exist:
43
+
44
+ ```bash
45
+ mkdir -p .storybook/public/_fonts/<font-slug>
46
+ cd .storybook/public/_fonts/<font-slug>
47
+
48
+ for weight in 400 500 600 700 800 900; do
49
+ for style in normal italic; do
50
+ curl -sfLo "<font-slug>-latin-${weight}-${style}.woff2" \
51
+ "https://fonts.bunny.net/<font-slug>/files/<font-slug>-latin-${weight}-${style}.woff2" \
52
+ && echo "OK: ${weight} ${style}" || echo "FAIL: ${weight} ${style}"
53
+ done
54
+ done
55
+ ```
56
+
57
+ `FAIL` results mean that weight/style combination doesn't exist — safe to ignore.
58
+
59
+ ### 4. Add @font-face declarations to `.storybook/fonts.css`
60
+
61
+ ```css
62
+ /* Font Name */
63
+ @font-face {
64
+ font-family: "Font Name";
65
+ src: url("/_fonts/<font-slug>/<font-slug>-latin-400-normal.woff2") format("woff2");
66
+ font-weight: 400;
67
+ font-style: normal;
68
+ font-display: swap;
69
+ }
70
+ /* repeat for each weight/style downloaded */
71
+ ```
72
+
73
+ ### 5. Register the font in `nuxt.config.ts`
74
+
75
+ Add to the `fonts.families` array (used by the Nuxt app, not Storybook):
76
+
77
+ ```ts
78
+ {
79
+ name: "Font Name",
80
+ weights: [400, 500, 600, 700, 800, 900],
81
+ styles: ["normal", "italic"],
82
+ provider: "bunny",
83
+ },
84
+ ```
85
+
86
+ ### 6. Update the font table in `README.md`
87
+
88
+ Add a row to the Storybook fonts table under `## Storybook > ### Fonts`.
89
+
90
+ ## Notes
91
+
92
+ **Architecture summary**
93
+
94
+ | Context | Font source |
95
+ | --------- | -------------------------------------------------------------------- |
96
+ | Nuxt app | `@nuxt/fonts` (bunny CDN → hashed `/_fonts/*.woff2`) |
97
+ | Storybook | `.storybook/fonts.css` + static files in `.storybook/public/_fonts/` |
98
+
99
+ **Poppins exception** — Poppins files use TTF (not woff2) and are stored as
100
+ `.storybook/public/_fonts/poppins/Poppins-<Weight>.ttf`. These came from Google Fonts
101
+ directly, not bunny, so there is no equivalent curl script.
@@ -0,0 +1,164 @@
1
+ # Adding a Storybook Story
2
+
3
+ ## Overview
4
+
5
+ Stories live alongside the component in a `stories/` subfolder and are the source for both
6
+ manual visual review and Playwright visual regression tests.
7
+
8
+ ## File location
9
+
10
+ ```url
11
+ app/components/<component-folder>/stories/<ComponentName>.stories.ts
12
+ ```
13
+
14
+ ## Two patterns
15
+
16
+ ### Simple component — `StoryObj`
17
+
18
+ Use when the component has no v-model and one representative story is enough.
19
+
20
+ ```ts
21
+ import ComponentName from "../ComponentName.vue";
22
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
23
+
24
+ const meta: Meta<typeof ComponentName> = {
25
+ title: "Category/Subcategory/ComponentName",
26
+ component: ComponentName,
27
+ argTypes: {
28
+ propName: {
29
+ control: { type: "select" },
30
+ options: ["a", "b", "c"],
31
+ description: "What this prop does",
32
+ },
33
+ booleanProp: {
34
+ control: "boolean",
35
+ description: "What this prop does",
36
+ },
37
+ textProp: {
38
+ control: "text",
39
+ description: "What this prop does",
40
+ },
41
+ objectProp: {
42
+ control: "object",
43
+ description: "What this prop does",
44
+ },
45
+ },
46
+ };
47
+
48
+ export default meta;
49
+ type Story = StoryObj<typeof ComponentName>;
50
+
51
+ export const Default: Story = {
52
+ args: {
53
+ propName: "a",
54
+ booleanProp: false,
55
+ textProp: "Hello",
56
+ objectProp: [],
57
+ },
58
+ render: (args) => ({
59
+ components: { ComponentName },
60
+ setup() {
61
+ return { args };
62
+ },
63
+ template: `<ComponentName v-bind="args" />`,
64
+ }),
65
+ };
66
+ ```
67
+
68
+ ### Component with v-model or slots — `StoryFn` with Template
69
+
70
+ Use when the component has `v-model`, reactive state, or named slots that need toggling.
71
+
72
+ ```ts
73
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
74
+ import StorybookComponent from "../ComponentName.vue";
75
+
76
+ interface ComponentStoryArgs {
77
+ modelValue: string;
78
+ propName: string;
79
+ useSlot: boolean;
80
+ slotContent: string;
81
+ }
82
+
83
+ export default {
84
+ title: "Category/Subcategory/ComponentName",
85
+ component: StorybookComponent,
86
+ argTypes: {
87
+ modelValue: {
88
+ control: "text",
89
+ description: "The bound value",
90
+ table: { category: "Model" },
91
+ },
92
+ propName: {
93
+ control: { type: "select" },
94
+ options: ["a", "b"],
95
+ description: "What this prop does",
96
+ table: { category: "Basic" },
97
+ },
98
+ useSlot: {
99
+ control: "boolean",
100
+ description: "Toggle named slot",
101
+ table: { category: "Slots" },
102
+ },
103
+ slotContent: {
104
+ control: "text",
105
+ description: "Content for the slot",
106
+ table: { category: "Slots" },
107
+ },
108
+ },
109
+ args: {
110
+ modelValue: "",
111
+ propName: "a",
112
+ useSlot: false,
113
+ slotContent: "Slot text",
114
+ },
115
+ } as Meta<typeof StorybookComponent>;
116
+
117
+ const Template: StoryFn<ComponentStoryArgs> = (args) => ({
118
+ components: { StorybookComponent },
119
+ setup() {
120
+ const { modelValue, ...otherArgs } = args;
121
+ const inputValue = ref(modelValue);
122
+ return { inputValue, args: otherArgs, useSlot: args.useSlot, slotContent: args.slotContent };
123
+ },
124
+ template: `
125
+ <StorybookComponent v-model="inputValue" v-bind="args">
126
+ <template v-if="useSlot" #slotName>{{ slotContent }}</template>
127
+ </StorybookComponent>
128
+ `,
129
+ });
130
+
131
+ export const Default = Template.bind({});
132
+ Default.args = {};
133
+
134
+ export const WithSlot = Template.bind({});
135
+ WithSlot.args = { useSlot: true, slotContent: "Custom content" };
136
+ ```
137
+
138
+ ## Title format
139
+
140
+ `"Category/Subcategory/ComponentName"` — this becomes the Storybook sidebar path and the
141
+ Playwright `STORY_BASE` slug. The slug is derived by lowercasing and replacing spaces and
142
+ `/` with `-`:
143
+
144
+ | Title | STORY_BASE slug |
145
+ | --------------------------------------------- | ------------------------------------------- |
146
+ | `"Atoms/Text Blocks/HeroText"` | `atoms-text-blocks-herotext` |
147
+ | `"Components/Forms/Input Text/InputTextCore"` | `components-forms-input-text-inputtextcore` |
148
+
149
+ ## argTypes control types
150
+
151
+ | Prop type | `control` |
152
+ | -------------- | -------------------------------- |
153
+ | String | `"text"` |
154
+ | Boolean | `"boolean"` |
155
+ | Number | `{ type: "number", min, max }` |
156
+ | Enum / union | `{ type: "select" }` + `options` |
157
+ | Array / object | `"object"` |
158
+
159
+ ## Notes
160
+
161
+ - Use `table: { category: "..." }` in `argTypes` when a component has many props — it groups
162
+ them in the Storybook controls panel (e.g. `"Model"`, `"Basic"`, `"Validation"`, `"Styling"`, `"Slots"`).
163
+ - Export multiple named stories (`Default`, `WithError`, `Outlined`, etc.) when you want
164
+ Playwright to test distinct visual states via separate story URLs.