srcdev-nuxt-components 9.1.55 → 9.1.57

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.
Files changed (104) hide show
  1. package/.claude/settings.json +30 -2
  2. package/.claude/skills/components/alert-content-inner.md +60 -0
  3. package/.claude/skills/components/display-dialog.md +80 -0
  4. package/.claude/skills/components/display-prompt.md +163 -0
  5. package/.claude/skills/components/display-toast.md +235 -0
  6. package/.claude/skills/index.md +7 -3
  7. package/.claude/skills/theming-colour-ramps.md +328 -0
  8. package/.claude/skills/theming-override-default.md +115 -186
  9. package/.claude/skills/theming-partial-override.md +88 -75
  10. package/README.md +166 -0
  11. package/app/app.config.ts +44 -0
  12. package/app/assets/styles/setup/01.config/_head.css +3 -3
  13. package/app/assets/styles/setup/02.colours/_amber.css +12 -10
  14. package/app/assets/styles/setup/02.colours/_blue.css +10 -9
  15. package/app/assets/styles/setup/02.colours/_green.css +12 -11
  16. package/app/assets/styles/setup/02.colours/_orange.css +12 -10
  17. package/app/assets/styles/setup/02.colours/_red.css +10 -9
  18. package/app/assets/styles/setup/02.colours/_slate.css +12 -11
  19. package/app/assets/styles/setup/02.colours/_sunset.css +12 -10
  20. package/app/assets/styles/setup/02.colours/_theme-params.css +18 -0
  21. package/app/assets/styles/setup/02.colours/index.css +1 -0
  22. package/app/assets/styles/setup/03.theming/_default.css +87 -0
  23. package/app/assets/styles/setup/03.theming/_error.css +14 -0
  24. package/app/assets/styles/setup/03.theming/_success.css +4 -0
  25. package/app/assets/styles/setup/03.theming/_theme-slots.css +16 -0
  26. package/app/assets/styles/setup/03.theming/_warning.css +13 -0
  27. package/app/assets/styles/setup/03.theming/index.css +6 -4
  28. package/app/assets/styles/setup/03.theming/theme-ramp.css +19 -0
  29. package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +0 -7
  30. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
  31. package/app/assets/styles/setup/index.css +1 -1
  32. package/app/components/01.atoms/display-dialog/DisplayDialog.vue +74 -24
  33. package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +148 -14
  34. package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +3 -3
  35. package/app/components/01.atoms/prompt/DisplayPrompt.vue +113 -0
  36. package/app/components/01.atoms/prompt/stories/DisplayPrompt.stories.ts +112 -0
  37. package/app/components/01.atoms/prompt/tests/DisplayPrompt.spec.ts +205 -0
  38. package/app/components/01.atoms/toast/DisplayToast.vue +327 -0
  39. package/app/components/01.atoms/toast/DisplayToastProvider.vue +362 -0
  40. package/app/components/01.atoms/toast/stories/DisplayToast.stories.ts +195 -0
  41. package/app/components/01.atoms/toast/stories/DisplayToastProvider.stories.ts +142 -0
  42. package/app/components/01.atoms/toast/tests/DisplayToast.spec.ts +266 -0
  43. package/app/components/01.atoms/toast/tests/DisplayToastProvider.spec.ts +256 -0
  44. package/app/components/02.molecules/action-menu/ActionMenu.vue +8 -1
  45. package/app/components/02.molecules/action-menu/ActionMenuItemCore.vue +11 -3
  46. package/app/components/02.molecules/alert-content/AlertContent.vue +67 -0
  47. package/app/components/02.molecules/alert-content/AlertContentInner.vue +153 -0
  48. package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +131 -0
  49. package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +135 -0
  50. package/app/components/02.molecules/alert-content/tests/AlertContentInner.spec.ts +135 -0
  51. package/app/components/02.molecules/alert-masked-content/AlertMaskedContent.vue +78 -0
  52. package/app/components/02.molecules/alert-masked-content/stories/AlertMaskedContent.stories.ts +132 -0
  53. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +149 -0
  54. package/app/components/05.forms/input-button/InputButtonCore.vue +35 -39
  55. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +8 -8
  56. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue +5 -5
  57. package/app/components/05.forms/input-copy/InputCopyCore.vue +7 -7
  58. package/app/components/05.forms/input-number/InputNumberCore.vue +5 -5
  59. package/app/components/05.forms/input-select/InputSelectCore.vue +6 -6
  60. package/app/components/05.forms/input-text/InputTextCore.vue +6 -6
  61. package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +2 -2
  62. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +5 -5
  63. package/app/components/05.forms/pending-effect/PendingEffect.vue +60 -60
  64. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +10 -10
  65. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +4 -4
  66. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +4 -4
  67. package/app/components/display-tooltip/DisplayTooltip.vue +4 -4
  68. package/app/composables/useBodyLock.ts +21 -0
  69. package/app/composables/useDialogControls.ts +11 -11
  70. package/app/composables/useToastQueue.ts +39 -0
  71. package/app/layouts/default.vue +1 -1
  72. package/app/pages/ui/display-dialog.vue +275 -127
  73. package/app/pages/ui/display-prompt.vue +255 -52
  74. package/app/pages/ui/display-toast.vue +278 -299
  75. package/app/pages/ui/mask-element.vue +25 -3
  76. package/app/pages/ui/settings.vue +9 -35
  77. package/app/types/app-config.d.ts +41 -0
  78. package/app/types/components/display-prompt.d.ts +11 -0
  79. package/app/types/components/display-toast.d.ts +11 -9
  80. package/app/types/components/index.ts +2 -0
  81. package/app/types/components/semantic-theme.d.ts +1 -0
  82. package/nuxt.config.ts +16 -0
  83. package/package.json +8 -4
  84. package/ramps.config.mjs +28 -0
  85. package/scripts/check-ramps.mjs +50 -0
  86. package/scripts/generate-consumer-ramps.mjs +100 -0
  87. package/scripts/generate-ramps.mjs +92 -0
  88. package/app/assets/styles/setup/03.theming/default/_dark.css +0 -156
  89. package/app/assets/styles/setup/03.theming/default/_light.css +0 -166
  90. package/app/assets/styles/setup/03.theming/default/index.css +0 -2
  91. package/app/assets/styles/setup/03.theming/error/_dark.css +0 -56
  92. package/app/assets/styles/setup/03.theming/error/_light.css +0 -74
  93. package/app/assets/styles/setup/03.theming/error/index.css +0 -2
  94. package/app/assets/styles/setup/03.theming/success/_dark.css +0 -50
  95. package/app/assets/styles/setup/03.theming/success/_light.css +0 -65
  96. package/app/assets/styles/setup/03.theming/success/index.css +0 -2
  97. package/app/assets/styles/setup/03.theming/warning/_dark.css +0 -50
  98. package/app/assets/styles/setup/03.theming/warning/_light.css +0 -64
  99. package/app/assets/styles/setup/03.theming/warning/index.css +0 -2
  100. package/app/components/display-prompt/DisplayPromptCore.vue +0 -228
  101. package/app/components/display-prompt/variants/DisplayPromptError.vue +0 -53
  102. package/app/components/display-toast/DisplayToast.vue +0 -447
  103. package/app/components/display-toast/molecules/DefaultToastContent.vue +0 -163
  104. package/app/components/display-toast/stories/DisplayToast.stories.ts +0 -380
