srcdev-nuxt-components 9.2.3 → 9.2.5

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,169 @@
1
+ # InputCopy Component
2
+
3
+ ## Overview
4
+
5
+ `InputCopy` renders a readonly text input with a copy-to-clipboard button. Displays a copyable value (e.g., license key, API token) with optional description text below. Shows visual feedback ("Copied!") in the button after a successful copy. Emits `copy` and `copied` events for consumer-side toast/feedback handling.
6
+
7
+ ---
8
+
9
+ ## Props reference
10
+
11
+ > **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:style-class-passthrough`.
12
+
13
+ | Prop (template form) | Type | Default | Notes |
14
+ | -------------------------- | ---------------------- | ---------------- | ------------------------------------------------------------ |
15
+ | `:value` | `string` | (required) | The text to copy to clipboard (e.g., license key, API token).|
16
+ | `:label` | `string` | (optional) | Short label for the field; used in aria-label if ariaLabel not set. |
17
+ | `:aria-label` | `string` | (optional) | Custom aria-label for the input; overrides label. |
18
+ | `:description` | `string` | (optional) | Helper text displayed below the input. |
19
+ | `:button-text` | `string` | `"Copy"` | Label on the copy button. |
20
+ | `:copied-text` | `string` | `"Copied!"` | Text shown in button after copy succeeds. |
21
+ | `:copied-duration` | `number` | `2000` | Milliseconds the "Copied!" state persists before resetting. |
22
+ | `:is-disabled` | `boolean` | `false` | Disables the copy button. |
23
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root element. |
24
+
25
+ ---
26
+
27
+ ## Events
28
+
29
+ | Event | Payload | Notes |
30
+ | -------- | -------------------- | ---------------------------------------------------------- |
31
+ | `copy` | `value: string` | Emitted when the copy button is clicked (before clipboard write). |
32
+ | `copied` | `value: string` | Emitted after successful clipboard write; fires after `copy`. |
33
+
34
+ ---
35
+
36
+ ## Slots
37
+
38
+ None. Use events to customize feedback behavior.
39
+
40
+ ---
41
+
42
+ ## CSS Token Customization
43
+
44
+ All `--input-copy-*` tokens can be overridden at global, page, or instance scope. See `CONSUMER-STYLING.md` in the component directory for full token documentation and examples.
45
+
46
+ **Common tokens:**
47
+ - `--input-copy-input-bg` — input field background
48
+ - `--input-copy-input-border` — input field border
49
+ - `--input-copy-button-bg` — copy button background
50
+ - `--input-copy-button-bg-hover` — button background on hover
51
+ - `--input-copy-description-color` — description text color
52
+
53
+ ---
54
+
55
+ ## Usage Examples
56
+
57
+ ### License key display (most common)
58
+
59
+ ```vue
60
+ <InputCopy
61
+ :value="licenseKey"
62
+ label="License key"
63
+ description="Your license key is ready to use. Copy it to embed in your application."
64
+ @copied="showSuccessToast('License key copied!')"
65
+ />
66
+ ```
67
+
68
+ ### API token with custom button text
69
+
70
+ ```vue
71
+ <InputCopy
72
+ :value="apiToken"
73
+ label="API token"
74
+ description="Do not share this token with anyone."
75
+ button-text="Copy token"
76
+ copied-text="Token copied!"
77
+ @copy="logCopy"
78
+ @copied="logCopied"
79
+ />
80
+ ```
81
+
82
+ ### Disabled state (e.g., loading or error)
83
+
84
+ ```vue
85
+ <InputCopy
86
+ :value="temporaryCode"
87
+ label="Temporary code"
88
+ button-text="Unavailable"
89
+ :is-disabled="isGenerating"
90
+ description="Code is being generated. Please wait."
91
+ />
92
+ ```
93
+
94
+ ### With custom styling via CSS tokens
95
+
96
+ ```vue
97
+ <InputCopy
98
+ :value="shareLink"
99
+ label="Share link"
100
+ description="Share this link with others"
101
+ style="
102
+ --input-copy-input-bg: #f0f0f0;
103
+ --input-copy-button-bg: var(--brand-primary);
104
+ --input-copy-button-text-color: white;
105
+ "
106
+ />
107
+ ```
108
+
109
+ ### With page-scoped styling
110
+
111
+ In your page's `<style>` block:
112
+
113
+ ```css
114
+ .success-page .input-copy {
115
+ --input-copy-input-border: 2px solid var(--success-color);
116
+ --input-copy-button-bg: var(--success-color);
117
+ --input-copy-description-color: var(--slate-06);
118
+ }
119
+ ```
120
+
121
+ Then in the template:
122
+
123
+ ```vue
124
+ <div class="success-page">
125
+ <InputCopy
126
+ :value="licenseKey"
127
+ label="Your license key"
128
+ description="Copy this key to activate the component in your application."
129
+ />
130
+ </div>
131
+ ```
132
+
133
+ ### With toast feedback (consumer-side)
134
+
135
+ ```vue
136
+ <template>
137
+ <InputCopy
138
+ :value="apiKey"
139
+ label="API key"
140
+ @copied="handleCopied"
141
+ />
142
+ </template>
143
+
144
+ <script setup>
145
+ const handleCopied = (value: string) => {
146
+ // Use your app's toast library
147
+ useToast().success(`Copied: ${value.slice(0, 8)}...`);
148
+ };
149
+ </script>
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Accessibility
155
+
156
+ - Input always has `aria-label` set from `label` or `aria-label` prop
157
+ - Button has descriptive aria-label: "Copy [label] to clipboard"
158
+ - Button is disabled during the "Copied!" feedback window to prevent repeated clicks
159
+ - Icon-only display on mobile (<480px); text shown on desktop for clarity
160
+ - Readonly input has `readonly` attribute; not editable
161
+
162
+ ---
163
+
164
+ ## Notes
165
+
166
+ - **Clipboard API**: Uses native `navigator.clipboard.writeText()`. Fails silently if unavailable (e.g., insecure context).
167
+ - **Button text hide on mobile**: By default, only the copy icon displays on screens <480px. Text appears on desktop (≥480px).
168
+ - **No toast built-in**: This component does not show toasts. Listen to `@copied` event and use your app's toast library for feedback.
169
+ - **Manual reset**: The "Copied!" state automatically resets after `copied-duration` milliseconds. No manual reset needed.
@@ -0,0 +1,163 @@
1
+ # PricingCard Component
2
+
3
+ ## Overview
4
+
5
+ `PricingCard` renders a SaaS-style pricing plan card with plan name, price, description, feature list, and call-to-action button. Supports "Most Popular" highlight state with optional badge. Features are highly customizable via CSS token overrides, and the CTA button can be customized via the `#cta` slot for custom variants and themes.
6
+
7
+ ---
8
+
9
+ ## Props reference
10
+
11
+ > **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:style-class-passthrough`.
12
+
13
+ | Prop (template form) | Type | Default | Notes |
14
+ | -------------------------- | ----------------------------- | ------------ | ------------------------------------------------------------------ |
15
+ | `tag` | `"div" \| "section" \| "article"` | `"article"` | Root element tag for semantic HTML. |
16
+ | `:plan-name` | `string` | (required) | Plan/tier name (e.g., "Basic", "Professional", "Enterprise"). |
17
+ | `:price` | `number` | (required) | Price amount (e.g., `99`, `4.99`). Currency symbol added in template. |
18
+ | `:billing-period` | `string` | `"one-time"` | Billing frequency text (e.g., "monthly", "yearly"). |
19
+ | `:description` | `string` | (optional) | Short plan description. |
20
+ | `:features` | `string[]` | `[]` | Array of feature strings. Rendered with checkmark icons. |
21
+ | `:is-highlighted` | `boolean` | `false` | Highlight state; applies "Most Popular" badge, border, shadow, and scale. |
22
+ | `:cta-text` | `string` | `"Get started"` | Button label text. |
23
+ | `:cta-disabled` | `boolean` | `false` | Disables the CTA button. |
24
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the card root. |
25
+
26
+ ---
27
+
28
+ ## Events
29
+
30
+ | Event | Payload | Notes |
31
+ | ------ | ---------- | ---------------------------------------- |
32
+ | `select` | `planName: string` | Emitted when the CTA button is clicked. |
33
+
34
+ ---
35
+
36
+ ## Slots
37
+
38
+ ### Default slot (features)
39
+
40
+ Override the default feature list rendering. Receives no slot scope.
41
+
42
+ ```vue
43
+ <PricingCard plan-name="Pro" :price="99">
44
+ <template #features>
45
+ <li>Custom feature HTML here</li>
46
+ </template>
47
+ </PricingCard>
48
+ ```
49
+
50
+ ### #cta slot
51
+
52
+ Customize the CTA button. Default renders `InputButtonCore` with theme support.
53
+
54
+ **Slot scope:**
55
+ - `ctaText: string` — button label from `:cta-text` prop
56
+ - `isDisabled: boolean` — button disabled state from `:cta-disabled` prop
57
+ - `planName: string` — plan name for context
58
+ - `onSelect: () => void` — callback to trigger `select` event
59
+
60
+ ```vue
61
+ <PricingCard plan-name="Pro" :price="99" @select="handleSelect">
62
+ <template #cta="{ ctaText, isDisabled, onSelect }">
63
+ <InputButtonCore
64
+ :button-text="ctaText"
65
+ variant="secondary"
66
+ theme="custom"
67
+ :readonly="isDisabled"
68
+ @click="onSelect"
69
+ />
70
+ </template>
71
+ </PricingCard>
72
+ ```
73
+
74
+ ---
75
+
76
+ ## CSS Token Customization
77
+
78
+ All `--pricing-card-*` tokens can be overridden at global, page, or instance scope. See `CONSUMER-STYLING.md` in the component directory for full token documentation and examples.
79
+
80
+ **Common tokens:**
81
+ - `--pricing-card-padding` — card internal spacing
82
+ - `--pricing-card-border-radius` — card corners
83
+ - `--pricing-card-cta-bg` — button background
84
+ - `--pricing-card-highlight-border` — highlighted card border
85
+ - `--pricing-card-name-color` — plan name text color
86
+
87
+ ---
88
+
89
+ ## Usage Examples
90
+
91
+ ### Single card with default button
92
+
93
+ ```vue
94
+ <PricingCard
95
+ plan-name="Basic"
96
+ :price="29.99"
97
+ billing-period="monthly"
98
+ description="Perfect for getting started"
99
+ :features="['Feature 1', 'Feature 2', 'Feature 3']"
100
+ cta-text="Get started"
101
+ @select="handleSelect"
102
+ />
103
+ ```
104
+
105
+ ### Highlighted "Most Popular" card
106
+
107
+ ```vue
108
+ <PricingCard
109
+ plan-name="Professional"
110
+ :price="79.99"
111
+ billing-period="monthly"
112
+ :is-highlighted="true"
113
+ :features="['All Basic features', 'Priority support', 'Advanced analytics']"
114
+ @select="handleSelect"
115
+ />
116
+ ```
117
+
118
+ ### Card with custom button variant
119
+
120
+ ```vue
121
+ <PricingCard
122
+ plan-name="Enterprise"
123
+ :price="199.99"
124
+ billing-period="monthly"
125
+ description="Everything you need"
126
+ :features="['Unlimited access', 'Dedicated support', 'Custom integrations']"
127
+ @select="handleSelect"
128
+ >
129
+ <template #cta="{ ctaText, isDisabled, onSelect }">
130
+ <InputButtonCore
131
+ :button-text="ctaText"
132
+ variant="primary"
133
+ :readonly="isDisabled"
134
+ @click="onSelect"
135
+ />
136
+ </template>
137
+ </PricingCard>
138
+ ```
139
+
140
+ ### Three-column grid with token customization
141
+
142
+ ```vue
143
+ <div class="pricing-grid">
144
+ <PricingCard
145
+ v-for="plan in plans"
146
+ :key="plan.id"
147
+ :plan-name="plan.name"
148
+ :price="plan.price"
149
+ :is-highlighted="plan.id === 'pro'"
150
+ :features="plan.features"
151
+ @select="handleSelect"
152
+ style="--pricing-card-padding: 2.5rem; --pricing-card-cta-bg: var(--brand-primary)"
153
+ />
154
+ </div>
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Notes
160
+
161
+ - Feature checkmarks are always rendered with a teal checkmark (`✓`). Customize the color via `--teal-06` or component tokens.
162
+ - The `#cta` slot is preferred over CSS-only button customization when you need variant or theme changes.
163
+ - Highlighted cards apply a 1.05x scale transform — consider z-index when nesting in flex/grid layouts.
@@ -48,6 +48,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
48
48
  ├── qa-panel.md — collapsible dev-only panel for toggling component props live on a page (demo pages and consuming apps)
