diff --git a/.claude/skills/components/display-prompt.md b/.claude/skills/components/display-prompt.md index fd452c51..383cf71b 100644 --- a/.claude/skills/components/display-prompt.md +++ b/.claude/skills/components/display-prompt.md @@ -16,6 +16,7 @@ the DOM. Dismiss can be controlled locally (closes itself) or by a parent via `v | `theme` | `SemanticTheme` | `"info"` | `"info" \| "success" \| "warning" \| "error"` | | `dismissible` | `boolean` | `false` | Shows a close button. | | `useAutoFocus` | `boolean` | `false` | Focuses the prompt root element on mount. | +| `masked` | `boolean` | `false` | SVG glass border — swaps `AlertContent` for `AlertMaskedContent`. | | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the inner wrapper. Supported modifier: `"outlined"`. | | `v-model` | `boolean` | `false` | Optional parent control — see dismiss behaviour below. | @@ -38,8 +39,9 @@ the DOM. Dismiss can be controlled locally (closes itself) or by a parent via `v | `"warning"` | `akar-icons:circle-alert` | | `"error"` | `akar-icons:circle-alert` | -`data-theme` is set on the inner wrapper, activating the CSS palette (`--theme-surface`, -`--theme-text`, `--theme-border`, `--theme-ring`, etc.). +`data-theme` is set on `.display-prompt-wrapper`, activating the CSS palette (`--theme-accent`, +`--theme-text`, `--theme-border`, `--theme-ring`, etc.). The wrapper's left-edge accent uses +`--theme-accent` (step 5/4) — not `--theme-surface` — so it stays visually distinct from buttons. ## Dismiss behaviour @@ -111,11 +113,25 @@ Scope overrides using your page or section wrapper class — no `:deep()` needed ```css .my-section .display-prompt-wrapper { - --theme-surface: oklch(60% 0.18 140); + --theme-accent: oklch(60% 0.18 140); /* left-edge accent strip */ border-radius: 0.8rem; } ``` +## Masked variant + +Setting `:masked="true"` swaps `AlertContent` for `AlertMaskedContent`, giving the prompt an SVG-based +border and a semi-transparent background so content behind it is faintly visible. The inner layout is +identical — all the same slots apply. Place the prompt over a coloured or image background for the +glass effect to be visible. + +```vue + + + + +``` + ## Notes - `DisplayPromptTheme` is an alias for `SemanticTheme` (`"info" | "success" | "warning" | "error"`). diff --git a/.claude/skills/components/display-toast.md b/.claude/skills/components/display-toast.md index 4daf9645..cf025f87 100644 --- a/.claude/skills/components/display-toast.md +++ b/.claude/skills/components/display-toast.md @@ -41,6 +41,7 @@ interface DisplayToastConfig { position?: "top" | "bottom" // default: "top" alignment?: "left" | "center" | "right" // default: "right" fullWidth?: boolean // default: false — overrides alignment + masked?: boolean // SVG glass border — semi-transparent background — default: false } behavior?: { autoDismiss?: boolean // default: true @@ -198,6 +199,14 @@ Override tokens via an unscoped style block scoped to a page or layout class: } ``` +### Masked variant + +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. + +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. + +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. + ### Provider notes - `useToastQueue` state persists across route changes — no `onBeforeRouteLeave` cleanup needed. diff --git a/.claude/skills/index.md b/.claude/skills/index.md index 154922d4..3f5da982 100644 --- a/.claude/skills/index.md +++ b/.claude/skills/index.md @@ -92,7 +92,7 @@ Each skill is a single markdown file named `-.md`. ├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, full CSS token API for border/outline/colour ├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens ├── samaritan-prompt-mixed.md — SamaritanPromptMixed: animated text prompt, typewriter/word-pulse effects, MessageConfig API, aria-live accessibility, CSS tokens - ├── display-toast.md — DisplayToast (standalone v-model) + DisplayToastProvider + useToastQueue (app-wide queue): stacking, FLIP dismiss, maxVisible, SemanticTheme × 4 + ├── display-toast.md — DisplayToast (standalone v-model) + DisplayToastProvider + useToastQueue (app-wide queue): stacking, FLIP dismiss, maxVisible, SemanticTheme × 4, masked SVG glass variant └── display-prompt.md — DisplayPrompt: inline notification banner, SemanticTheme × 4, local vs parent-controlled dismiss, outlined modifier, CSS token override ``` diff --git a/.claude/skills/theming-colour-ramps.md b/.claude/skills/theming-colour-ramps.md index d3fddfcb..c0577709 100644 --- a/.claude/skills/theming-colour-ramps.md +++ b/.claude/skills/theming-colour-ramps.md @@ -70,17 +70,18 @@ These are used by components that need a specific step by name (e.g. error state ## Semantic slots -Eight shared colour roles are declared in `_theme-slots.css` on the same selector as the ramp. +Nine shared colour roles are declared in `_theme-slots.css` on the same selector as the ramp. All themed components (buttons, inputs, prompts, toasts) read only these tokens: | Token | Light (step) | Dark (step) | Role | |-------------------------|--------------|-------------|-----------------------------------| -| `--theme-surface` | 9 | 7 | Filled button/chip surface | -| `--theme-surface-hover` | 8 | 6 | Hover state of filled surface | -| `--theme-surface-subtle`| 1 | 9 | Hover bg for outline elements | +| `--theme-surface` | 7 | 9 | Filled button/chip surface | +| `--theme-surface-hover` | 9 | 7 | Hover state of filled surface | +| `--theme-accent` | 5 | 4 | Decorative accent strip (prompt/toast left edge) | +| `--theme-surface-subtle`| 1 | 9 | Subtle body bg for prompt/toast, outline element hover | | `--theme-border` | 6 | 5 | Input/card border | | `--theme-border-focus` | 4 | 3 | Focused border | -| `--theme-ring` | 2 | 2 | Focus ring (outline) | +| `--theme-ring` | 1 | 9 | Focus ring (outline) | | `--theme-on-surface` | 0 | 0 | Text/icon on filled surface | | `--theme-text` | 9 | 2 | Text on page, outline element text| diff --git a/.claude/skills/theming-override-default.md b/.claude/skills/theming-override-default.md index cbaa218d..92c3d7cc 100644 --- a/.claude/skills/theming-override-default.md +++ b/.claude/skills/theming-override-default.md @@ -181,4 +181,5 @@ that hue/chroma pair. - `--slate-*` neutrals ship with the layer — no need to redefine for page backgrounds/text - To add a custom `[data-theme]` variant using your generated palette, see `theming-colour-ramps.md` -- The `warning` theme overrides `--theme-surface` steps directly for a different surface intensity — this pattern is available for any custom theme that needs a non-default surface step +- The `warning` theme overrides `--theme-surface` and `--theme-surface-hover` to lighter steps (5/4 and 7/5) so warning buttons read as warm/alert rather than the default dark surface — this pattern is available for any custom theme that needs a non-default surface step +- `--theme-accent` (step 5/4) is separate from `--theme-surface` (step 7/9) — accent is for decorative strips in `DisplayPrompt` and `DisplayToast`; surface is for interactive elements like buttons diff --git a/.claude/skills/theming-partial-override.md b/.claude/skills/theming-partial-override.md index f5555d93..5cc28b30 100644 --- a/.claude/skills/theming-partial-override.md +++ b/.claude/skills/theming-partial-override.md @@ -14,17 +14,18 @@ after the layer styles. ## Semantic slots — the tokens to override -All themed components share the same 8-slot vocabulary. Overriding these affects buttons, inputs, +All themed components share a 9-slot vocabulary. Overriding these affects buttons, inputs, prompts, and toasts simultaneously: | Token | Default (light → dark) | Role | |-------------------------|----------------------------|-----------------------------------| -| `--theme-surface` | `--colour-theme-9` / `7` | Filled button/chip surface | -| `--theme-surface-hover` | `--colour-theme-8` / `6` | Hover state of filled surface | -| `--theme-surface-subtle`| `--colour-theme-1` / `9` | Hover bg for outline elements | +| `--theme-surface` | `--colour-theme-7` / `9` | Filled button/chip surface | +| `--theme-surface-hover` | `--colour-theme-9` / `7` | Hover state of filled surface | +| `--theme-accent` | `--colour-theme-5` / `4` | Decorative accent strip (prompt/toast left edge) | +| `--theme-surface-subtle`| `--colour-theme-1` / `9` | Subtle body bg for prompt/toast, outline element hover | | `--theme-border` | `--colour-theme-6` / `5` | Input/card border | | `--theme-border-focus` | `--colour-theme-4` / `3` | Focused border | -| `--theme-ring` | `--colour-theme-2` | Focus ring (outline) | +| `--theme-ring` | `--colour-theme-1` / `9` | Focus ring (outline) | | `--theme-on-surface` | `--colour-theme-0` | Text/icon on filled surface | | `--theme-text` | `--colour-theme-9` / `2` | Text on page, outline element text| @@ -64,10 +65,10 @@ Page-level tokens: #### Override button surface only ```css -/* Make primary buttons darker/more saturated */ +/* Make primary buttons darker/more saturated (step-7/9 defaults, step-9/7 hover) */ :where(html) { - --theme-surface: light-dark(oklch(28% 0.18 85), oklch(45% 0.20 85)); - --theme-surface-hover: light-dark(oklch(22% 0.16 85), oklch(38% 0.18 85)); + --theme-surface: light-dark(oklch(48% 0.18 85), oklch(32% 0.20 85)); + --theme-surface-hover: light-dark(oklch(32% 0.18 85), oklch(48% 0.20 85)); } ``` @@ -118,8 +119,8 @@ To restrict an override to a specific section, scope to a wrapper class instead .hero-cta { /* Just these buttons use a different surface colour */ - --theme-surface: light-dark(oklch(32% 0.16 85), oklch(55% 0.18 85)); - --theme-surface-hover: light-dark(oklch(25% 0.14 85), oklch(48% 0.16 85)); + --theme-surface: light-dark(oklch(48% 0.16 85), oklch(32% 0.18 85)); + --theme-surface-hover: light-dark(oklch(32% 0.14 85), oklch(48% 0.16 85)); } ``` diff --git a/README.md b/README.md index 92b2063a..fc7a7c68 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ computes the scale from the two params: Scale direction: **00 = lightest, 10 = darkest**. Chroma tapers at both ends and peaks at step 06. -Eight semantic slots (`--theme-surface`, `--theme-text`, `--theme-ring`, etc.) are derived from the +Nine semantic slots (`--theme-surface`, `--theme-accent`, `--theme-text`, `--theme-ring`, etc.) are derived from the scale using `light-dark()` and are shared by all components. Theme variants (`data-theme="success"`, `"warning"`, `"error"`) swap `--theme-hue` and `--theme-chroma` on their element; the formula re-evaluates locally so each themed element gets its own full palette without affecting the page. diff --git a/app/assets/styles/setup/03.theming/_theme-slots.css b/app/assets/styles/setup/03.theming/_theme-slots.css index 90548e04..7dc306f2 100644 --- a/app/assets/styles/setup/03.theming/_theme-slots.css +++ b/app/assets/styles/setup/03.theming/_theme-slots.css @@ -2,12 +2,15 @@ Declared on the same selector as theme-ramp.css so the formula re-evaluates per [data-theme] element rather than being inherited from html. */ :where(html, [data-theme], [data-invalid]) { - --theme-surface: light-dark(var(--colour-theme-9), var(--colour-theme-7)); - --theme-surface-hover: light-dark(var(--colour-theme-8), var(--colour-theme-6)); - --theme-surface-subtle: light-dark(var(--colour-theme-1), var(--colour-theme-9)); - --theme-border: light-dark(var(--colour-theme-6), var(--colour-theme-5)); - --theme-border-focus: light-dark(var(--colour-theme-4), var(--colour-theme-3)); - --theme-ring: var(--colour-theme-2); - --theme-on-surface: var(--colour-theme-0); - --theme-text: light-dark(var(--colour-theme-9), var(--colour-theme-2)); + --theme-surface: light-dark(var(--colour-theme-7), var(--colour-theme-9)); + --theme-surface-inverted: light-dark(var(--colour-theme-9), var(--colour-theme-2)); + --theme-surface-hover: light-dark(var(--colour-theme-9), var(--colour-theme-7)); + --theme-accent: light-dark(var(--colour-theme-5), var(--colour-theme-4)); + --theme-surface-subtle: light-dark(var(--colour-theme-1), var(--colour-theme-9)); + --theme-border: light-dark(var(--colour-theme-6), var(--colour-theme-5)); + --theme-border-focus: light-dark(var(--colour-theme-4), var(--colour-theme-3)); + --theme-ring: light-dark(var(--colour-theme-1), var(--colour-theme-9)); + --theme-on-surface: var(--colour-theme-0); + --theme-text: light-dark(var(--colour-theme-9), var(--colour-theme-2)); + --theme-text-inverted: light-dark(var(--colour-theme-9), var(--colour-theme-7)); } diff --git a/app/assets/styles/setup/04.elements/forms/06.button-geometry.css b/app/assets/styles/setup/04.elements/forms/06.button-geometry.css index 6d1d010d..9dd36a7d 100644 --- a/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +++ b/app/assets/styles/setup/04.elements/forms/06.button-geometry.css @@ -2,7 +2,7 @@ --button-padding-block: 1rem; --button-padding-inline: 1.8rem; - --button-border-radius: 0.8rem; + --button-border-radius: 0.2rem; --button-border-radius-icon-only: 100vw; --button-border-width: 0.1rem; diff --git a/app/components/01.atoms/prompt/DisplayPrompt.vue b/app/components/01.atoms/prompt/DisplayPrompt.vue index 4cc9efc5..0b29e201 100644 --- a/app/components/01.atoms/prompt/DisplayPrompt.vue +++ b/app/components/01.atoms/prompt/DisplayPrompt.vue @@ -7,45 +7,43 @@ tabindex="0" >
-
- -
-