@@ -27,10 +27,38 @@
27
27
  "Bash(git -C /Users/simoncornforth/websites/instepreflexology log --oneline -3)",
28
28
  "Bash(git -C /Users/simoncornforth/websites/instepreflexology add package-lock.json .nvmrc)",
29
29
  "Bash(git -C /Users/simoncornforth/websites/instepreflexology commit -m ' *)",
30
- "Bash(git -C /Users/simoncornforth/websites/instepreflexology push)"
30
+ "Bash(git -C /Users/simoncornforth/websites/instepreflexology push)",
31
+ "Bash(node scripts/generate-ramps.mjs)",
32
+ "Bash(sed -i '' 's/var\\(--theme-input-border\\)/var\\(--theme-border\\)/g' app/components/05.forms/input-text/InputTextCore.vue app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue app/components/05.forms/input-textarea/InputTextareaCore.vue app/components/05.forms/input-select/InputSelectCore.vue app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue app/components/05.forms/toggle-switch/ToggleSwitchCore.vue app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue app/components/display-theme-switch/DisplayThemeSwitch.vue app/components/05.forms/input-copy/InputCopyCore.vue app/components/05.forms/input-number/InputNumberCore.vue)",
33
+ "Bash(sed -i '' 's/var\\(--theme-input-outline\\);/transparent;/g' app/components/05.forms/input-text/InputTextCore.vue app/components/05.forms/input-textarea/InputTextareaCore.vue app/components/05.forms/input-select/InputSelectCore.vue app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue app/components/05.forms/toggle-switch/ToggleSwitchCore.vue app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue app/components/display-theme-switch/DisplayThemeSwitch.vue app/components/05.forms/input-number/InputNumberCore.vue)",
34
+ "Bash(sed -i '' 's/var\\(--theme-input-outline-focus\\)/var\\(--theme-border-focus\\)/g' app/components/05.forms/input-text/InputTextCore.vue app/components/05.forms/input-textarea/InputTextareaCore.vue app/components/05.forms/input-select/InputSelectCore.vue app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue app/components/05.forms/toggle-switch/ToggleSwitchCore.vue app/components/05.forms/input-copy/InputCopyCore.vue)",
35
+ "Bash(sed -i '' 's/var\\(--theme-input-outline-hover\\)/var\\(--theme-ring\\)/g' app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue app/components/display-theme-switch/DisplayThemeSwitch.vue)",
36
+ "Bash(sed -i '' 's/var\\(--theme-checkbox-symbol-color\\)/var\\(--theme-text\\)/g' app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue app/components/05.forms/toggle-switch/ToggleSwitchCore.vue)",
37
+ "Bash(sed -i '' 's/var\\(--theme-input-surface-focus\\)/var\\(--theme-surface-subtle\\)/g' /Users/simoncornforth/websites/nuxt-components/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue)",
38
+ "Bash(sed -i '' 's/var\\(--theme-checkbox-label-text-color\\)/var\\(--colour-text-default\\)/g' /Users/simoncornforth/websites/nuxt-components/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue)",
39
+ "Bash(sed -i '' 's/var\\(--theme-button-border\\);/var\\(--theme-border\\);/g; s/var\\(--theme-button-outline\\);/var\\(--theme-ring\\);/g; s/var\\(--theme-button-border-hover\\);/var\\(--theme-surface\\);/g; s/var\\(--theme-button-outline-hover\\);/var\\(--theme-surface\\);/g' /Users/simoncornforth/websites/nuxt-components/app/components/display-tooltip/DisplayTooltip.vue)",
40
+ "Bash(sed -i '' 's/var\\(--theme-input-outline, red\\)/transparent/g' /Users/simoncornforth/websites/nuxt-components/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue)",
41
+ "Bash(xargs ls -la)",
42
+ "Skill(code-review)",
43
+ "Skill(code-review:*)",
44
+ "Bash(cd /Users/simoncornforth/websites/nuxt-components/.vscode && rm srcdev-nuxt3-accordian-component.code-snippets srcdev-nuxt3-carousel-basic-component.code-snippets srcdev-nuxt3-carousel-flip-component.code-snippets srcdev-nuxt3-component-boilerplate.code-snippets srcdev-nuxt3-container-glow-component.code-snippets srcdev-nuxt3-display-banner-component.code-snippets srcdev-nuxt3-display-details-component.code-snippets srcdev-nuxt3-expanding-panel-component.code-snippets srcdev-nuxt3-layout-row.code-snippets srcdev-nuxt3-page-layout.code-snippets srcdev-nuxt3-responsive-header-component.code-snippets srcdev-nuxt3-tabs-component.code-snippets && ls *.code-snippets)",
45
+ "Bash(mv srcdev-nuxt3-dialog-component.code-snippets srcdev-component-dialog.code-snippets)",
46
+ "Bash(mv srcdev-nuxt3-display-prompt-component.code-snippets srcdev-component-display-prompt.code-snippets)",
47
+ "Bash(mv srcdev-nuxt3-display-toast-component.code-snippets srcdev-component-display-toast.code-snippets)",
48
+ "Bash(mv srcdev-nuxt3-pinia-store-setup.code-snippets srcdev-component-pinia-store.code-snippets)",
49
+ "Bash(SRCDEV_STANDALONE=true npx vitest run app/components/01.atoms/display-dialog)",
50
+ "Bash(ls /Users/simoncornforth/websites/nuxt-components/.claude/ && echo \"---\" && cat /Users/simoncornforth/websites/nuxt-components/.gitignore | grep -i \"claude\\\\|memory\\\\|projects\" && echo \"---gitignore done---\" && ls /Users/simoncornforth/.claude/projects/ 2>/dev/null | head -5)",
51
+ "Read(//Users/simoncornforth/.claude/projects/**)",
52
+ "Bash(cp /Users/simoncornforth/websites/nuxt-components/.claude/projects/-Users-simoncornforth-websites-nuxt-components/memory/project_app_config_defaults.md /Users/simoncornforth/.claude/projects/-Users-simoncornforth-websites-nuxt-components/memory/project_app_config_defaults.md)",
53
+ "Bash(rm -rf /Users/simoncornforth/websites/nuxt-components/.claude/projects/)",
54
+ "Bash(SRCDEV_STANDALONE=true npx vitest run app/components/01.atoms/toast)",
55
+ "Bash(SRCDEV_STANDALONE=true npx vitest run app/components/02.molecules/alert-content)",
56
+ "Bash(SRCDEV_STANDALONE=true npx vitest run app/components/01.atoms/prompt)",
57
+ "Bash(git fetch *)"
31
58
  ],
