srcdev-nuxt-components 9.1.16 → 9.1.18

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.
@@ -19,7 +19,9 @@
19
19
  "Edit(/.claude/skills/components/**)",
20
20
  "Bash(npx nuxi:*)",
21
21
  "Bash(node -e \"const t = require\\('/Users/simoncornforth/websites/nuxt-components/node_modules/pinia-plugin-persistedstate'\\); console.log\\(Object.keys\\(t\\)\\)\")",
22
- "Bash(npx vue-tsc:*)"
22
+ "Bash(npx vue-tsc:*)",
23
+ "Bash(git -C /Users/simoncornforth/websites/nuxt-components log --oneline -5)",
24
+ "Bash(git -C /Users/simoncornforth/websites/nuxt-components show 16ac4ef --stat)"
23
25
  ],
24
26
  "additionalDirectories": [
25
27
  "/Users/simoncornforth/websites/nuxt-components/app/components/01.atoms/content-wrappers/content-width",
@@ -0,0 +1,66 @@
1
+ # useColourScheme Composable
2
+
3
+ ## Overview
4
+
5
+ `useColourScheme` provides reactive dark/light/auto mode switching, persisted in `localStorage` and applied via a CSS class on `<html>`. It reads an `enabled` flag from runtime config so consuming apps can disable the feature entirely.
6
+
7
+ **This composable ships inside the `srcdev-nuxt-components` layer** (`app/composables/useColourScheme.ts`). It is auto-imported via the Nuxt layer — **do not create a local copy** in the consuming app.
8
+
9
+ ## Prerequisites
10
+
11
+ - `runtimeConfig.public.colourScheme.enabled` set in the consuming app's `nuxt.config.ts` (see below)
12
+ - If disabled, the `colour-scheme-disable.md` skill covers the one-line CSS override needed to lock the theme
13
+
14
+ ## Runtime config
15
+
16
+ The composable reads `config.public.colourScheme.enabled` at runtime. Add this to the consuming app's `nuxt.config.ts`:
17
+
18
+ ```ts
19
+ // nuxt.config.ts
20
+ runtimeConfig: {
21
+ public: {
22
+ colourScheme: {
23
+ enabled: true, // set false to disable switching (see colour-scheme-disable.md)
24
+ },
25
+ },
26
+ },
27
+ ```
28
+
29
+ When `enabled` is `false`, `useColourScheme()` returns `{ currentColourScheme }` but the watcher and `localStorage` logic are skipped — `currentColourScheme` stays at `"auto"` and the composable is inert.
30
+
31
+ ## Usage
32
+
33
+ ```ts
34
+ const { currentColourScheme } = useColourScheme()
35
+ // currentColourScheme is a Ref<"light" | "dark" | "auto">
36
+ ```
37
+
38
+ To let the user toggle the scheme, bind `currentColourScheme` to a control:
39
+
40
+ ```vue
41
+ <select v-model="currentColourScheme">
42
+ <option value="auto">Auto</option>
43
+ <option value="light">Light</option>
44
+ <option value="dark">Dark</option>
45
+ </select>
46
+ ```
47
+
48
+ Setting `currentColourScheme.value` triggers the watcher, which writes to `localStorage` and calls `applyColourScheme()` to update the `<html>` class immediately.
49
+
50
+ ## How scheme application works
51
+
52
+ The layer ships a head script (`utils/colour-scheme-init.ts`) that runs before paint to read `localStorage` and apply the correct class to `<html>`, preventing flash of wrong theme. `useColourScheme` then syncs its reactive state to match on `onMounted`.
53
+
54
+ The three valid values are:
55
+
56
+ | Value | Behaviour |
57
+ |---|---|
58
+ | `"auto"` | Follows `prefers-color-scheme` media query |
59
+ | `"light"` | Forces light theme regardless of OS setting |
60
+ | `"dark"` | Forces dark theme regardless of OS setting |
61
+
62
+ ## Notes
63
+
64
+ - `onMounted` is used to read `localStorage` — `currentColourScheme` is always `"auto"` during SSR and hydrates client-side. Do not read it server-side.
65
+ - To disable scheme switching completely in a consuming app, set `enabled: false` in runtime config **and** follow `colour-scheme-disable.md` to lock the CSS to a single theme.
66
+ - The composable name exported from the layer is `useColourScheme` (named export).
@@ -0,0 +1,78 @@
1
+ # useDialogControls Composable
2
+
3
+ ## Overview
4
+
5
+ `useDialogControls` manages open/closed state for one or more named dialogs (modals, confirmation panels, etc.) with optional confirm/cancel callbacks. It is the standard pattern for dialog orchestration in consuming apps.
6
+
7
+ **This composable ships inside the `srcdev-nuxt-components` layer** (`app/composables/useDialogControls.ts`). It is auto-imported via the Nuxt layer — **do not create a local copy** in the consuming app.
8
+
9
+ ## Usage
10
+
11
+ ### 1. Initialise named dialogs
12
+
13
+ Call `initialiseDialogs` with an array of string IDs — one per dialog you need to control:
14
+
15
+ ```ts
16
+ const { dialogsConfig, controlDialogs, initialiseDialogs, registerDialogCallbacks } = useDialogControls()
17
+
18
+ initialiseDialogs(["confirmDelete", "editProfile"])
19
+ ```
20
+
21
+ Each ID gets a reactive boolean in `dialogsConfig` (initially `false` = closed).
22
+
23
+ ### 2. Bind to a dialog component
24
+
25
+ Use `dialogsConfig[id]` as the `v-model` or `:open` prop on your dialog:
26
+
27
+ ```vue
28
+ <ExpandingPanel v-model="dialogsConfig.confirmDelete">
29
+ <template #summary>Confirm delete</template>
30
+ <template #content>
31
+ <p>Are you sure?</p>
32
+ <button @click="controlDialogs('confirmDelete', false, 'confirm')">Yes, delete</button>
33
+ <button @click="controlDialogs('confirmDelete', false, 'cancel')">Cancel</button>
34
+ </template>
35
+ </ExpandingPanel>
36
+ ```
37
+
38
+ ### 3. Open and close dialogs
39
+
40
+ ```ts
41
+ // Open
42
+ controlDialogs("confirmDelete", true)
43
+
44
+ // Close without action
45
+ controlDialogs("confirmDelete", false)
46
+
47
+ // Close and fire a callback
48
+ controlDialogs("confirmDelete", false, "confirm") // fires onConfirm if registered
49
+ controlDialogs("confirmDelete", false, "cancel") // fires onCancel if registered
50
+ ```
51
+
52
+ ### 4. Register callbacks (optional)
53
+
54
+ Register confirm/cancel callbacks before the dialog is opened:
55
+
56
+ ```ts
57
+ registerDialogCallbacks("confirmDelete", {
58
+ onConfirm: () => deleteItem(),
59
+ onCancel: () => console.log("Cancelled"),
60
+ })
61
+ ```
62
+
63
+ Callbacks fire when `controlDialogs` is called with a matching action string.
64
+
65
+ ## API reference
66
+
67
+ | Return value | Description |
68
+ |---|---|
69
+ | `dialogsConfig` | Reactive object — `{ [id]: boolean }`. Bind to dialog `v-model` or `:open`. |
70
+ | `initialiseDialogs(ids)` | Seeds `dialogsConfig` with `false` for each ID. Call once in `<script setup>`. |
71
+ | `controlDialogs(name, state, action?)` | Set `dialogsConfig[name]` to `state`. If `state` is `false` and `action` is provided, fires the registered callback before closing. |
72
+ | `registerDialogCallbacks(id, { onConfirm?, onCancel? })` | Register lifecycle callbacks for a dialog ID. |
73
+
74
+ ## Notes
75
+
76
+ - `useDialogControls` is scoped to the component instance — each component that calls it gets its own `dialogsConfig`. It is not a global store; do not expect state to persist across components.
77
+ - For a single dialog, `initialiseDialogs(["myDialog"])` is still required — omitting it means `dialogsConfig.myDialog` is `undefined` and reactivity won't work.
78
+ - `controlDialogs` checks for the callback before setting state, so the callback always runs before the dialog closes.
@@ -0,0 +1,97 @@
1
+ # useTooltipsGuide Composable
2
+
3
+ ## Overview
4
+
5
+ `useTooltipsGuide` runs a sequential popover guide — it finds all `[popover]` elements inside a container, shows them one by one, and waits for the user to dismiss each before advancing. Useful for onboarding flows and feature introductions.
6
+
7
+ **This composable ships inside the `srcdev-nuxt-components` layer** (`app/composables/useTooltips.ts`, exported as `useTooltipsGuide`). It is auto-imported via the Nuxt layer — **do not create a local copy** in the consuming app.
8
+
9
+ ## Prerequisites
10
+
11
+ - Uses the native HTML Popover API — supported in all modern browsers (Chrome 114+, Firefox 125+, Safari 17+)
12
+ - Popovers must have `id` attributes and corresponding trigger buttons with `popovertarget` and `popovertargetaction="toggle"` attributes
13
+ - Close buttons inside each popover must have `popovertargetaction="hide"`
14
+
15
+ ## Usage
16
+
17
+ ### 1. Mark up the popovers
18
+
19
+ Each step in the guide is a native `[popover]` element. Include a trigger button (used internally to open the popover respecting anchor positioning) and a close button:
20
+
21
+ ```vue
22
+ <div ref="guideContainerRef">
23
+ <button popovertarget="step-1" popovertargetaction="toggle" style="display:none">Open step 1</button>
24
+ <div id="step-1" popover>
25
+ <p>Welcome! This is step one.</p>
26
+ <button popovertarget="step-1" popovertargetaction="hide">Got it</button>
27
+ </div>
28
+
29
+ <button popovertarget="step-2" popovertargetaction="toggle" style="display:none">Open step 2</button>
30
+ <div id="step-2" popover>
31
+ <p>This is step two.</p>
32
+ <button popovertarget="step-2" popovertargetaction="hide">Got it</button>
33
+ </div>
34
+ </div>
35
+ ```
36
+
37
+ ### 2. Initialise the composable
38
+
39
+ ```ts
40
+ const guideContainerRef = ref<HTMLElement | null>(null)
41
+
42
+ const {
43
+ isGuideRunning,
44
+ currentTooltipIndex,
45
+ startGuide,
46
+ restartGuide,
47
+ stopGuide,
48
+ hasPopovers,
49
+ totalPopovers,
50
+ } = useTooltipsGuide(guideContainerRef, {
51
+ autoStart: true, // default: true — starts after startDelay on mount
52
+ startDelay: 2000, // default: 2000ms — delay before auto-start
53
+ })
54
+ ```
55
+
56
+ Pass `guideContainerRef` to the container element via `ref="guideContainerRef"`.
57
+
58
+ ### 3. Manual controls (optional)
59
+
60
+ ```ts
61
+ // Restart the guide from the beginning
62
+ await restartGuide()
63
+
64
+ // Stop mid-guide
65
+ stopGuide()
66
+
67
+ // Start manually (when autoStart: false)
68
+ await startGuide()
69
+ ```
70
+
71
+ ## How it works
72
+
73
+ 1. On `onMounted`, waits `startDelay` ms, then calls `initializePopovers()` to collect all `[popover]` elements inside the container.
74
+ 2. If `autoStart` is `true`, calls `startGuide()` which iterates the popovers in DOM order.
75
+ 3. For each popover: finds the `[popovertarget][popovertargetaction="toggle"]` trigger button and clicks it to open the popover, then waits for the `[popovertargetaction="hide"]` button to be clicked before advancing.
76
+ 4. After the last popover is dismissed, `autoRunGuide` is set to `false` and `isGuideRunning` becomes `false`.
77
+
78
+ ## API reference
79
+
80
+ | Return value | Type | Description |
81
+ |---|---|---|
82
+ | `isGuideRunning` | `Readonly<Ref<boolean>>` | `true` while the guide is active |
83
+ | `currentTooltipIndex` | `Readonly<Ref<number>>` | Zero-based index of the currently shown popover |
84
+ | `autoRunGuide` | `Readonly<Ref<boolean>>` | Whether auto-start is still enabled |
85
+ | `hasPopovers` | `ComputedRef<boolean>` | `true` if any `[popover]` elements were found |
86
+ | `totalPopovers` | `ComputedRef<number>` | Count of `[popover]` elements found |
87
+ | `startGuide()` | `async () => void` | Start from the first popover; no-op if already running |
88
+ | `restartGuide()` | `async () => void` | Close any open popovers and restart from the beginning |
89
+ | `stopGuide()` | `() => void` | Close any open popovers and stop the guide |
90
+ | `initializePopovers()` | `() => void` | Re-scan the container for `[popover]` elements; call if popovers are added dynamically |
91
+
92
+ ## Notes
93
+
94
+ - If no trigger button is found for a popover, `togglePopover(true)` is called directly as a fallback — anchor positioning may not apply in this case.
95
+ - `restartGuide` is a no-op if the guide is already running (`isGuideRunning` is `true`).
96
+ - Popovers are collected in DOM order — control guide sequence by ordering elements in the markup.
97
+ - `startDelay` uses `useSleep` (also from the layer) — the delay runs on `onMounted` so it is always client-side only.
@@ -4,32 +4,38 @@
4
4
 
5
5
  `useWhatsApp` opens a pre-filled WhatsApp conversation in a new tab via the `wa.me` deep-link API. It formats an array of labelled fields into a bold-label WhatsApp message and requires a phone number configured in runtime config.
6
6
 
7
- Composable location: `app/composables/useWhatsApp.ts`
7
+ **This composable ships inside the `srcdev-nuxt-components` layer** (`app/composables/useWhatsApp.ts`). Consuming apps get it via Nuxt's layer auto-import — **do not create a local copy** in the consuming app.
8
8
 
9
9
  ## Prerequisites
10
10
 
11
11
  - `NUXT_PUBLIC_WHATSAPP_NUMBER` env var set to the recipient number in international format, no `+` or spaces (e.g. `447700900000`).
12
12
 
13
- ## Runtime Config
13
+ ## Setup in the consuming app
14
14
 
15
- `whatsappNumber` must live in `runtimeConfig.public` **not** the private root block — because `openWhatsApp` runs client-side and private keys are server-only.
15
+ ### 1. Runtime config
16
+
17
+ Add `whatsappNumber` to `runtimeConfig.public` in the consuming app's `nuxt.config.ts`. It must be in `public` — **not** the private root block — because `openWhatsApp` runs client-side and private keys are server-only.
16
18
 
17
19
  ```ts
18
20
  // nuxt.config.ts
19
21
  runtimeConfig: {
20
- // private server-only keys (Resend, etc.)
21
22
  public: {
22
23
  whatsappNumber: "", // NUXT_PUBLIC_WHATSAPP_NUMBER
23
24
  },
24
25
  },
25
26
  ```
26
27
 
27
- Env var name follows Nuxt convention: `NUXT_PUBLIC_` prefix + SCREAMING_SNAKE of the key path.
28
+ Env var name follows Nuxt convention: `NUXT_PUBLIC_` prefix + SCREAMING_SNAKE of the key path. Set `NUXT_PUBLIC_WHATSAPP_NUMBER` in `.env` locally and in your hosting provider's environment variables (e.g. Vercel) for production.
29
+
30
+ ### 2. No import needed
31
+
32
+ `useWhatsApp` is auto-imported by Nuxt from the layer. Use it directly in `<script setup>` or any composable without an explicit import.
33
+
34
+ ## Composable reference
28
35
 
29
- ## Composable
36
+ Source lives at `app/composables/useWhatsApp.ts` in the layer. Shown here for reference only — do not recreate it in the consuming app.
30
37
 
31
38
  ```ts
32
- // app/composables/useWhatsApp.ts
33
39
  export const useWhatsApp = () => {
34
40
  const config = useRuntimeConfig(); // must be inside the function, not at module scope
35
41
 
@@ -0,0 +1,154 @@
1
+ # useZodValidation Composable
2
+
3
+ ## Overview
4
+
5
+ `useZodValidation` wires a Zod schema to a form ref, providing reactive validation state, error formatting, field-level error messages, and scroll-to-error behaviour. It is the standard form validation composable in this layer — use it in any form in consuming apps.
6
+
7
+ **This composable ships inside the `srcdev-nuxt-components` layer** (`app/composables/useZodValidation.ts`). It is auto-imported via the Nuxt layer — **do not create a local copy** in the consuming app.
8
+
9
+ ## Prerequisites
10
+
11
+ - `zod` installed in the consuming app (`npm i zod`)
12
+ - A `<form>` element accessible via a template ref
13
+
14
+ ## Setup in the consuming app
15
+
16
+ ### 1. Define a Zod schema
17
+
18
+ ```ts
19
+ import { z } from "zod"
20
+
21
+ const formSchema = z.object({
22
+ fullName: z
23
+ .string({ error: (i) => (i.input === undefined ? "Full name is required" : "Full name must be a string") })
24
+ .trim()
25
+ .min(2, "Name is too short")
26
+ .max(80, "Name is too long"),
27
+ emailAddress: z
28
+ .string({ error: (i) => (i.input === undefined ? "Email is required" : "Email must be a string") })
29
+ .email({ error: "Invalid email address" }),
30
+ })
31
+
32
+ type FormSchema = z.infer<typeof formSchema>
33
+ ```
34
+
35
+ ### 2. Create form state and a form ref
36
+
37
+ ```ts
38
+ const formRef = ref<HTMLFormElement | null>(null)
39
+
40
+ const state = reactive({
41
+ fullName: "",
42
+ emailAddress: "",
43
+ })
44
+ ```
45
+
46
+ ### 3. Initialise the composable
47
+
48
+ `useZodValidation` must be typed with the schema type to get typed error objects:
49
+
50
+ ```ts
51
+ type ReturnTypeUseZodValidation = ReturnType<typeof useZodValidation>
52
+
53
+ const { initZodForm, zodFormControl, zodErrorObj, doZodValidate, fieldMaxLength, scrollToFirstError } =
54
+ useZodValidation<typeof formSchema>(formSchema, formRef) as ReturnTypeUseZodValidation
55
+
56
+ initZodForm()
57
+ ```
58
+
59
+ ### 4. Derive typed form errors
60
+
61
+ ```ts
62
+ const formErrors = computed<z.ZodFormattedError<FormSchema> | null>(() => zodErrorObj.value)
63
+ ```
64
+
65
+ ### 5. Bind to the template
66
+
67
+ Pass `formRef` to the `<form>` and bind `formErrors` to each field's `error-message` and `field-has-error` props:
68
+
69
+ ```vue
70
+ <form ref="formRef" @submit.stop.prevent="submitForm()">
71
+ <InputTextWithLabel
72
+ id="fullName"
73
+ v-model="state.fullName"
74
+ name="fullName"
75
+ label="Full name"
76
+ :error-message="formErrors?.fullName?._errors[0] ?? ''"
77
+ :field-has-error="Boolean(zodFormControl.submitAttempted && formErrors?.fullName)"
78
+ :required="true"
79
+ />
80
+ <InputButtonCore
81
+ type="submit"
82
+ :is-pending="zodFormControl.displayLoader"
83
+ :readonly="zodFormControl.submitDisabled"
84
+ button-text="Submit"
85
+ @click.stop.prevent="submitForm()"
86
+ />
87
+ </form>
88
+ ```
89
+
90
+ ### 6. Submit handler
91
+
92
+ ```ts
93
+ const submitForm = async () => {
94
+ zodFormControl.submitAttempted = true
95
+ if (!(await doZodValidate(state))) {
96
+ scrollToFirstError()
97
+ return
98
+ }
99
+ zodFormControl.displayLoader = true
100
+ try {
101
+ // await $fetch("/api/contact", { method: "POST", body: state })
102
+ zodFormControl.submitSuccessful = true
103
+ } catch (error) {
104
+ console.warn("Form submission failed", error)
105
+ } finally {
106
+ zodFormControl.displayLoader = false
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### 7. Live validation on state change (optional)
112
+
113
+ Re-run validation on every state change so errors clear as the user types:
114
+
115
+ ```ts
116
+ watch(
117
+ () => state,
118
+ () => { doZodValidate(state) },
119
+ { deep: true }
120
+ )
121
+ ```
122
+
123
+ ## API reference
124
+
125
+ ### `useZodValidation<T>(formSchema, formRef)`
126
+
127
+ | Return value | Type | Description |
128
+ |---|---|---|
129
+ | `initZodForm()` | `() => void` | Initialises previous-value tracking — call once after setup |
130
+ | `zodFormControl` | `reactive` | Mutable form state (see below) |
131
+ | `zodErrorObj` | `Ref<ZodFormattedError \| null>` | Raw formatted error object from Zod |
132
+ | `doZodValidate(state)` | `async (state) => boolean` | Runs `safeParse` and updates `zodErrorObj`; returns `true` if valid |
133
+ | `pushCustomErrors(apiErrorResponse, state)` | `async` | Merges server-side API errors into the Zod error object |
134
+ | `fieldMaxLength(name)` | `(name: string) => undefined` | Currently returns `undefined` (Zod v4 internal API change — do not rely on this) |
135
+ | `scrollToFirstError()` | `async () => void` | Scrolls to the first `[aria-invalid=true]` element in the form |
136
+ | `scrollToFormHead()` | `() => void` | Scrolls to the top of the form element |
137
+
138
+ ### `zodFormControl` properties
139
+
140
+ | Property | Type | Description |
141
+ |---|---|---|
142
+ | `submitAttempted` | `boolean` | Set to `true` when submit is first clicked — gates error display |
143
+ | `displayLoader` | `boolean` | Drive `:is-pending` on the submit button |
144
+ | `submitDisabled` | `boolean` | Drive `:readonly` on the submit button |
145
+ | `submitSuccessful` | `boolean` | Set to `true` after a successful submission |
146
+ | `formIsValid` | `boolean` | `true` when `zodErrorObj` is null |
147
+ | `errorCount` | `number` | Number of invalid fields |
148
+
149
+ ## Notes
150
+
151
+ - `useZodValidation` is exported as a default export from the layer, not a named export — the auto-import handles this transparently.
152
+ - The `as ReturnTypeUseZodValidation` cast is required because TypeScript cannot infer the generic return type through the layer auto-import.
153
+ - `fieldMaxLength` is currently a no-op (returns `undefined`) due to a Zod v4 internal API change — omit it from templates or pass `undefined` to `:maxlength`.
154
+ - For server-side validation errors (e.g. from a Resend or API call), use `pushCustomErrors` with the API error response shape `{ data: { errors: Record<string, string | string[]> } }`.
@@ -36,7 +36,12 @@ Each skill is a single markdown file named `<area>-<task>.md`.
36
36
  ├── component-inline-action-button.md — InputButtonCore variant="inline" pattern for buttons embedded in custom input wrappers
37
37
  ├── icon-sets.md — icon set packages required by layer components, FOUC prevention, component→package map
38
38
  ├── robots-env-aware.md — @nuxtjs/robots: allow crawling on prod domain only, block on preview/staging via env var
39
+ ├── release-notes.md — produce release notes as a fenced markdown block from git log
39
40
  ├── composable-whatsapp.md — useWhatsApp: open pre-filled wa.me link from form payload; runtime config, security, usage
41
+ ├── composable-zod-validation.md — useZodValidation: schema-driven form validation, error binding, submit flow, API error push
42
+ ├── composable-colour-scheme.md — useColourScheme: reactive light/dark/auto switching, localStorage persistence, runtime config
43
+ ├── composable-dialog-controls.md — useDialogControls: named dialog open/close state with confirm/cancel callbacks
44
+ ├── composable-tooltips-guide.md — useTooltipsGuide: sequential popover guide with auto-start, dismiss-to-advance, manual controls
40
45
  └── components/
41
46
  ├── accordian-core.md — AccordianCore indexed dynamic slots (accordian-{n}-summary/icon/content), exclusive-open grouping
42
47
  ├── eyebrow-text.md — EyebrowText props, usage patterns, styling
@@ -0,0 +1,48 @@
1
+ # Release Notes
2
+
3
+ ## Overview
4
+
5
+ When asked to create release notes, always produce a fenced markdown code block (` ```markdown `) so the content can be copied and pasted directly into a git tag, GitHub release, or changelog.
6
+
7
+ ## Steps
8
+
9
+ ### 1. Determine the version
10
+
11
+ Run `git log --oneline -8` to find the most recent release commit and the commits since the previous release.
12
+
13
+ ### 2. Review the diff
14
+
15
+ Run `git show <commit> --stat` for each commit since the last release to understand what changed.
16
+
17
+ ### 3. Produce a fenced markdown block
18
+
19
+ Always wrap the output in a ` ```markdown ` code fence — never render it as plain markdown. This ensures the user can copy the raw text without formatting being stripped.
20
+
21
+ ## Format
22
+
23
+ ```markdown
24
+ ## vX.Y.Z
25
+
26
+ ### New
27
+
28
+ - **`ComponentOrComposableName`** — one-line description of what it does and why it exists
29
+
30
+ ### Fixed
31
+
32
+ - Short description of what was wrong and what was corrected
33
+
34
+ ### Changed
35
+
36
+ - Short description of intentional behaviour or API changes
37
+
38
+ ### Documentation
39
+
40
+ - **`skill-name` skill** — new/updated: what it covers
41
+ ```
42
+
43
+ ## Notes
44
+
45
+ - Only include sections that have content — omit empty headings
46
+ - Keep each bullet to one line where possible
47
+ - Lead with the most user-facing changes (New, Fixed) before internal ones (Documentation, Tests)
48
+ - Test additions warrant their own section only when substantial; otherwise fold into the relevant New/Fixed bullet
@@ -1,4 +1,6 @@
1
1
  :where(html) {
2
+ --field-margin-block: 0 1.6rem; /* space between field and other elements */
3
+
2
4
  /* Field spacing */
3
5
  --field-gap-block: 0.8rem; /* space between stacked fields */
4
6
  --field-gap-inline: 0;
@@ -34,48 +34,48 @@ defineProps({
34
34
 
35
35
  <style lang="css">
36
36
  @layer components {
37
- .form-field {
38
- --_gutter-width: 0rem;
39
- --_max-width: 400px;
40
- --_background-color: transparent;
41
- --_border-radius: 0.4rem;
37
+ .form-field {
38
+ --_gutter-width: 0rem;
39
+ --_max-width: 400px;
40
+ --_background-color: transparent;
41
+ --_border-radius: 0.4rem;
42
42
 
43
- background-color: var(--_background-color);
44
- border-radius: var(--_border-radius);
45
- margin-inline: auto;
46
- margin-block: 0 1rem;
43
+ background-color: var(--_background-color);
44
+ border-radius: var(--_border-radius);
45
+ margin-inline: auto;
46
+ margin-block: var(--field-margin-block);
47
47
 
48
- width: min(100% - calc(2 * var(--_gutter-width)), var(--_max-width));
49
- outline: 0rem solid var(--slate-05);
50
- /* overflow-block: hidden; */
48
+ width: min(100% - calc(2 * var(--_gutter-width)), var(--_max-width));
49
+ outline: 0rem solid var(--slate-05);
50
+ /* overflow-block: hidden; */
51
51
 
52
- &:has(.underline) {
53
- --_background-color: var(--theme-form-input-bg-underlined);
54
- }
52
+ &:has(.underline) {
53
+ --_background-color: var(--theme-form-input-bg-underlined);
54
+ }
55
55
 
56
- .form-field-inner {
57
- background-color: var(--_background-color);
58
- border-radius: var(--_border-radius);
59
- margin-inline-start: 0rem;
60
- padding-inline-start: 0rem;
61
- outline: 0 solid var(--slate-05);
62
- }
56
+ .form-field-inner {
57
+ background-color: var(--_background-color);
58
+ border-radius: var(--_border-radius);
59
+ margin-inline-start: 0rem;
60
+ padding-inline-start: 0rem;
61
+ outline: 0 solid var(--slate-05);
62
+ }
63
63
 
64
- &.has-gutter {
65
- --_gutter-width: 1.6rem;
66
- }
64
+ &.has-gutter {
65
+ --_gutter-width: 1.6rem;
66
+ }
67
67
 
68
- &.narrow {
69
- max-width: 400px;
70
- }
68
+ &.narrow {
69
+ max-width: 400px;
70
+ }
71
71
 
72
- &.medium {
73
- --_max-width: 800px;
74
- }
72
+ &.medium {
73
+ --_max-width: 800px;
74
+ }
75
75
 
76
- &.wide {
77
- --_max-width: 1200px;
76
+ &.wide {
77
+ --_max-width: 1200px;
78
+ }
78
79
  }
79
80
  }
80
- }
81
81
  </style>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.16",
4
+ "version": "9.1.18",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",