Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .claude/skills/components/display-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# DisplayPrompt

## Overview

`DisplayPrompt` is an inline notification banner with a themed icon, title, optional content, and
an optional dismiss button. It collapses in-place via CSS grid animation rather than removing from
the DOM. Dismiss can be controlled locally (closes itself) or by a parent via `v-model`.

**Location**: `app/components/01.atoms/prompt/DisplayPrompt.vue`
**Types**: `~/types/components` — `DisplayPromptTheme`, `SemanticTheme`

## Props

| Prop | Type | Default | Notes |
|---|---|---|---|
| `theme` | `SemanticTheme` | `"info"` | `"info" \| "success" \| "warning" \| "error"` |
| `dismissible` | `boolean` | `false` | Shows a close button. |
| `useAutoFocus` | `boolean` | `false` | Focuses the prompt root element on mount. |
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the inner wrapper. Supported modifier: `"outlined"`. |
| `v-model` | `boolean` | `false` | Optional parent control — see dismiss behaviour below. |

## Slots

| Slot | Description |
|---|---|
| `#title` | **Required in practice.** Bold heading text. Always rendered (even when empty). |
| `#content` | Body text below the title. The `<p>` element is omitted when this slot is empty. |
| `#customDecoratorIcon` | Replaces the default theme icon. |
| `#customCloseIcon` | Replaces the default × close icon inside the dismiss button. |
| `#customTitle` | Screen-reader label for the dismiss button (default: `"Close this prompt"`). |

## Themes

| Theme | Default icon |
|---|---|
| `"info"` | `akar-icons:info` |
| `"success"` | `akar-icons:check` |
| `"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.).

## Dismiss behaviour

Two modes depending on whether `v-model` is bound:

| Scenario | What happens on close |
|---|---|
| No `v-model` (or `v-model="false"`) | Sets internal `componentOpen = false` → `.closed` class → collapses via CSS |
| `v-model="true"` | Emits `update:modelValue = false`; internal state unchanged — parent controls visibility |

The `.closed` class triggers a CSS grid row animation (`grid-template-rows: 1fr → 0fr`) with
`opacity: 0` and `pointer-events: none`.

## Basic usage

```vue
<DisplayPrompt theme="info">
<template #title>Your session will expire soon.</template>
<template #content>Save your work to avoid losing changes.</template>
</DisplayPrompt>
```

## Dismissible prompt

```vue
<DisplayPrompt theme="warning" :dismissible="true">
<template #title>Action required</template>
<template #content>Please verify your email address.</template>
</DisplayPrompt>
```

## Parent-controlled dismiss (v-model)

Use when the parent needs to react to dismiss (e.g. save state, conditionally re-show):

```vue
<script setup lang="ts">
const showPrompt = ref(true)
</script>

<template>
<DisplayPrompt
v-model="showPrompt"
theme="success"
:dismissible="true"
>
<template #title>Profile updated.</template>
</DisplayPrompt>
</template>
```

## `styleClassPassthrough` modifiers

| Class | Effect |
|---|---|
| `"outlined"` | Adds `1px solid var(--theme-border)` border to the wrapper |

Apply via prop:

```vue
<DisplayPrompt :style-class-passthrough="['outlined']" theme="error">
<template #title>Something went wrong.</template>
</DisplayPrompt>
```

## CSS token override

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);
border-radius: 0.8rem;
}
```

## Notes

- `DisplayPromptTheme` is an alias for `SemanticTheme` (`"info" | "success" | "warning" | "error"`).
- The root element always has `tabindex="0"` — it is focusable whether or not `dismissible` is set.
- `useAutoFocus` focuses the root element on mount (useful when injecting a prompt in response to a
user action that has already moved focus elsewhere).
- The `#title` slot renders unconditionally — an empty title `<p>` will still appear. Always
provide meaningful content in `#title`.
162 changes: 162 additions & 0 deletions .claude/skills/components/display-toast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# DisplayToast

## Overview

`DisplayToast` is a notification toast that teleports to `<body>` and is triggered via `v-model`.
It supports four semantic themes, configurable position/alignment, auto-dismiss with a progress bar,
and optional custom slot content. The inner content is rendered by `DefaultToastContent` unless a
default slot is provided.

**Location**: `app/components/01.atoms/toast/DisplayToast.vue`
**Inner molecule**: `app/components/01.atoms/toast/molecules/DefaultToastContent.vue`
**Types**: `~/types/components` — `DisplayToastConfig`, `DisplayToastTheme`, `SemanticTheme`

## Props

| Prop | Type | Default | Notes |
|---|---|---|---|
| `v-model` | `boolean` | `false` | Setting to `true` shows the toast; setting back to `false` hides it. |
| `config` | `DisplayToastConfig` | see below | Full config object — all sub-keys are optional. |
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the toast root element. |

### config shape

```ts
interface DisplayToastConfig {
appearance?: {
theme?: SemanticTheme // "info" | "success" | "warning" | "error" — default: "info"
position?: "top" | "bottom" // default: "top"
alignment?: "left" | "center" | "right" // default: "right"
fullWidth?: boolean // default: false — overrides alignment
}
behavior?: {
autoDismiss?: boolean // default: true
duration?: number // ms before auto-dismiss — default: 5000
revealDuration?: number // animation duration ms — default: 550
returnFocusTo?: HTMLElement | ComponentPublicInstance | null
}
content?: {
text?: string // simple message (used when no title/description)
title?: string // bold title line
description?: string // smaller description line
customIcon?: string // icon name override (e.g. "akar-icons:check-box")
}
}
```