32
59
  "additionalDirectories": [
33
- "/Users/simoncornforth/websites/instepreflexology"
60
+ "/Users/simoncornforth/websites/instepreflexology",
61
+ "/Users/simoncornforth/websites/nuxt-components/.vscode"
34
62
  ]
35
63
  }
36
64
  }
@@ -0,0 +1,60 @@
1
+ # AlertContentInner
2
+
3
+ ## Overview
4
+
5
+ Internal molecule used by `AlertContent` and `AlertMaskedContent`, which are in turn used by
6
+ `DisplayToast` and `DisplayPrompt`. Renders the icon, title/body text, and optional dismiss button.
7
+ Because it sits below multiple consumer components, icon customisation here applies everywhere.
8
+
9
+ **Location**: `app/components/02.molecules/alert-content/AlertContentInner.vue`
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Default | Notes |
14
+ |---|---|---|---|
15
+ | `theme` | `SemanticTheme` | — | **Required.** Drives the default icon and `data-theme` (set by parent `AlertContent`). |
16
+ | `customIcon` | `string` | `undefined` | Icon name override passed down from the toast/prompt `config.content.customIcon`. |
17
+ | `dismissible` | `boolean` | `false` | Shows the dismiss button when `true`. |
18
+ | `contentId` | `string` | `undefined` | Sets `id` on `.alert-content-body` for `aria-describedby` wiring. |
19
+ | `ariaLive` | `"polite" \| "assertive" \| "off"` | `undefined` | Sets `aria-live` on `.alert-content-body`. |
20
+
21
+ ## app.config defaults
22
+
23
+ Icon names — both per-theme and the dismiss button — are configurable globally via `app.config`.
24
+ One change here covers every toast, prompt, and any other component built on `AlertContentInner`.
25
+
26
+ ```ts
27
+ // Consumer's app.config.ts
28
+ export default defineAppConfig({
29
+ srcdev: {
30
+ alertContent: {
31
+ icons: {
32
+ info: "heroicons:information-circle",
33
+ success: "heroicons:check-circle",
34
+ warning: "heroicons:exclamation-triangle",
35
+ error: "heroicons:x-circle",
36
+ },
37
+ dismissIcon: "heroicons:x-mark",
38
+ },
39
+ },
40
+ })
41
+ ```
42
+
43
+ Resolution chain: **`customIcon` prop → app.config icons → hardcoded fallback**.
44
+
45
+ The `dismissIcon` slot (`#dismissIcon` on `AlertContent`) always wins over app.config if provided.
46
+
47
+ ## Slots
48
+
49
+ | Slot | Description |
50
+ |---|---|
51
+ | `#icon` | Replaces the entire icon region. |
52
+ | `#title` | Title line inside `.alert-content-body`. |
53
+ | `#content` | Body text inside `.alert-content-body`. |
54
+ | `#dismissIcon` | Replaces the dismiss button icon. |
55
+ | `#dismissLabel` | SR-only label for the dismiss button (default: `"Close"`). |
56
+
57
+ ## Notes
58
+
59
+ - Do not use `AlertContentInner` directly in pages — use `AlertContent` or `AlertMaskedContent` instead.
60
+ - `AlertMaskedContent` wraps `AlertContentInner` with a different background treatment (SVG glass border).
@@ -14,11 +14,13 @@ open/close state management.
14
14
  |---|---|---|---|
