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.
- package/.claude/settings.local.json +12 -0
- package/.claude/skills/components/eyebrow-text.md +83 -0
- package/.claude/skills/components/hero-text.md +110 -0
- package/.claude/skills/components/link-text.md +101 -0
- package/.claude/skills/index.md +54 -0
- package/.claude/skills/storybook-add-font.md +101 -0
- package/.claude/skills/storybook-add-story.md +164 -0
- package/.claude/skills/testing-add-playwright.md +167 -0
- package/.claude/skills/testing-add-unit-test.md +185 -0
- package/.claude/skills/theming-override-default.md +250 -0
- package/README.md +26 -0
- package/app/components/01.atoms/text-blocks/link-text/LinkText.vue +66 -0
- package/app/components/01.atoms/text-blocks/link-text/stories/LinkText.stories.ts +140 -0
- package/app/components/01.atoms/text-blocks/link-text/tests/LinkText.spec.ts +168 -0
- package/package.json +2 -1
|
@@ -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.
|