## Slots

| Slot | Description |
|---|---|
| `default` | Replaces `DefaultToastContent` entirely — use for fully custom toast bodies. `has-theme` class and accessibility attributes are omitted when this slot is used. |
| `#customToastIcon` | Replaces the default theme icon. Only forwarded to `DefaultToastContent` when provided. |
| `#title` | Replaces the `config.content.title` text. Only forwarded when provided — do not provide both slot and `config.content.title`. |
| `#description` | Replaces the `config.content.description` text. Only forwarded when provided. |

> **Slot forwarding note**: `#title`, `#description`, and `#customToastIcon` are conditionally
> forwarded to `DefaultToastContent`. If you provide the slot, `DefaultToastContent` uses the slot;
> if not, it falls back to the `config.content.*` value. Never provide both — the slot wins.

## Themes and ARIA

Theme drives both colour and ARIA behaviour:

| Theme | ARIA role | aria-live |
|---|---|---|
| `"info"` | `status` | `polite` |
| `"success"` | `status` | `polite` |
| `"warning"` | `alert` | `assertive` |
| `"error"` | `alert` | `assertive` |

The `data-theme` attribute on the root element activates the CSS palette via the project's
theming system (`--theme-surface`, `--theme-text`, `--theme-border`, etc.).

## Basic usage — simple text

```vue
<script setup lang="ts">
const toastVisible = ref(false)
</script>

<template>
<button @click="toastVisible = true">Save</button>

<DisplayToast
v-model="toastVisible"
:config="{
appearance: { theme: 'success' },
behavior: { autoDismiss: true, duration: 4000 },
content: { text: 'Changes saved.' },
}"
/>
</template>
```

## Title + description

```vue
<DisplayToast
v-model="toastVisible"
:config="{
appearance: { theme: 'error', position: 'top', alignment: 'right' },
behavior: { autoDismiss: false },
content: {
title: 'Save failed',
description: 'Check your connection and try again.',
},
}"
/>
```

## Custom slot content

When the `default` slot is used, `has-theme`, `tabindex`, and `aria-describedby` are removed —
full accessibility is the caller's responsibility.

```vue
<DisplayToast v-model="toastVisible">
<div class="my-toast-body">
<p>Custom content here</p>
</div>
</DisplayToast>
```

## Position and alignment

| Config | Result |
|---|---|
| `position: "top"`, `alignment: "right"` | Top-right (default) |
| `position: "bottom"`, `alignment: "center"` | Bottom-centre |
| `fullWidth: true` | Spans full viewport width; alignment is ignored |

On screens narrower than 600 px the toast always spans the full inline width regardless of `alignment`.

## Progress bar

When `autoDismiss: true` a thin progress bar animates across the bottom of the toast over
`duration` ms. It is removed when `autoDismiss: false`.

## CSS

The toast uses `--theme-*` semantic slots from the theming system. Direct token overrides via
`styleClassPassthrough`:

```vue
<DisplayToast style-class-passthrough="my-toast" ... />
```

```css
.my-toast.display-toast {
--theme-surface: oklch(60% 0.15 140);
}
```

## Notes

- The component uses `<Teleport to="body">` — the toast DOM is always a direct child of
`<body>`, not inside the mounting component's subtree. In tests, use
`document.querySelector(".display-toast")` not `wrapper.find()`.
- `onBeforeRouteLeave` dismisses the toast on navigation. This emits a Vue Router warning in
Vitest environments (no active route record) — it is harmless and can be ignored.
- `returnFocusTo` accepts either an `HTMLElement` or a component instance with `$el`.
- Type: `DisplayToastTheme` is an alias for `SemanticTheme` (`"info" | "success" | "warning" | "error"`).
9 changes: 6 additions & 3 deletions .claude/skills/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
├── testing-add-unit-test.md — create a Vitest unit test with snapshots
├── testing-add-playwright.md — create a Playwright visual regression test
├── setup-postinstall.md — automate nuxt prepare + Claude skills copy via postinstall so neither is forgotten after npm install
├── theming-override-default.md — replace the entire default theme with a custom colour scale (full palette swap)
├── theming-partial-override.md — override a specific token category (forms, buttons, colours) without a full theme replacement
├── theming-colour-ramps.md — parametric oklch ramp system: formula, named palettes, semantic slots, generator, consumer setup
├── theming-override-default.md — replace the entire default theme with a custom palette (set --theme-hue/--theme-chroma)
├── theming-partial-override.md — override a specific token category (palette, buttons, inputs) without a full theme replacement
├── colour-scheme-disable.md — disable light/dark scheme support in a consumer app
├── component-dynamic-slots.md — named dynamic slots ($slots iteration) vs indexed dynamic slots (itemCount pattern)
├── component-local-style-override.md — styleClassPassthrough + scoped style block for per-usage visual customisation
Expand Down Expand Up @@ -90,7 +91,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
├── 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
├── samaritan-prompt-mixed.md — SamaritanPromptMixed: animated text prompt, typewriter/word-pulse effects, MessageConfig API, aria-live accessibility, CSS tokens
├── display-toast.md — DisplayToast: Teleport-based notification toast, SemanticTheme × 4, config object API, autoDismiss, position/alignment, slot forwarding gotcha
└── display-prompt.md — DisplayPrompt: inline notification banner, SemanticTheme × 4, local vs parent-controlled dismiss, outlined modifier, CSS token override
```

## Skill file template
Expand Down
Loading
Loading