15
15
  | `dataDialogId` | `string` | — | **Required.** Unique identifier rendered as `data-dialog-id` attribute. |
16
16
  | `variant` | `'dialog' \| 'modal' \| 'confirm' \| 'alert' \| 'fullscreen'` | `'dialog'` | Controls panel sizing and ARIA role. |
17
+ | `theme` | `SemanticTheme` | `undefined` | Optional header accent — `data-theme` on `.header` only; adds a coloured bottom border and tints the close icon. |
17
18
  | `v-model` | `boolean` | — | Controls open/closed state. |
18
19
  | `allowContentScroll` | `boolean` | `false` | Enables independent scroll on `.dialog-content`. |
19
20
  | `lockViewport` | `boolean` | `true` | Adds/removes `lock` class on `<body>` on mount/close. |
20
21
  | `justifyDialog` | `'start' \| 'center' \| 'end'` | `'center'` | Horizontal position of panel within overlay. |
21
22
  | `alignDialog` | `'start' \| 'center' \| 'end'` | `'center'` | Vertical position of panel within overlay. |
23
+ | `closeIcon` | `string` | app.config | Icon name for the header close button. Override per-instance or set globally via `app.config`. |
22
24
  | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `<dialog>` element. |
23
25
 
24
26
  ## Slots
@@ -104,6 +106,28 @@ are both disabled — the user must act via the footer buttons.
104
106
  </DisplayDialog>
105
107
  ```
106
108
 
109
+ ## app.config defaults
110
+
111
+ All props except `dataDialogId` and `styleClassPassthrough` can be set globally via `app.config`.
112
+ The resolution chain is: **explicit prop → app.config → hardcoded fallback**.
113
+
114
+ ```ts
115
+ // app.config.ts in the consumer app
116
+ export default defineAppConfig({
117
+ srcdev: {
118
+ displayDialog: {
119
+ variant: "modal",
120
+ alignDialog: "end",
121
+ lockViewport: true,
122
+ closeIcon: "heroicons:x-mark",
123
+ theme: "info",
124
+ },
125
+ },
126
+ })
127
+ ```
128
+
129
+ Per-instance props always win — app.config only fills in when a prop is absent.
130
+
107
131
  ## CSS token API
108
132
 
109
133
  See `CONSUMER-STYLING.md` in the component folder for the full token reference. Prefer global
@@ -119,6 +143,62 @@ CSS over per-instance overrides — dialogs are site-wide UI:
119
143
  }
120
144
  ```
121
145
 
146
+ ## Nested dialogs
147
+
148
+ When a dialog needs to launch its own sub-dialog (e.g. "Discard changes?" before closing an edit
149
+ form), the cleanest approach is to call `useDialogControls` **inside the dialog component itself**.
150
+ The sub-dialog is entirely self-contained — the parent page doesn't need to know about it.
151
+
152
+ ```vue
153
+ <!-- EditProfileDialog.vue -->
154
+ <script setup lang="ts">
155
+ const emit = defineEmits<{ close: [] }>();
156
+
157
+ const { dialogsConfig, openDialog, closeDialog } = useDialogControls({
158
+ confirmDiscard: {
159
+ onConfirm: () => emit("close"),
160
+ onCancel: () => {},
161
+ },
162
+ });
163
+
164
+ const handleClose = () => {
165
+ if (hasUnsavedChanges.value) {
166
+ openDialog("confirmDiscard");
167
+ } else {
168
+ emit("close");
169
+ }
170
+ };
171
+ </script>
172
+
173
+ <template>
174
+ <!-- main dialog content, close button calls handleClose() -->
175
+
176
+ <!-- sub-dialog rendered inside the same component -->
177
+ <DisplayDialog
178
+ v-if="dialogsConfig['confirmDiscard']"
179
+ v-model="dialogsConfig['confirmDiscard']"
180
+ variant="confirm"
181
+ theme="warning"
182
+ data-dialog-id="confirmDiscard"
183
+ >
184
+ <template #dialogTitle><p>Discard changes?</p></template>
185
+ <template #dialogContent><p>Your unsaved changes will be lost.</p></template>
186
+ <template #actionButtonLeft>
187
+ <button @click="closeDialog('confirmDiscard', 'cancel')">Keep editing</button>
188
+ </template>
189
+ <template #actionButtonRight>
190
+ <button @click="closeDialog('confirmDiscard', 'confirm')">Discard</button>
191
+ </template>
192
+ </DisplayDialog>
193
+ </template>
194
+ ```
195
+
196
+ Both dialogs are open simultaneously — native `<dialog>` stacking order ensures the confirm
197
+ renders on top. The confirm resolves first; its `onConfirm` callback triggers the parent close.
198
+
199
+ **Avoid** registering the sub-dialog on the page level — that couples the page to the dialog's
200
+ internal concern and leaks implementation detail upward.
201
+
122
202
  ## Notes
