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
60 changes: 60 additions & 0 deletions .claude/skills/components/alert-content-inner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# AlertContentInner

## Overview

Internal molecule used by `AlertContent` and `AlertMaskedContent`, which are in turn used by
`DisplayToast` and `DisplayPrompt`. Renders the icon, title/body text, and optional dismiss button.
Because it sits below multiple consumer components, icon customisation here applies everywhere.

**Location**: `app/components/02.molecules/alert-content/AlertContentInner.vue`

## Props

| Prop | Type | Default | Notes |
|---|---|---|---|
| `theme` | `SemanticTheme` | — | **Required.** Drives the default icon and `data-theme` (set by parent `AlertContent`). |
| `customIcon` | `string` | `undefined` | Icon name override passed down from the toast/prompt `config.content.customIcon`. |
| `dismissible` | `boolean` | `false` | Shows the dismiss button when `true`. |
| `contentId` | `string` | `undefined` | Sets `id` on `.alert-content-body` for `aria-describedby` wiring. |
| `ariaLive` | `"polite" \| "assertive" \| "off"` | `undefined` | Sets `aria-live` on `.alert-content-body`. |

## app.config defaults

Icon names — both per-theme and the dismiss button — are configurable globally via `app.config`.
One change here covers every toast, prompt, and any other component built on `AlertContentInner`.

```ts
// Consumer's app.config.ts
export default defineAppConfig({
srcdev: {
alertContent: {
icons: {
info: "heroicons:information-circle",
success: "heroicons:check-circle",
warning: "heroicons:exclamation-triangle",
error: "heroicons:x-circle",
},
dismissIcon: "heroicons:x-mark",
},
},
})
```

Resolution chain: **`customIcon` prop → app.config icons → hardcoded fallback**.

The `dismissIcon` slot (`#dismissIcon` on `AlertContent`) always wins over app.config if provided.

## Slots

| Slot | Description |
|---|---|
| `#icon` | Replaces the entire icon region. |
| `#title` | Title line inside `.alert-content-body`. |
| `#content` | Body text inside `.alert-content-body`. |
| `#dismissIcon` | Replaces the dismiss button icon. |
| `#dismissLabel` | SR-only label for the dismiss button (default: `"Close"`). |

## Notes

- Do not use `AlertContentInner` directly in pages — use `AlertContent` or `AlertMaskedContent` instead.
- `AlertMaskedContent` wraps `AlertContentInner` with a different background treatment (SVG glass border).
23 changes: 23 additions & 0 deletions .claude/skills/components/display-dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ open/close state management.
| `lockViewport` | `boolean` | `true` | Adds/removes `lock` class on `<body>` on mount/close. |
| `justifyDialog` | `'start' \| 'center' \| 'end'` | `'center'` | Horizontal position of panel within overlay. |
| `alignDialog` | `'start' \| 'center' \| 'end'` | `'center'` | Vertical position of panel within overlay. |
| `closeIcon` | `string` | app.config | Icon name for the header close button. Override per-instance or set globally via `app.config`. |
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `<dialog>` element. |

