import type { Meta, StoryObj } from '@storybook/vue3-vite' import { ref } from 'vue' import Admonition from '../../components/base/Admonition.vue' import ButtonStyled from '../../components/base/ButtonStyled.vue' import StackedAdmonitionsRaw, { type StackedAdmonitionItem, } from '../../components/base/StackedAdmonitions.vue' // The generic type signature of StackedAdmonitions breaks Storybook's Meta // inference and Vue's components record type. Cast to `any` for story wiring; // runtime behavior is unchanged. // eslint-disable-next-line @typescript-eslint/no-explicit-any const StackedAdmonitions = StackedAdmonitionsRaw as any interface DemoItem extends StackedAdmonitionItem { header: string body: string } const meta: Meta = { title: 'Base/StackedAdmonitions', component: StackedAdmonitions, } export default meta type Story = StoryObj const initialItems: DemoItem[] = [ { id: 'backup-failed', type: 'critical', header: 'Backup failed', body: 'Something went wrong while creating your backup. Try again or contact support.', }, { id: 'storage-nearly-full', type: 'warning', header: 'Storage nearly full', body: 'Your server is using 92% of available storage.', }, { id: 'scheduled-maintenance', type: 'info', header: 'Scheduled maintenance', body: 'Routine maintenance will begin in 30 minutes.', }, ] const variedHeightItems: DemoItem[] = [ initialItems[0], { id: 'storage-nearly-full', type: 'warning', header: 'Storage nearly full', body: 'Your server is using 92% of available storage. Large world backups, installation artifacts, and cached uploads are all contributing here, so this card is intentionally taller to exercise the collapse animation between mixed-height banners.', }, initialItems[2], ] const completedBackupItem: DemoItem = { id: 'backup-completed', type: 'success', header: 'Backup completed', body: 'Backup 38298832 finished successfully.', } export const Empty: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, template: /* html */ `

Nothing should render above this line.

`, }), } export const SingleItem: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref([completedBackupItem]) function dismiss(id: string) { items.value = items.value.filter((i) => i.id !== id) } function reset() { items.value = [completedBackupItem] } return { items, dismiss, reset } }, template: /* html */ `
`, }), } export const TwoItems: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref(initialItems.slice(0, 2)) return { items } }, template: /* html */ ` `, }), } export const FiveItems: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref([ ...initialItems, { id: 'update-available', type: 'success', header: 'Update available', body: 'A new version of your server software is ready to install.', }, { id: 'memory-pressure', type: 'warning', header: 'High memory usage', body: 'Your server is using 89% of allocated memory.', }, ]) return { items } }, template: /* html */ ` `, }), } export const MixedTypes: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref([ { id: 'critical', type: 'critical', header: 'Critical admonition', body: 'Red background placeholder when not front.', }, { id: 'warning', type: 'warning', header: 'Warning admonition', body: 'Orange background placeholder when not front.', }, { id: 'info', type: 'info', header: 'Info admonition', body: 'Blue background placeholder when not front.', }, { id: 'success', type: 'success', header: 'Success admonition', body: 'Green background placeholder when not front.', }, ]) return { items } }, template: /* html */ ` `, }), } export const VariedHeights: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref(variedHeightItems) const expanded = ref(false) let nextId = 1 function addAlert() { const type = ['info', 'warning', 'critical', 'success'][nextId % 4] as DemoItem['type'] items.value = [ ...items.value, { id: `new-alert-${nextId}`, type, header: `New alert ${nextId}`, body: nextId % 2 === 0 ? 'A short dynamically added alert.' : 'A dynamically added alert with a longer body so the stack can exercise measurement updates when new mixed-height items enter.', }, ] nextId += 1 } function reset() { items.value = variedHeightItems expanded.value = false nextId = 1 } return { items, expanded, addAlert, reset } }, template: /* html */ `
`, }), } export const ForceExpanded: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref(variedHeightItems) const expanded = ref(true) return { items, expanded } }, template: /* html */ ` `, }), } export const DismissIndividual: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref([...initialItems]) function dismiss(id: string) { items.value = items.value.filter((i) => i.id !== id) } function reset() { items.value = [...initialItems] } return { items, dismiss, reset } }, template: /* html */ `
`, }), } export const DismissAll: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref([...initialItems]) function dismiss(id: string) { items.value = items.value.filter((i) => i.id !== id) } function dismissAll() { items.value = [] } function reset() { items.value = [...initialItems] } return { items, dismiss, dismissAll, reset } }, template: /* html */ `
`, }), } interface RichItem extends StackedAdmonitionItem { header: string body: string progress?: number canRetry?: boolean canCancel?: boolean } export const RichContent: Story = { render: () => ({ components: { StackedAdmonitions, Admonition, ButtonStyled }, setup() { const items = ref([ { id: 'upload-1', type: 'info', header: 'Uploading files (2/5)', body: '128 KB / 1.2 MB (45%)', progress: 0.45, canCancel: true, }, { id: 'extract-1', type: 'critical', header: 'Extraction failed', body: 'Something went wrong while extracting the archive.', canRetry: true, }, { id: 'install-1', type: 'success', header: 'Installation complete', body: 'All files have been installed successfully.', }, ]) function dismiss(id: string) { items.value = items.value.filter((i) => i.id !== id) } function dismissAll() { items.value = [] } return { items, dismiss, dismissAll } }, template: /* html */ ` `, }), } export const KeyboardAndA11y: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const items = ref([...initialItems]) function dismiss(id: string) { items.value = items.value.filter((i) => i.id !== id) } return { items, dismiss } }, template: /* html */ `

Tab to the stack and press Enter or Space to expand. While collapsed, only the front card's buttons are focusable (inert on back cards).

`, }), } export const TwoInstances: Story = { render: () => ({ components: { StackedAdmonitions, Admonition }, setup() { const stackA = ref([initialItems[0], initialItems[1]]) const stackB = ref([initialItems[2]]) function dismissA(id: string) { stackA.value = stackA.value.filter((i) => i.id !== id) } function dismissB(id: string) { stackB.value = stackB.value.filter((i) => i.id !== id) } return { stackA, stackB, dismissA, dismissB } }, template: /* html */ `
`, }), }