123
203
 
124
204
  - Always use `v-if="dialogsConfig['id']"` (not `v-show`) — `DisplayDialog` runs body-lock logic
@@ -0,0 +1,163 @@
1
+ # DisplayPrompt
2
+
3
+ ## Overview
4
+
5
+ `DisplayPrompt` is an inline notification banner with a themed icon, title, optional content, and
6
+ an optional dismiss button. It collapses in-place via CSS grid animation rather than removing from
7
+ the DOM. Dismiss can be controlled locally (closes itself) or by a parent via `v-model`.
8
+
9
+ **Location**: `app/components/01.atoms/prompt/DisplayPrompt.vue`
10
+ **Types**: `~/types/components` — `DisplayPromptTheme`, `SemanticTheme`
11
+
12
+ ## Props
13
+
14
+ | Prop | Type | Default | Notes |
15
+ |---|---|---|---|
16
+ | `theme` | `SemanticTheme` | `"info"` | `"info" \| "success" \| "warning" \| "error"` |
17
+ | `dismissible` | `boolean` | `false` | Shows a close button. |
18
+ | `useAutoFocus` | `boolean` | `false` | Focuses the prompt root element on mount. |
19
+ | `masked` | `boolean` | `false` | SVG glass border — swaps `AlertContent` for `AlertMaskedContent`. |
20
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the inner wrapper. Supported modifier: `"outlined"`. |
21
+ | `v-model` | `boolean` | `false` | Optional parent control — see dismiss behaviour below. |
22
+
23
+ ## Slots
24
+
25
+ | Slot | Description |
26
+ |---|---|
27
+ | `#title` | **Required in practice.** Bold heading text. Always rendered (even when empty). |
28
+ | `#content` | Body text below the title. The `<p>` element is omitted when this slot is empty. |
29
+ | `#customDecoratorIcon` | Replaces the default theme icon. |
30
+ | `#customCloseIcon` | Replaces the default × close icon inside the dismiss button. |
31
+ | `#customTitle` | Screen-reader label for the dismiss button (default: `"Close this prompt"`). |
32
+
33
+ ## Themes
34
+
35
+ | Theme | Default icon |
36
+ |---|---|
37
+ | `"info"` | `akar-icons:info` |
38
+ | `"success"` | `akar-icons:check` |
39
+ | `"warning"` | `akar-icons:circle-alert` |
40
+ | `"error"` | `akar-icons:circle-alert` |
41
+
42
+ `data-theme` is set on `.display-prompt-wrapper`, activating the CSS palette (`--theme-accent`,
43
+ `--theme-text`, `--theme-border`, `--theme-ring`, etc.). The wrapper's left-edge accent uses
44
+ `--theme-accent` (step 5/4) — not `--theme-surface` — so it stays visually distinct from buttons.
45
+
46
+ ## Dismiss behaviour
47
+
48
+ Two modes depending on whether `v-model` is bound:
49
+
50
+ | Scenario | What happens on close |
51
+ |---|---|
52
+ | No `v-model` (or `v-model="false"`) | Sets internal `componentOpen = false` → `.closed` class → collapses via CSS |
53
+ | `v-model="true"` | Emits `update:modelValue = false`; internal state unchanged — parent controls visibility |
54
+
55
+ The `.closed` class triggers a CSS grid row animation (`grid-template-rows: 1fr → 0fr`) with
56
+ `opacity: 0` and `pointer-events: none`.
57
+
58
+ ## app.config defaults
59
+
60
+ All props except `styleClassPassthrough` can be set globally so every prompt in the app inherits
61
+ the same defaults without repeating them at each usage site.
62
+
63
+ ```ts
64
+ // Consumer's app.config.ts
65
+ export default defineAppConfig({
66
+ srcdev: {
67
+ displayPrompt: {
68
+ theme: "info",
69
+ dismissible: true,
70
+ masked: false,
71
+ useAutoFocus: false,
72
+ },
73
+ },
74
+ })
75
+ ```
76
+
77
+ Resolution chain: **explicit prop → app.config → hardcoded fallback**.
78
+
79
+ ## Basic usage
80
+
81
+ ```vue
82
+ <DisplayPrompt theme="info">
83
+ <template #title>Your session will expire soon.</template>
84
+ <template #content>Save your work to avoid losing changes.</template>
85
+ </DisplayPrompt>
86
+ ```
87
+
88
+ ## Dismissible prompt
89
+
90
+ ```vue
91
+ <DisplayPrompt theme="warning" :dismissible="true">
92
+ <template #title>Action required</template>
93
+ <template #content>Please verify your email address.</template>
94
+ </DisplayPrompt>
95
+ ```
96
+
97
+ ## Parent-controlled dismiss (v-model)
98
+
99
+ Use when the parent needs to react to dismiss (e.g. save state, conditionally re-show):
100
+
101
+ ```vue
102
+ <script setup lang="ts">
103
+ const showPrompt = ref(true)
104
+ </script>
105
+
106
+ <template>
107
+ <DisplayPrompt
108
+ v-model="showPrompt"
109
+ theme="success"
110
+ :dismissible="true"
111
+ >
112
+ <template #title>Profile updated.</template>
113
+ </DisplayPrompt>
114
+ </template>
115
+ ```
116
+
117
+ ## `styleClassPassthrough` modifiers
118
+
119
+ | Class | Effect |
120
+ |---|---|
121
+ | `"outlined"` | Adds `1px solid var(--theme-border)` border to the wrapper |
122
+
123
+ Apply via prop:
124
+
125
+ ```vue
126
+ <DisplayPrompt :style-class-passthrough="['outlined']" theme="error">
127
+ <template #title>Something went wrong.</template>
128
+ </DisplayPrompt>
129
+ ```
130
+
131
+ ## CSS token override
132
+
133
+ Scope overrides using your page or section wrapper class — no `:deep()` needed:
134
+
135
+ ```css
136
+ .my-section .display-prompt-wrapper {
137
+ --theme-accent: oklch(60% 0.18 140); /* left-edge accent strip */
138
+ border-radius: 0.8rem;
139
+ }
140
+ ```
141
+
142
+ ## Masked variant
143
+
144
+ Setting `:masked="true"` swaps `AlertContent` for `AlertMaskedContent`, giving the prompt an SVG-based
145
+ border and a semi-transparent background so content behind it is faintly visible. The inner layout is
146
+ identical — all the same slots apply. Place the prompt over a coloured or image background for the
147
+ glass effect to be visible.
148
+
149
+ ```vue
150
+ <DisplayPrompt theme="info" :masked="true">
151
+ <template #title>Glass prompt</template>
152
+ <template #content>Semi-transparent, backed by AlertMaskedContent.</template>
153
+ </DisplayPrompt>
154
+ ```
155
+
156
+ ## Notes
157
+
158
+ - `DisplayPromptTheme` is an alias for `SemanticTheme` (`"info" | "success" | "warning" | "error"`).
159
+ - The root element always has `tabindex="0"` — it is focusable whether or not `dismissible` is set.
160
+ - `useAutoFocus` focuses the root element on mount (useful when injecting a prompt in response to a
161
+ user action that has already moved focus elsewhere).
162
+ - The `#title` slot renders unconditionally — an empty title `<p>` will still appear. Always
163
+ provide meaningful content in `#title`.
@@ -0,0 +1,235 @@
1
+ # DisplayToast / DisplayToastProvider
2
+
3
+ ## Overview
4
+
5
+ Two patterns are available depending on the use case:
6
+
7
+ | Pattern | Component | When to use |
8
+ |---|---|---|
9
+ | **Standalone** | `DisplayToast` | One-off toast tied to a specific UI action; no queueing needed |
10
+ | **App-wide queue** | `DisplayToastProvider` + `useToastQueue` | Multiple toasts across the app, stacking, queue management |
11
+
12
+ **Locations**:
13
+
14
+ - `app/components/01.atoms/toast/DisplayToast.vue`
15
+ - `app/components/01.atoms/toast/DisplayToastProvider.vue`
16
+ - `app/composables/useToastQueue.ts`
17
+ - `app/components/01.atoms/toast/molecules/DefaultToastContent.vue`
18
+
19
+ **Types**: `~/types/components` — `DisplayToastConfig`, `DisplayToastTheme`, `ToastQueueEntry`, `ToastQueueStatus`
20
+
21
+ ---
22
+
23
+ ## Pattern 1 — Standalone (`DisplayToast`)
24
+
25
+ Triggered via `v-model`. Each instance manages its own visibility and position.
26
+
27
+ ### Props
28
+
29
+ | Prop | Type | Default | Notes |
30
+ |---|---|---|---|
31
+ | `v-model` | `boolean` | `false` | `true` shows; `false` hides. |
32
+ | `config` | `DisplayToastConfig` | see below | All sub-keys optional. |
33
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root element. |
34
+
35
+ ### Config shape
36
+
37
+ ```ts
38
+ interface DisplayToastConfig {
39
+ appearance?: {
40
+ theme?: SemanticTheme // "info" | "success" | "warning" | "error" — default: "info"
41
+ position?: "top" | "bottom" // default: "top"
42
+ alignment?: "left" | "center" | "right" // default: "right"
43
+ fullWidth?: boolean // default: false — overrides alignment
44
+ masked?: boolean // SVG glass border — semi-transparent background — default: false
45
+ }
46
+ behavior?: {
47
+ autoDismiss?: boolean // default: true
48
+ duration?: number // ms before auto-dismiss — default: 5000
49
+ revealDuration?: number // animation duration ms — default: 550
50
+ returnFocusTo?: HTMLElement | ComponentPublicInstance | null
51
+ }
52
+ content?: {
53
+ text?: string // simple message (used when no title/description)
54
+ title?: string // bold title line
55
+ description?: string // smaller description line
56
+ customIcon?: string // icon name override (e.g. "akar-icons:check-box")
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### app.config defaults
62
+
63
+ `appearance` and `behavior` keys (not `content` — that's per-instance) can be set globally so
64
+ every toast in the app inherits the same defaults without repeating them in every `config` prop.
65
+
66
+ ```ts
67
+ // Consumer's app.config.ts
68
+ export default defineAppConfig({
69
+ srcdev: {
70
+ displayToast: {
71
+ appearance: { theme: "success", position: "bottom", alignment: "left" },
72
+ behavior: { autoDismiss: true, duration: 4000 },
73
+ },
74
+ },
75
+ })
76
+ ```
77
+
78
+ Resolution chain: **`config` prop → app.config → hardcoded fallback**.
79
+
80
+ ### Slots
81
+
82
+ | Slot | Description |
83
+ |---|---|
84
+ | `default` | Replaces `DefaultToastContent` entirely. `has-theme`, `tabindex`, and `aria-describedby` are omitted — accessibility is the caller's responsibility. |
85
+ | `#customToastIcon` | Replaces the default theme icon. |
86
+ | `#title` | Replaces `config.content.title`. Do not provide both slot and config value. |
87
+ | `#description` | Replaces `config.content.description`. Do not provide both slot and config value. |
88
+
89
+ ### Basic usage
90
+
91
+ ```vue
92
+ <script setup lang="ts">
93
+ const toastVisible = ref(false)
94
+ </script>
95
+
96
+ <template>
97
+ <button @click="toastVisible = true">Save</button>
98
+
99
+ <DisplayToast
100
+ v-model="toastVisible"
101
+ :config="{
102
+ appearance: { theme: 'success' },
103
+ behavior: { autoDismiss: true, duration: 4000 },
104
+ content: { text: 'Changes saved.' },
105
+ }"
106
+ />
107
+ </template>
108
+ ```
109
+
110
+ ### Notes
111
+
112
+ - `onBeforeRouteLeave` dismisses the toast on navigation. This emits a Vue Router warning in Vitest (no active route record) — harmless.
113
+ - `returnFocusTo` accepts an `HTMLElement` or a component instance with `$el`.
114
+ - The toast uses `<Teleport to="body">`. In tests, query `document.querySelector(".display-toast")` not `wrapper.find()`.
115
+
116
+ ---
117
+
118
+ ## Pattern 2 — App-wide queue (`DisplayToastProvider` + `useToastQueue`)
119
+
120
+ Place `DisplayToastProvider` once in the app layout. Trigger toasts from anywhere using `useToastQueue`. The provider promotes pending entries to visible up to `maxVisible`, manages timers, and handles stacked FLIP animations.
121
+
122
+ ### Setup — layout
123
+
124
+ ```vue
125
+ <!-- layouts/default.vue -->
126
+ <template>
127
+ <div>
128
+ <slot />
129
+ <DisplayToastProvider position="top" alignment="right" :max-visible="1" />
130
+ </div>
131
+ </template>
132
+ ```
133
+
134
+ Only one `DisplayToastProvider` should be mounted at a time. It uses `<Teleport to="body">` so its placement in the layout tree does not affect visual output.
135
+
136
+ ### `DisplayToastProvider` props
137
+
138
+ | Prop | Type | Default | Notes |
139
+ |---|---|---|---|
140
+ | `position` | `"top" \| "bottom"` | `"top"` | Vertical screen edge |
141
+ | `alignment` | `"left" \| "center" \| "right"` | `"right"` | Horizontal alignment (ignored when `fullWidth`) |
142
+ | `fullWidth` | `boolean` | `false` | Toast spans the full viewport width |
143
+ | `maxVisible` | `number` | `1` | Max toasts visible simultaneously; rest queue as pending |
144
+
145
+ ### `useToastQueue` API
146
+
147
+ ```ts
148
+ const { show, dismiss, clear, queue } = useToastQueue()
149
+ ```
150
+
151
+ | Method | Signature | Notes |
152
+ |---|---|---|
153
+ | `show(config)` | `(config: DisplayToastConfig) => string` | Adds a toast to the queue; returns its ID |
154
+ | `dismiss(id)` | `(id: string) => void` | Removes a specific toast by ID |
155
+ | `clear()` | `() => void` | Flushes all pending and visible toasts |
156
+ | `queue` | `Readonly<Ref<ToastQueueEntry[]>>` | Reactive read-only queue state |
157
+
158
+ The composable uses a **module-level singleton** — state is shared across all callers without Pinia. Safe for client-only ephemeral UI state.
159
+
160
+ > **Internal API**: `DisplayToastProvider` uses `useToastQueueProvider()` (a separate export from the same file) which additionally exposes `promote`. Do not call `useToastQueueProvider` from consuming app code — manually promoting without a timer puts the queue into an inconsistent state.
161
+
162
+ ### Triggering toasts
163
+
164
+ ```vue
165
+ <script setup lang="ts">
166
+ const { show } = useToastQueue()
167
+
168
+ const onSave = async () => {
169
+ try {
170
+ await save()
171
+ show({
172
+ appearance: { theme: 'success' },
173
+ behavior: { autoDismiss: true, duration: 4000 },
174
+ content: { title: 'Saved', description: 'Your changes have been saved.' },
175
+ })
176
+ } catch {
177
+ show({
178
+ appearance: { theme: 'error' },
179
+ behavior: { autoDismiss: false },
180
+ content: { title: 'Save failed', description: 'Check your connection and try again.' },
181
+ })
182
+ }
183
+ }
184
+ </script>
185
+ ```
186
+
187
+ ### Stacking (`maxVisible > 1`)
188
+
189
+ ```vue
190
+ <DisplayToastProvider :max-visible="3" position="top" alignment="right" />
191
+ ```
192
+
193
+ With `maxVisible: 3` and 5 toasts triggered:
194
+
195
+ - Toasts 1–3 are visible immediately
196
+ - Toasts 4–5 queue as `pending`
197
+ - When toast 1 is dismissed, toast 4 is promoted and animates in
198
+
199
+ Stacked dismissals use a FLIP animation — remaining toasts slide smoothly to fill the gap rather than snapping.
200
+
201
+ ### Themes and ARIA
202
+
203
+ | Theme | ARIA role | aria-live |
204
+ |---|---|---|
205
+ | `"info"` | `status` | `polite` |
206
+ | `"success"` | `status` | `polite` |
207
+ | `"warning"` | `alert` | `assertive` |
208
+ | `"error"` | `alert` | `assertive` |
209
+
210
+ ### CSS / styling
211
+
212
+ Override tokens via an unscoped style block scoped to a page or layout class:
213
+
214
+ ```css
215
+ .my-page .display-toast-provider-item {
216
+ --theme-surface: oklch(15% 0 0);
217
+ --theme-text: oklch(95% 0 0);
218
+ }
219
+ ```
220
+
221
+ ### Masked variant
222
+
223
+ Setting `appearance.masked: true` swaps `AlertContent` for `AlertMaskedContent`, which uses an SVG-based border and a semi-transparent background (`rgba(0,0,0,0.3)`) so page content is faintly visible beneath the toast.
224
+
225
+ The SVG border shape matches the standard toast exactly (8px left radius, 4px right, 6px accent-colour left bar via `--theme-accent`). Override any dimension or colour via a `maskConfig` prop if using `AlertMaskedContent` standalone.
226
+
227
+ The inner content layout is handled by the shared `AlertContentInner` molecule — both variants reuse it. `AlertMaskedContent` overrides `--_alert-content-inner-bg: transparent` so the glass effect shows through.
228
+
229
+ ### Provider notes
230
+
231
+ - `useToastQueue` state persists across route changes — no `onBeforeRouteLeave` cleanup needed.
232
+ - `crypto.randomUUID()` generates toast IDs (`toast-<uuid>`). IDs are returned by `show()` for targeted `dismiss(id)` calls.
233
+ - In tests, query via `document.querySelector(".display-toast-provider-item")` — the Teleport renders outside the component wrapper.
234
+ - The progress bar (`.display-toast-provider-progress`) is only rendered when `autoDismiss: true`.
235
+ - Dismissal can be triggered by: close button click, Escape key (when the toast item has focus), `dismiss(id)`, auto-dismiss timer, or `clear()`.
@@ -27,8 +27,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
27
27
  ├── testing-add-unit-test.md — create a Vitest unit test with snapshots