## Slots
Expand Down Expand Up @@ -105,6 +106,28 @@ are both disabled — the user must act via the footer buttons.
</DisplayDialog>
```

## app.config defaults

All props except `dataDialogId` and `styleClassPassthrough` can be set globally via `app.config`.
The resolution chain is: **explicit prop → app.config → hardcoded fallback**.

```ts
// app.config.ts in the consumer app
export default defineAppConfig({
srcdev: {
displayDialog: {
variant: "modal",
alignDialog: "end",
lockViewport: true,
closeIcon: "heroicons:x-mark",
theme: "info",
},
},
})
```

Per-instance props always win — app.config only fills in when a prop is absent.

## CSS token API

See `CONSUMER-STYLING.md` in the component folder for the full token reference. Prefer global
Expand Down
21 changes: 21 additions & 0 deletions .claude/skills/components/display-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ Two modes depending on whether `v-model` is bound:
The `.closed` class triggers a CSS grid row animation (`grid-template-rows: 1fr → 0fr`) with
`opacity: 0` and `pointer-events: none`.

## app.config defaults

All props except `styleClassPassthrough` can be set globally so every prompt in the app inherits
the same defaults without repeating them at each usage site.

```ts
// Consumer's app.config.ts
export default defineAppConfig({
srcdev: {
displayPrompt: {
theme: "info",
dismissible: true,
masked: false,
useAutoFocus: false,
},
},
})
```

Resolution chain: **explicit prop → app.config → hardcoded fallback**.

## Basic usage

```vue
Expand Down
19 changes: 19 additions & 0 deletions .claude/skills/components/display-toast.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ interface DisplayToastConfig {
}
```

### app.config defaults

`appearance` and `behavior` keys (not `content` — that's per-instance) can be set globally so
every toast in the app inherits the same defaults without repeating them in every `config` prop.

```ts
// Consumer's app.config.ts
export default defineAppConfig({
srcdev: {
displayToast: {
appearance: { theme: "success", position: "bottom", alignment: "left" },
behavior: { autoDismiss: true, duration: 4000 },
},
},
})
```

Resolution chain: **`config` prop → app.config → hardcoded fallback**.

### Slots

| Slot | Description |
Expand Down
1 change: 1 addition & 0 deletions .claude/skills/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
├── composable-anchor-scroll.md — useAnchorScroll: smooth anchor scrolling with reduced-motion support, dynamic offset, and TabNavigation integration
├── composable-tooltips-guide.md — useTooltipsGuide: sequential popover guide with auto-start, dismiss-to-advance, manual controls
└── components/
├── alert-content-inner.md — AlertContentInner: shared icon/body/dismiss molecule under AlertContent + AlertMaskedContent; app.config icon map (alertContent.icons + dismissIcon) covers all consumers
├── accordian-core.md — AccordianCore indexed dynamic slots (accordian-{n}-summary/icon/content), exclusive-open grouping
├── eyebrow-text.md — EyebrowText props, usage patterns, styling
├── hero-text.md — HeroText props, usage patterns, styling
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ storybook-static/
# Claude Code settings (both are machine-specific, neither belongs in the repo)
.claude/settings.json
.claude/settings.local.json
# Claude Code session/memory data (machine-specific, generated at runtime)
.claude/projects/
44 changes: 44 additions & 0 deletions app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { SemanticTheme, DisplayPromptTheme, DisplayToastTheme, DisplayToastPosition, DisplayToastAlignment } from "~/types/components";

export default defineAppConfig({
srcdev: {
alertContent: {
icons: {
info: "akar-icons:info",
success: "akar-icons:check",
warning: "akar-icons:circle-alert",
error: "akar-icons:circle-alert",
},
dismissIcon: "material-symbols:close",
},
displayPrompt: {
theme: "info" as DisplayPromptTheme,
dismissible: false,
useAutoFocus: false,
masked: false,
},
displayToast: {
appearance: {
theme: "info" as DisplayToastTheme,
position: "top" as DisplayToastPosition,
alignment: "right" as DisplayToastAlignment,
fullWidth: false,
masked: false,
},
behavior: {
autoDismiss: true,
duration: 5000,
revealDuration: 550,
},
},
displayDialog: {
variant: "dialog" as "dialog" | "modal" | "confirm" | "alert" | "fullscreen",
justifyDialog: "center" as "start" | "center" | "end",
alignDialog: "center" as "start" | "center" | "end",
lockViewport: true,
allowContentScroll: false,
theme: undefined as SemanticTheme | undefined,
closeIcon: "bitcoin-icons:cross-filled",
},
},
});
45 changes: 31 additions & 14 deletions app/components/01.atoms/display-dialog/DisplayDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
:role="isAlert ? 'alertdialog' : undefined"
:aria-modal="true"
:aria-labelledby="hasTitle ? dialogTitleId : undefined"
:align-dialog
:justify-dialog
:align-dialog="resolved.alignDialog"
:justify-dialog="resolved.justifyDialog"
:open
:data-dialog-id="dataDialogId"
>
Expand All @@ -16,8 +16,8 @@
:click-outside-deactivates="!isAlert"
@deactivate="closeDialog()"
>
<div class="inner" :class="[variant]">
<div class="header" :data-theme="theme">
<div class="inner" :class="[resolved.variant]">
<div class="header" :data-theme="resolved.theme">
<div v-if="hasTitle" :id="dialogTitleId" class="col-left">
<slot name="dialogTitle"></slot>
</div>
Expand All @@ -28,16 +28,16 @@
class="display-dialog-close"
@click.prevent="closeDialog()"
>
<Icon name="bitcoin-icons:cross-filled" class="icon" />
<Icon :name="resolved.closeIcon" class="icon" />
<span class="sr-only">Close</span>
</button>
</div>
</div>
<div
v-if="hasContent"
class="dialog-content"
:class="[{ 'allow-content-scroll': allowContentScroll }]"
:tabindex="allowContentScroll ? 0 : undefined"
:class="[{ 'allow-content-scroll': resolved.allowContentScroll }]"
:tabindex="resolved.allowContentScroll ? 0 : undefined"
>
<slot name="dialogContent"></slot>
</div>
Expand All @@ -63,21 +63,38 @@ interface Props {
allowContentScroll?: boolean;
dataDialogId: string;
theme?: SemanticTheme;
closeIcon?: string;
}

const props = withDefaults(defineProps<Props>(), {
styleClassPassthrough: () => [],
variant: "dialog",
justifyDialog: "center",
alignDialog: "center",
lockViewport: true,
allowContentScroll: false,
variant: undefined,
justifyDialog: undefined,
alignDialog: undefined,
Comment thread
srcdev marked this conversation as resolved.
lockViewport: undefined,
allowContentScroll: undefined,
theme: undefined,
closeIcon: undefined,
});

const appConfig = useAppConfig();

const resolved = computed(() => {
const config = appConfig.srcdev?.displayDialog;
return {
Comment thread
srcdev marked this conversation as resolved.
variant: props.variant ?? config?.variant ?? "dialog",
justifyDialog: props.justifyDialog ?? config?.justifyDialog ?? "center",
alignDialog: props.alignDialog ?? config?.alignDialog ?? "center",
lockViewport: props.lockViewport ?? config?.lockViewport ?? true,
allowContentScroll: props.allowContentScroll ?? config?.allowContentScroll ?? false,
theme: props.theme ?? config?.theme,
closeIcon: props.closeIcon ?? config?.closeIcon ?? "bitcoin-icons:cross-filled",
} as const;
});

const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);

const isAlert = computed(() => props.variant === "alert");
const isAlert = computed(() => resolved.value.variant === "alert");
const dialogTitleId = useId();

const open = defineModel<boolean>();
Expand All @@ -95,7 +112,7 @@ const { lock, unlock } = useBodyLock();
let hasLocked = false;

onMounted(() => {
if (props.lockViewport) {
if (resolved.value.lockViewport) {
lock();
hasLocked = true;
}
Expand Down
Loading
Loading