49
49
  ├── release-notes.md — produce release notes as a fenced markdown block from git log
50
50
  ├── pull-request-description.md — produce a PR description as a fenced markdown block from git diff vs main
51
+ ├── using-component-skills.md — discover and use component skills in consumer apps: where skills land, browsing patterns, workflow for deciding build vs. compose
51
52
  ├── composable-canonical-url.md — useCanonicalUrl: set <link rel="canonical"> from runtimeConfig.public.canonicalHost; layout setup, node types
52
53
  ├── composable-whatsapp.md — useWhatsApp: open pre-filled wa.me link from form payload; runtime config, security, usage
53
54
  ├── composable-zod-validation.md — useZodValidation: schema-driven form validation, error binding, submit flow, API error push
@@ -73,7 +74,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
73
74
  ├── expanding-panel.md — ExpandingPanel v-model, forceOpened, slots (summary/icon/content), ARIA wiring
74
75
  ├── glass-panel.md — GlassPanel props, slots, CSS token API (--glass-panel-bg/border-color/shadow/highlight), theming override
75
76
  ├── navigation-horizontal.md — NavigationHorizontal props, NavItemData type, CSS token API, import path gotcha
76
- ├── input-copy-core.md InputCopyCore: readonly copy-to-clipboard input; props, emits, slots, CSS classes, usage
77
+ ├── pricing-card.md PricingCard: SaaS-style plan card with highlight, feature list, #cta slot for button customization, CSS token API
78
+ ├── input-copy.md — InputCopy: readonly copy-to-clipboard input, visual feedback, Clipboard API, accessibility, CSS token API
77
79
  ├── banner-video.md — BannerVideo: full-width hero video banner, depth tier system, objectFit/objectPosition, playIcon/pauseIcon/toggle-icon slot, reduced-motion fallback, CSS tokens