28
28
  ├── testing-add-playwright.md — create a Playwright visual regression test
29
29
  ├── setup-postinstall.md — automate nuxt prepare + Claude skills copy via postinstall so neither is forgotten after npm install
30
- ├── theming-override-default.md replace the entire default theme with a custom colour scale (full palette swap)
31
- ├── theming-partial-override.md — override a specific token category (forms, buttons, colours) without a full theme replacement
30
+ ├── theming-colour-ramps.md parametric oklch ramp system: formula, named palettes, semantic slots, generator, consumer setup
31
+ ├── theming-override-default.md — replace the entire default theme with a custom palette (set --theme-hue/--theme-chroma)
32
+ ├── theming-partial-override.md — override a specific token category (palette, buttons, inputs) without a full theme replacement
32
33
  ├── colour-scheme-disable.md — disable light/dark scheme support in a consumer app
33
34
  ├── component-dynamic-slots.md — named dynamic slots ($slots iteration) vs indexed dynamic slots (itemCount pattern)
34
35
  ├── component-local-style-override.md — styleClassPassthrough + scoped style block for per-usage visual customisation
@@ -55,6 +56,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
55
56
  ├── composable-anchor-scroll.md — useAnchorScroll: smooth anchor scrolling with reduced-motion support, dynamic offset, and TabNavigation integration