- -

-

- -

-
- -
+ + + + + + +
- - diff --git a/app/components/01.atoms/toast/stories/DisplayToast.stories.ts b/app/components/01.atoms/toast/stories/DisplayToast.stories.ts index 4aba1abb..c0e69bc9 100644 --- a/app/components/01.atoms/toast/stories/DisplayToast.stories.ts +++ b/app/components/01.atoms/toast/stories/DisplayToast.stories.ts @@ -2,7 +2,6 @@ import type { Meta, StoryFn } from "@nuxtjs/storybook"; import { ref, onMounted } from "vue"; import StorybookComponent from "../DisplayToast.vue"; -// Custom interface for story args interface ToastStoryArgs { theme: "info" | "success" | "warning" | "error"; position: "top" | "bottom"; @@ -11,153 +10,70 @@ interface ToastStoryArgs { autoDismiss: boolean; duration: number; revealDuration: number; - text: string; + title: string; + description: string; customIcon: string; - // DisplayPrompt specific props - usePrompt: boolean; - promptTheme: "info" | "success" | "warning" | "error"; - dismissible: boolean; - promptTitle: string; - promptContent: string; - useAutoFocus: boolean; - styleClassPassthrough: string[]; } export default { title: "Atoms/DisplayToast", component: StorybookComponent, argTypes: { - // Appearance Config theme: { control: { type: "select" }, options: ["info", "success", "warning", "error"], description: "Toast theme/variant", - table: { - category: "Appearance", - }, + table: { category: "Appearance" }, }, position: { control: { type: "select" }, options: ["top", "bottom"], description: "Vertical position of toast", - table: { - category: "Appearance", - }, + table: { category: "Appearance" }, }, alignment: { control: { type: "select" }, options: ["left", "center", "right"], description: "Horizontal alignment of toast", - table: { - category: "Appearance", - }, + table: { category: "Appearance" }, }, fullWidth: { control: { type: "boolean" }, description: "Whether toast spans full width", - table: { - category: "Appearance", - }, + table: { category: "Appearance" }, }, - // Behavior Config autoDismiss: { control: { type: "boolean" }, description: "Auto dismiss toast after duration", - table: { - category: "Behavior", - }, + table: { category: "Behaviour" }, }, duration: { control: { type: "range", min: 1000, max: 10000, step: 500 }, description: "Auto dismiss duration in milliseconds", - table: { - category: "Behavior", - }, + table: { category: "Behaviour" }, }, revealDuration: { control: { type: "range", min: 100, max: 1000, step: 50 }, description: "Animation duration for reveal/hide", - table: { - category: "Behavior", - }, + table: { category: "Behaviour" }, }, - // Content Config - text: { + title: { control: { type: "text" }, - description: "Toast message text", - table: { - category: "Content", - }, + description: "Toast title text", + table: { category: "Content" }, }, - customIcon: { - control: { type: "text" }, - description: "Custom icon name (e.g., 'akar-icons:check-box')", - table: { - category: "Content", - }, - }, - // DisplayPrompt Config - usePrompt: { - control: { type: "boolean" }, - description: "Use DisplayPrompt instead of simple text", - table: { - category: "Prompt", - }, - }, - promptTheme: { - control: { type: "select" }, - options: ["info", "success", "warning", "error"], - description: "Theme for DisplayPrompt", - table: { - category: "Prompt", - }, - }, - dismissible: { - control: { type: "boolean" }, - description: "Whether DisplayPrompt is dismissible", - table: { - category: "Prompt", - }, - }, - promptTitle: { + description: { control: { type: "text" }, - description: "Title for DisplayPrompt", - table: { - category: "Prompt", - }, + description: "Toast description text shown below the title", + table: { category: "Content" }, }, - promptContent: { + customIcon: { control: { type: "text" }, - description: "Content for DisplayPrompt", - table: { - category: "Prompt", - }, - }, - useAutoFocus: { - control: { type: "boolean" }, - description: "Auto focus DisplayPrompt", - table: { - category: "Prompt", - }, - }, - styleClassPassthrough: { - control: { type: "object" }, - description: "Style classes for DisplayPrompt (e.g., ['dark', 'outlined'])", - table: { - category: "Prompt", - }, - }, - // Hide complex internal props - config: { - table: { - disable: true, - }, - }, - modelValue: { - table: { - disable: true, - }, + description: "Custom icon name (Iconify, e.g. 'akar-icons:check-box')", + table: { category: "Content" }, }, + config: { table: { disable: true } }, + modelValue: { table: { disable: true } }, }, args: { theme: "info", @@ -167,26 +83,17 @@ export default { autoDismiss: false, duration: 3000, revealDuration: 300, - text: "This is a toast notification message", + title: "Notification", + description: "This is a toast notification message.", customIcon: "", - usePrompt: false, - promptTheme: "info", - dismissible: true, - promptTitle: "Prompt Title", - promptContent: "This is prompt content, it can contain html or plain text.", - useAutoFocus: false, - styleClassPassthrough: ["dark", "outlined"], }, } as Meta; const Template: StoryFn = (args) => ({ - components: { - StorybookComponent, - }, + components: { StorybookComponent }, setup() { const isActive = ref(false); - // Auto-trigger toast for demo onMounted(() => { setTimeout(() => { isActive.value = true; @@ -197,10 +104,6 @@ const Template: StoryFn = (args) => ({ }, template: `
-

Toast will appear and demonstrate the configured behavior:

-

- Note: DisplayPrompt examples require component to be properly loaded in the environment. -