78
80
  ├── grid-stack.md — GridStack: CSS Grid z-axis stacking, slot API, z-order rules, sizing, video+overlay and image+text patterns
79
81
  ├── scroll-reveal-frame.md — ScrollRevealFrame: generic parallax clipping frame, slot API, image grid pattern, CSS tokens, browser support
@@ -2,12 +2,14 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- Prevent two common "forgot to run after install" problems in a consuming app:
5
+ Prevent common "forgot to run after install" problems in a consuming app:
6
6
 
7
7
  1. `nuxt prepare` — generates Nuxt type declarations. Skipping it causes TypeScript errors after install or package updates.
8
8
  2. `npm run setup:claude` — copies the latest `srcdev-nuxt-components` skills into `.claude/skills/srcdev-nuxt-components/`. Skipping it leaves Claude working from stale skill docs after a package update.
9
9
 
10
- A `postinstall` script runs both automatically after every `npm install`.
10
+ Additionally, `srcdev-nuxt-components` automatically copies VSCode snippet files (`.code-snippets`) to your `.vscode/` folder during its own postinstall, so components' code snippets are available immediately.
11
+
12
+ A consumer app's `postinstall` script runs both automatically after every `npm install`.
11
13
 
12
14
  ## Steps
13
15
 
@@ -15,11 +17,17 @@ A `postinstall` script runs both automatically after every `npm install`.
15
17
 