56
57
  ├── composable-tooltips-guide.md — useTooltipsGuide: sequential popover guide with auto-start, dismiss-to-advance, manual controls
57
58
  └── components/
59
+ ├── alert-content-inner.md — AlertContentInner: shared icon/body/dismiss molecule under AlertContent + AlertMaskedContent; app.config icon map (alertContent.icons + dismissIcon) covers all consumers
58
60
  ├── accordian-core.md — AccordianCore indexed dynamic slots (accordian-{n}-summary/icon/content), exclusive-open grouping
59
61
  ├── eyebrow-text.md — EyebrowText props, usage patterns, styling
60
62
  ├── hero-text.md — HeroText props, usage patterns, styling
@@ -90,7 +92,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
90
92
  ├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
91
93
  ├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, full CSS token API for border/outline/colour
92
94
  ├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
93
- └── samaritan-prompt-mixed.md — SamaritanPromptMixed: animated text prompt, typewriter/word-pulse effects, MessageConfig API, aria-live accessibility, CSS tokens
95
+ ├── samaritan-prompt-mixed.md — SamaritanPromptMixed: animated text prompt, typewriter/word-pulse effects, MessageConfig API, aria-live accessibility, CSS tokens
96
+ ├── display-toast.md — DisplayToast (standalone v-model) + DisplayToastProvider + useToastQueue (app-wide queue): stacking, FLIP dismiss, maxVisible, SemanticTheme × 4, masked SVG glass variant
97
+ └── display-prompt.md — DisplayPrompt: inline notification banner, SemanticTheme × 4, local vs parent-controlled dismiss, outlined modifier, CSS token override
94
98
  ```
95
99
 
96
100
  ## Skill file template