16
18
  ```json
17
19
  "scripts": {
18
- "setup:claude": "cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components",
19
- "postinstall": "nuxt prepare && npm run setup:claude"
20
+ "setup:claude": "mkdir -p .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components",
21
+ "postinstall": "node node_modules/srcdev-nuxt-components/scripts/copy-snippets.mjs && nuxt prepare && npm run setup:claude"
20
22
  }
21
23
  ```
22
24
 
25
+ This postinstall hook does three things in order:
26
+
27
+ 1. Copies VSCode snippets from the layer to your `.vscode/` folder
28
+ 2. Runs `nuxt prepare` to generate type declarations
29
+ 3. Runs `setup:claude` to copy skills into `.claude/skills/srcdev-nuxt-components/`
30
+
23
31
  ### 2. Check whether your app needs an env flag for nuxt prepare
24
32
 
25
33
  Some apps set an env var to switch `nuxt.config.ts` behaviour when running outside the full dev server (e.g. `NUXT_STANDALONE=true`). Check your own `nuxt.config.ts` — if it gates any config behind such a variable, add it to the `postinstall` command:
@@ -40,6 +48,7 @@ From this point on, `npm install` and `npm ci` trigger both steps automatically.
40
48
 
41
49
  ## Notes
42
50
 
51
+ - **Snippet copy must run in consumer app**: npm doesn't invoke postinstall hooks for dependencies. Each consumer app must explicitly call `scripts/copy-snippets.mjs` in its own postinstall to copy VSCode snippets to `.vscode/`. The layer's postinstall is used when the layer runs standalone, but consumers are responsible for their own `.vscode/` folder.
43
52
  - Skills land in `.claude/skills/srcdev-nuxt-components/` — safe to re-run without overwriting your own project's skills.
44
- - `postinstall` also fires on `npm ci`, so CI environments get the skills too if they have a `.claude/` directory in the project.
45
- - If you do not want `postinstall` running in CI, guard it: `"postinstall": "[ \"$CI\" = \"true\" ] || (nuxt prepare && npm run setup:claude)"`.
53
+ - `postinstall` also fires on `npm ci`, so CI environments get the skills and snippets too if they have a `.claude/` directory in the project.
54
+ - If you do not want `postinstall` running in CI, guard it: `"postinstall": "[ \"$CI\" = \"true\" ] || (node node_modules/srcdev-nuxt-components/scripts/copy-snippets.mjs && nuxt prepare && npm run setup:claude)"`.
@@ -0,0 +1,215 @@
1
+ # Using Component Skills
2
+
3
+ ## Overview
4
+
5
+ Component skills are reference documentation files for every reusable component in `srcdev-nuxt-components`. They land in your consumer app after running `npm run setup:claude` and serve two purposes:
6
+
7
+ 1. **As human-readable reference** — browse component skills to understand props, slots, CSS tokens, and usage patterns
8
+ 2. **As discovery tool** — before building custom components, check if a suitable pattern already exists in the layer
9
+
10
+ ---
11
+
12
+ ## Setup
13
+
14
+ ### 1. Copy skills from the layer
15
+
16
+ After scaffolding a new Nuxt app that extends `srcdev-nuxt-components`, run:
17
+
18
+ ```bash
19
+ npm run setup:claude
20
+ ```
21
+
22
+ This copies all skills (including component skills) into `.claude/skills/srcdev-nuxt-components/`.
23
+
24
+ ### 2. After upgrading the layer
25
+
26
+ When you upgrade `srcdev-nuxt-components` to a new version (which may include new components), re-run the command to refresh:
27
+
28
+ ```bash
29
+ npm run setup:claude
30
+ ```
31
+
32
+ ---
33
+
34
+ ## File structure
35
+
36
+ Component skills land in `.claude/skills/srcdev-nuxt-components/components/`:
37
+
38
+ ```
39
+ .claude/
40
+ ├── skills/
41
+ │ └── srcdev-nuxt-components/
42
+ │ ├── index.md — overview of all skills
43
+ │ ├── new-app-scaffold.md — scaffolding a new app
44
+ │ ├── theming-colour-ramps.md — theme customization
45
+ │ ├── composable-*.md — utility composables
46
+ │ └── components/
47
+ │ ├── pricing-card.md — SaaS pricing plan card
48
+ │ ├── input-copy.md — copy-to-clipboard input
49
+ │ ├── expanding-panel.md — single expand/collapse panel
50
+ │ ├── accordian-core.md — native accordion (grouped panels)
51
+ │ ├── display-avatar.md — circular avatar component
52
+ │ ├── card-core.md — generic card container
53
+ │ ├── auto-grid.md — responsive auto-fit grid
54
+ │ ├── display-dialog.md — native dialog overlay
55
+ │ ├── display-toast.md — toast notifications
56
+ │ └── ... (40+ more components)
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Discovering components
62
+
63
+ ### Via your file explorer
64
+
65
+ Browse `.claude/skills/srcdev-nuxt-components/components/` to see all available components with one-line descriptions.
66
+
67
+ ### Via grep
68
+
69
+ Search for components by pattern:
70
+
71
+ ```bash
72
+ # Find all form-related components
73
+ grep -l "input\|form\|button" .claude/skills/srcdev-nuxt-components/components/*.md
74
+
75
+ # Find components with slot patterns
76
+ grep -l "slot" .claude/skills/srcdev-nuxt-components/components/*.md
77
+
78
+ # Find components with CSS token customization
79
+ grep -l "CSS token" .claude/skills/srcdev-nuxt-components/components/*.md
80
+ ```
81
+
82
+ ### Via Claude Code
83
+
84
+ If your `.claude/settings.json` includes the layer's skills in `additionalDirectories`, Claude can reference them:
85
+
86
+ ```json
87
+ {
88
+ "permissions": {
89
+ "additionalDirectories": [
90
+ "path/to/srcdev-nuxt-components/.claude/skills/components"
91
+ ]
92
+ }
93
+ }
94
+ ```
95
+
96
+ Then ask Claude: *"What button components are available in the layer?"* or *"Show me the expanding-panel skill."*
97
+
98
+ ---
99
+
100
+ ## Understanding a component skill
101
+
102
+ Each component skill documents:
103
+
104
+ ### Props
105
+
106
+ | Prop | Type | Default | Notes |
107
+ | --- | --- | --- | --- |
108
+ | `:prop-name` | type | default | What this prop does |
109
+
110
+ ### Slots
111
+
112
+ Explains what content goes inside each slot and provides scoped variables if applicable.
113
+
114
+ ### Events / Emits
115
+
116
+ When the component emits events and what data they carry.
117
+
118
+ ### CSS Tokens
119
+
120
+ All `--component-*` tokens available for customization via global/page/instance scope.
121
+
122
+ ### Usage Examples
123
+
124
+ Practical examples showing:
125
+ - Basic usage (simplest case)
126
+ - Common customizations
127
+ - Advanced patterns (slots, tokens, events)
128
+
129
+ ### Accessibility
130
+
131
+ ARIA attributes, keyboard support, screen reader behavior.
132
+
133
+ ### Notes
134
+
135
+ Edge cases, browser support, performance considerations, common gotchas.
136
+
137
+ ---
138
+
139
+ ## Workflow: Build a feature
140
+
141
+ ### 1. Understand the requirement
142
+
143
+ *"I need a contact form with error handling and field validation."*
144
+
145
+ ### 2. Search skills for similar patterns
146
+
147
+ ```bash
148
+ grep -l "form\|input\|validation" .claude/skills/srcdev-nuxt-components/components/*.md
149
+ ```
150
+
151
+ ### 3. Read relevant skills
152
+
153
+ Open 2–3 matching skills to see:
154
+ - What components already handle form inputs?
155
+ - What slots and CSS tokens do they expose?
156
+ - What validation patterns are documented?
157
+
158
+ ### 4. Decide: build vs. compose
159
+
160
+ **Compose existing components** if the layer has:
161
+ - Input components with validation props
162
+ - Card/container components for layout
163
+ - Button variants for form actions
164
+
165
+ **Build custom component** if you need something specific the layer doesn't provide.
166
+
167
+ ---
168
+
169
+ ## Examples
170
+
171
+ ### Pricing page
172
+
173
+ *Task: Build a pricing plan comparison with 3 tiers.*
174
+
175
+ 1. Search skills: `grep -l "price\|plan\|tier" components/*.md`
176
+ 2. Read `pricing-card.md` — describes PricingCard component, props, #cta slot, CSS tokens
177
+ 3. Use PricingCard in a grid layout, customize buttons via slot, override token colors via page-scoped CSS
178
+
179
+ ### FAQ section
180
+
181
+ *Task: Add expandable FAQ items.*
182
+
183
+ 1. Search skills: `grep -l "expand\|accordion\|collaps" components/*.md`
184
+ 2. Read `expanding-panel.md` — describes single panel behavior, v-model, slots
185
+ 3. Use ExpandingPanel in a v-for loop for FAQ items (no need to build custom accordion)
186
+
187
+ ### License key display
188
+
189
+ *Task: Show a copyable license key on success page.*
190
+
191
+ 1. Search skills: `grep -l "copy\|clipboard\|readonly" components/*.md`
192
+ 2. Read `input-copy.md` — describes readonly input, copy button, events, CSS tokens
193
+ 3. Use InputCopy component, listen to @copied event, show toast via consumer's toast library
194
+
195
+ ---
196
+
197
+ ## Updating skills
198
+
199
+ When the layer releases a new component or updates an existing one:
200
+
201
+ 1. **Upgrade the package**: `npm install srcdev-nuxt-components@latest`
202
+ 2. **Refresh skills**: `npm run setup:claude`
203
+ 3. **Read the new skill** to understand the new component's API
204
+
205
+ The skills always stay in sync with the layer's released version.
206
+
207
+ ---
208
+
209
+ ## Tips
210
+
211
+ - **Skim the index.md** in `.claude/skills/srcdev-nuxt-components/` to get an overview of all available skills
212
+ - **Reference skills when asking Claude** — "Here's the InputCopy skill: [paste content]. How do I customize the button color?"
213
+ - **CSS tokens are your friend** — most components expose tokens for customization without component changes
214
+ - **Slots > props for complexity** — if you need custom button behavior, use a #cta slot instead of adding button props to the component
215
+ - **Check accessibility notes** — ARIA labels, keyboard support, and screen reader behavior are documented per component
@@ -18,7 +18,7 @@
18
18
  ]
19
19
  },
20
20
  "SRCDEV PricingCard Highlighted": {
21
- "description": "PricingCard with 'Most Popular' highlight",
21
+ "description": "PricingCard with 'Most Popular' highlight and custom button",
22
22
  "scope": "vue,html",
23
23
  "body": [
24
24
  "<PricingCard",
@@ -33,7 +33,16 @@
33
33
  " '$7Feature 3',",
34
34
  " ]\"",
35
35
  " @select=\"$8handleSelect\"",
36
- "/>"
36
+ ">",
37
+ " <template #cta=\"{ ctaText, isDisabled, onSelect }\">",
38
+ " <InputButtonCore",
39
+ " :button-text=\"ctaText\"",
40
+ " variant=\"$9primary\"",
41
+ " :readonly=\"isDisabled\"",
42
+ " @click=\"onSelect\"",
43
+ " />",
44
+ " </template>",
45
+ "</PricingCard>"
37
46
  ]
38
47
  },
39
48
  "SRCDEV PricingCard Grid": {
@@ -69,6 +78,30 @@
69
78
  "</div>"
70
79
  ]
71
80
  },
81
+ "SRCDEV PricingCard Custom Button": {
82
+ "description": "PricingCard with custom button variant via #cta slot",
83
+ "scope": "vue,html",
84
+ "body": [
85
+ "<PricingCard",
86
+ " planName=\"$1Premium\"",
87
+ " :price=\"$2799\"",
88
+ " billingPeriod=\"$3one-time\"",
89
+ " description=\"$4All features included.\"",
90
+ " :features=\"['$5Feature 1', '$6Feature 2']\"",
91
+ " @select=\"$7handleSelect\"",
92
+ ">",
93
+ " <template #cta=\"{ ctaText, isDisabled, onSelect }\">",
94
+ " <InputButtonCore",
95
+ " :button-text=\"ctaText\"",
96
+ " variant=\"$8secondary\"",
97
+ " theme=\"$9default\"",
98
+ " :readonly=\"isDisabled\"",
99
+ " @click=\"onSelect\"",
100
+ " />",
101
+ " </template>",
102
+ "</PricingCard>"
103
+ ]
104
+ },
72
105
  "SRCDEV PricingCard CSS Override": {
73
106
  "description": "CSS override scaffold for PricingCard tokens",
74
107
  "scope": "css",
package/README.md CHANGED
@@ -43,16 +43,22 @@ Skills are copied into `.claude/skills/srcdev-nuxt-components/` so they never co
43
43
 
44
44
  ### Automate with postinstall (recommended)
45
45
 
46
- To ensure skills are always up to date and `nuxt prepare` is never forgotten, combine both into a `postinstall` script. npm runs this automatically after every `npm install`:
46
+ To ensure skills are always up to date, VSCode snippets are installed, and `nuxt prepare` is never forgotten, add a `postinstall` script. npm runs this automatically after every `npm install`:
47
47
 
48
48
  ```json
49
49
  "scripts": {
50
- "setup:claude": "cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components",
51
- "postinstall": "nuxt prepare && npm run setup:claude"
50
+ "setup:claude": "mkdir -p .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-components/.claude/skills/. .claude/skills/srcdev-nuxt-components",
51
+ "postinstall": "node node_modules/srcdev-nuxt-components/scripts/copy-snippets.mjs && nuxt prepare && npm run setup:claude"
52
52
  }
53
53
  ```
54
54
 
55
- > If your app uses a standalone env flag for `nuxt prepare` (e.g. `NUXT_STANDALONE=true`), include it in the `postinstall` command. This is project-specific — check your own `nuxt.config.ts` to confirm whether it is needed.
55
+ **What this does:**
56
+
57
+ 1. `copy-snippets.mjs` — copies VSCode snippets (`.code-snippets` files) from the layer to your `.vscode/` folder
58
+ 2. `nuxt prepare` — generates Nuxt type declarations
59
+ 3. `setup:claude` — copies skills into `.claude/skills/srcdev-nuxt-components/`
60
+
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. If your app uses a standalone env flag for `nuxt prepare` (e.g. `NUXT_STANDALONE=true`), add it before `nuxt prepare`. This is project-specific — check your own `nuxt.config.ts` to confirm whether it is needed.
56
62
 
57
63
  ---
58
64
 
@@ -5,6 +5,9 @@
5
5
  All `--pricing-card-*` tokens are the stable override surface. Set them at any scope (global, page,
6
6
  or instance) without touching the component itself.
7
7
 
8
+ Note: the price amount defaults to a `$` prefix. Pass `currency-symbol="£"` (or any other symbol) to
9
+ override it — there is no locale-aware formatting, just a literal prefix character.
10
+
8
11
  | Token | Default | Controls |
9
12
  |---|---|---|
10
13
  | `--pricing-card-border` | `1px solid var(--slate-03)` | Card border |
@@ -26,6 +29,7 @@ or instance) without touching the component itself.
26
29
  | `--pricing-card-period-color` | `#666` | Billing period text colour |
27
30
  | `--pricing-card-description-color` | `#555` | Plan description text colour |
28
31
  | `--pricing-card-feature-color` | `#333` | Feature list text colour |
32
+ | `--pricing-card-cta-align` | `flex-start` | CTA wrapper `align-self` — set to `stretch`, `center`, or `flex-end` to change CTA alignment/width within the card |
29
33
  | `--pricing-card-cta-bg` | `var(--theme-button-primary-surface)` | CTA button background |
30
34
  | `--pricing-card-cta-bg-hover` | `var(--theme-button-primary-surface-hover)` | CTA button background on hover |
31
35
  | `--pricing-card-cta-text` | `var(--theme-button-primary-text)` | CTA button text colour |
@@ -5,7 +5,7 @@
5
5
  <h3 class="pricing-card__name">{{ planName }}</h3>
6
6
 
7
7
  <div class="pricing-card__price">
8
- <span class="pricing-card__amount">${{ price }}</span>
8
+ <span class="pricing-card__amount">{{ currencySymbol }}{{ price }}</span>
9
9
  <span v-if="billingPeriod" class="pricing-card__period">{{ billingPeriod }}</span>
10
10
  </div>
11
11
 
@@ -42,6 +42,7 @@ interface Props {
42
42
  tag?: "div" | "section" | "article";
43
43
  planName: string;
44
44
  price: number;
45
+ currencySymbol?: string;
45
46
  billingPeriod?: string;
46
47
  description?: string;
47
48
  features?: string[];
@@ -53,6 +54,7 @@ interface Props {
53
54
 
54
55
  const props = withDefaults(defineProps<Props>(), {
55
56
  tag: "article",
57
+ currencySymbol: "$",
56
58
  billingPeriod: "one-time",
57
59
  description: undefined,
58
60
  features: () => [],
@@ -108,6 +110,7 @@ watch(
108
110
  --_description-color: var(--pricing-card-description-color, #555);
109
111
  --_feature-color: var(--pricing-card-feature-color, #333);
110
112
 
113
+ --_cta-align: var(--pricing-card-cta-align, flex-start);
111
114
  --_cta-bg: var(--pricing-card-cta-bg, var(--theme-button-primary-surface));
112
115
  --_cta-bg-hover: var(--pricing-card-cta-bg-hover, var(--theme-button-primary-surface-hover));
113
116
  --_cta-text: var(--pricing-card-cta-text, var(--theme-button-primary-text));
@@ -206,7 +209,7 @@ watch(
206
209
  }
207
210
 
208
211
  .pricing-card__cta {
209
- align-self: flex-start;
212
+ align-self: var(--_cta-align);
210
213
 
211
214
  :deep(.input-button-core) {
212
215
  padding: var(--_cta-padding);
@@ -19,6 +19,19 @@ describe("PricingCard", () => {
19
19
  expect(wrapper.text()).toContain("Get started");
20
20
  });
21
21
 
22
+ it("uses custom currency symbol when provided", () => {
23
+ const wrapper = mount(PricingCard, {
24
+ props: {
25
+ planName: "Single Site",
26
+ price: 49,
27
+ currencySymbol: "£",
28
+ },
29
+ });
30
+
31
+ expect(wrapper.text()).toContain("£49");
32
+ expect(wrapper.text()).not.toContain("$49");
33
+ });
34
+
22
35
  it("renders features from props", () => {
23
36
  const features = ["Feature 1", "Feature 2", "Feature 3"];
24
37
  const wrapper = mount(PricingCard, {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.2.3",
4
+ "version": "9.2.5",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",
8
8
  "scripts": {
9
+ "postinstall": "node scripts/copy-snippets.mjs",
9
10
  "clean": "rm -rf .nuxt && rm -rf .output",
10
11
  "cleanall": "rm -rf node_modules && rm -rf .nuxt && rm -rf .output && rm package-lock.json",
11
12
  "reinstall": "rm -rf node_modules && npm install",
@@ -0,0 +1,38 @@
1
+ import { copyFileSync, mkdirSync, readdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
6
+ const srcDir = join(__dirname, '../.vscode');
7
+ const destDir = join(process.cwd(), '.vscode');
8
+
9
+ // Only run when installed as a package (not in the layer's own repo)
10
+ if (process.cwd() === join(__dirname, '..')) {
11
+ console.log('⊘ Skipping snippet copy (running in layer repo)');
12
+ process.exit(0);
13
+ }
14
+
15
+ mkdirSync(destDir, { recursive: true });
16
+
17
+ // Find and copy all .code-snippets files
18
+ try {
19
+ const files = readdirSync(srcDir).filter(file => file.endsWith('.code-snippets'));
20
+
21
+ if (files.length === 0) {
22
+ console.log('⊘ No snippet files found');
23
+ process.exit(0);
24
+ }
25
+
26
+ files.forEach(file => {
27
+ try {
28
+ copyFileSync(join(srcDir, file), join(destDir, file));
29
+ console.log(`✓ Copied VSCode snippet: ${file}`);
30
+ } catch (err) {
31
+ console.warn(`⚠ Failed to copy ${file}:`, err.message);
32
+ }
33
+ });
34
+
35
+ console.log(`✓ VSCode snippets installed to .vscode/`);
36
+ } catch (err) {
37
+ console.warn('⚠ Snippet installation skipped:', err.message);
38
+ }