feat(storybook): migrate stories to react

This commit is contained in:
vince 2026-06-19 23:45:54 +02:00
parent 92a8990e7a
commit 76fe468df5
64 changed files with 590 additions and 685 deletions

View file

@ -1,15 +1,14 @@
import type { StorybookConfig } from "@storybook/sveltekit"; import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = { const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|ts|svelte)"], stories: ["../src/**/*.stories.@(js|ts|tsx)"],
staticDirs: ["../static"], staticDirs: ["../static"],
addons: [ addons: [
"@storybook/addon-svelte-csf",
"@storybook/addon-a11y", "@storybook/addon-a11y",
"@storybook/addon-vitest", "@storybook/addon-vitest",
], ],
framework: { framework: {
name: "@storybook/sveltekit", name: "@storybook/react-vite",
options: {}, options: {},
}, },
docs: { docs: {

View file

@ -1,5 +1,5 @@
import "../src/app.css"; import "../src/app.css";
import type { Preview } from "@storybook/sveltekit"; import type { Preview } from "@storybook/react-vite";
import { setupWorker } from "msw/browser"; import { setupWorker } from "msw/browser";
import { externalApiHandlers } from "../src/lib/testing/external-api-mocks"; import { externalApiHandlers } from "../src/lib/testing/external-api-mocks";

View file

@ -1,21 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Badge from "../components/Badge.svelte";
const { Story } = defineMeta({
title: "Base UI/Badge",
component: Badge,
tags: ["autodocs"],
});
</script>
<Story name="Neutral" args={{ label: "Neutral", severity: "neutral" }} />
<Story name="Ok" args={{ label: "Operational", severity: "ok" }} />
<Story name="Warning" args={{ label: "Warning", severity: "warning" }} />
<Story name="Danger" args={{ label: "Incident", severity: "danger" }} />
<Story name="Stale" args={{ label: "Stale", severity: "stale" }} />
<Story name="Unavailable" args={{ label: "Unavailable", severity: "unavailable" }} />
<Story
name="Long Label"
args={{ label: "Long generic operational label", severity: "warning" }}
/>

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Badge } from "../components/Badge";
const meta = {
title: "UI/Badge",
component: Badge,
args: {
label: "Ready",
severity: "ok",
},
} satisfies Meta<typeof Badge>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,19 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Button from "../components/Button.svelte";
const { Story } = defineMeta({
title: "Base UI/Button",
component: Button,
tags: ["autodocs"],
});
</script>
<Story name="Default" args={{ label: "Refresh" }} />
<Story name="Secondary" args={{ label: "Preview", variant: "secondary" }} />
<Story name="Danger" args={{ label: "Stop", variant: "danger" }} />
<Story name="Ghost Outline" args={{ label: "Inspect", variant: "ghost" }} />
<Story name="Disabled" args={{ label: "Publish", disabled: true }} />
<Story name="Loading" args={{ label: "Sync", loading: true }} />
<Story name="Icon Leading" args={{ label: "Open", icon: "mdi:open-in-new" }} />
<Story name="Compact" args={{ label: "Apply", size: "compact" }} />

View file

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Button } from "../components/Button";
const meta = {
title: "UI/Button",
component: Button,
args: {
label: "Refresh",
icon: "mdi:refresh",
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {};
export const Secondary: Story = {
args: {
variant: "secondary",
},
};

View file

@ -1,22 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import CornerBracketFrame from "../components/CornerBracketFrame.svelte";
import GridFrame from "../components/GridFrame.svelte";
const { Story } = defineMeta({
title: "Style Primitives/CornerBracketFrame",
component: CornerBracketFrame,
tags: ["autodocs"],
});
</script>
<Story name="Neutral Medium" args={{ tone: "neutral", size: "md" }} />
<Story name="Accent Tight" args={{ tone: "accent", density: "tight" }} />
<Story name="Large Danger" args={{ tone: "danger", size: "lg" }} />
<Story name="Grid Set" asChild>
<GridFrame density="compact">
<CornerBracketFrame tone="neutral" size="sm" />
<CornerBracketFrame tone="accent" size="sm" />
<CornerBracketFrame tone="danger" size="sm" density="tight" />
</GridFrame>
</Story>

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { CornerBracketFrame } from "../components/CornerBracketFrame";
const meta = {
title: "UI/CornerBracketFrame",
component: CornerBracketFrame,
args: {
tone: "accent",
size: "md",
},
} satisfies Meta<typeof CornerBracketFrame>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,15 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import DashboardFrame from "../components/DashboardFrame.svelte";
import { emptyDashboard, fullCompositionDashboard, primaryDashboard } from "./story-data";
const { Story } = defineMeta({
title: "Composition/DashboardFrame",
component: DashboardFrame,
tags: ["autodocs"],
});
</script>
<Story name="Generic Runtime" args={{ dashboard: primaryDashboard }} />
<Story name="Dense Generic Runtime" args={{ dashboard: fullCompositionDashboard }} />
<Story name="Empty Regions" args={{ dashboard: emptyDashboard }} />

View file

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { DashboardFrame } from "../components/DashboardFrame";
import { fullCompositionDashboard, secondaryDashboard } from "./story-data";
const meta = {
title: "UI/DashboardFrame",
component: DashboardFrame,
args: {
dashboard: fullCompositionDashboard,
},
} satisfies Meta<typeof DashboardFrame>;
export default meta;
type Story = StoryObj<typeof meta>;
export const FullComposition: Story = {};
export const Secondary: Story = {
args: {
dashboard: secondaryDashboard,
},
};

View file

@ -1,31 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import DashboardHeader from "../components/DashboardHeader.svelte";
import { moduleBlocks } from "./story-data";
const { Story } = defineMeta({
title: "Composition/DashboardHeader",
component: DashboardHeader,
tags: ["autodocs"],
});
</script>
<Story
name="Title Subtitle Module"
args={{
eyebrow: "Command Surface",
title: "Operations Console",
subtitle: "Capacity and queue health",
module: moduleBlocks.compact,
}}
/>
<Story
name="Long Title"
args={{
eyebrow: "Preview Surface",
title: "Long Generic Dashboard Title That Must Wrap",
subtitle: "Secondary status text wraps independently",
module: moduleBlocks.long,
}}
/>
<Story name="No Module" args={{ title: "Standalone Console", subtitle: "Header without side module" }} />

View file

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { DashboardHeader } from "../components/DashboardHeader";
import { moduleBlocks } from "./story-data";
const meta = {
title: "UI/DashboardHeader",
component: DashboardHeader,
args: {
eyebrow: "Preview Surface",
title: "Operations Console",
subtitle: "Generic dashboard header",
module: moduleBlocks.compact,
},
} satisfies Meta<typeof DashboardHeader>;
export default meta;
type Story = StoryObj<typeof meta>;
export const WithModule: Story = {};

View file

@ -1,18 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import DashboardFrame from "../components/DashboardFrame.svelte";
import { fullCompositionDashboard } from "./story-data";
const { Story } = defineMeta({
title: "Composition/DashboardOnePager",
component: DashboardFrame,
tags: ["autodocs"],
});
</script>
<Story name="Desktop Generic Dashboard" args={{ dashboard: fullCompositionDashboard }} />
<Story name="Mobile Generic Dashboard" asChild>
<div style="max-width: 390px">
<DashboardFrame dashboard={fullCompositionDashboard} />
</div>
</Story>

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { DashboardFrame } from "../components/DashboardFrame";
import { fullCompositionDashboard } from "./story-data";
const meta = {
title: "UI/DashboardOnePager",
component: DashboardFrame,
args: {
dashboard: fullCompositionDashboard,
},
} satisfies Meta<typeof DashboardFrame>;
export default meta;
type Story = StoryObj<typeof meta>;
export const OnePager: Story = {};

View file

@ -1,23 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import DiagonalStripeField from "../components/DiagonalStripeField.svelte";
import Panel from "../components/Panel.svelte";
import SignalTrace from "../components/SignalTrace.svelte";
const { Story } = defineMeta({
title: "Style Primitives/DiagonalStripeField",
component: DiagonalStripeField,
tags: ["autodocs"],
});
</script>
<Story name="Accent Regular" args={{ density: "regular", tone: "accent", size: "md" }} />
<Story name="Tight Warning" args={{ density: "tight", tone: "warning", direction: "backward" }} />
<Story name="Large Danger" args={{ density: "open", tone: "danger", size: "lg" }} />
<Story name="Composed Divider" asChild>
<Panel title="Generic Surface">
<DiagonalStripeField density="tight" size="sm" />
<SignalTrace tone="accent" />
<DiagonalStripeField direction="backward" tone="neutral" />
</Panel>
</Story>

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { DiagonalStripeField } from "../components/DiagonalStripeField";
const meta = {
title: "UI/DiagonalStripeField",
component: DiagonalStripeField,
args: {
tone: "accent",
density: "regular",
},
} satisfies Meta<typeof DiagonalStripeField>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,33 +0,0 @@
<script lang="ts">
import { onMount, tick, type Snippet } from "svelte";
let {
children,
selector = "button, a[href]",
}: {
children?: Snippet;
selector?: string;
} = $props();
let root: HTMLDivElement;
onMount(async () => {
await tick();
root.querySelector<HTMLElement>(selector)?.focus();
});
</script>
<div class="focus-preview" bind:this={root}>
{@render children?.()}
</div>
<style>
.focus-preview {
display: inline-grid;
padding: var(--ui-space-2);
}
.focus-preview :global(:is(a, button)) {
box-shadow: var(--ui-focus-ring);
}
</style>

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import FooterCell from "../components/FooterCell.svelte";
import { statusItems } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/FooterCell",
component: FooterCell,
tags: ["autodocs"],
});
</script>
<Story name="Label Value" args={{ item: statusItems.ok }} />
<Story name="Action Cell" args={{ item: statusItems.action }} />
<Story name="Stale" args={{ item: statusItems.stale }} />
<Story name="Long Value" args={{ item: statusItems.long }} />

View file

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { FooterCell } from "../components/FooterCell";
import { statusItems } from "./story-data";
const meta = {
title: "UI/FooterCell",
component: FooterCell,
args: {
item: statusItems.ok,
},
} satisfies Meta<typeof FooterCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Operational: Story = {};
export const Linked: Story = {
args: {
item: statusItems.action,
},
};

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import FooterStatusCell from "../components/FooterStatusCell.svelte";
import { statusItems } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/FooterStatusCell",
component: FooterStatusCell,
tags: ["autodocs"],
});
</script>
<Story name="Label Value" args={{ item: statusItems.ok }} />
<Story name="Action Cell" args={{ item: statusItems.action }} />
<Story name="Stale" args={{ item: statusItems.stale }} />
<Story name="Long Value" args={{ item: statusItems.long }} />

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { FooterStatusCell } from "../components/FooterStatusCell";
import { statusItems } from "./story-data";
const meta = {
title: "UI/FooterStatusCell",
component: FooterStatusCell,
args: {
item: statusItems.degraded,
},
} satisfies Meta<typeof FooterStatusCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Warning: Story = {};

View file

@ -1,37 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import GridFrame from "../components/GridFrame.svelte";
import SystemState from "../components/SystemState.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/GridFrame",
component: GridFrame,
tags: ["autodocs"],
});
</script>
<Story name="Desktop Grid" asChild>
<GridFrame>
<SystemState title="Cell One" />
<SystemState title="Cell Two" />
<SystemState title="Cell Three" />
<SystemState title="Cell Four" />
</GridFrame>
</Story>
<Story name="Tablet Wrap" asChild>
<div style="max-width: 760px">
<GridFrame density="dense">
<SystemState title="Cell One" />
<SystemState title="Cell Two" />
<SystemState title="Cell Three" />
</GridFrame>
</div>
</Story>
<Story name="Mobile Stack" asChild>
<div style="max-width: 390px">
<GridFrame density="compact">
<SystemState title="Cell One" />
<SystemState title="Cell Two" />
</GridFrame>
</div>
</Story>

View file

@ -0,0 +1,25 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { GridFrame } from "../components/GridFrame";
import { ModuleCard } from "../components/ModuleCard";
import { moduleBlocks } from "./story-data";
const meta = {
title: "UI/GridFrame",
component: GridFrame,
render: (args) => (
<GridFrame {...args}>
<ModuleCard module={moduleBlocks.compact} />
<ModuleCard module={moduleBlocks.unavailable} />
<ModuleCard module={moduleBlocks.long} />
</GridFrame>
),
} satisfies Meta<typeof GridFrame>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Dense: Story = {
args: {
density: "dense",
},
};

View file

@ -1,22 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import IconButton from "../components/IconButton.svelte";
import FocusPreview from "./FocusPreview.svelte";
const { Story } = defineMeta({
title: "Base UI/IconButton",
component: IconButton,
tags: ["autodocs"],
});
</script>
<Story name="Refresh" args={{ icon: "mdi:refresh", label: "Refresh" }} />
<Story name="Open" args={{ icon: "mdi:open-in-new", label: "Open" }} />
<Story name="Inspect" args={{ icon: "mdi:magnify-scan", label: "Inspect" }} />
<Story name="Disabled" args={{ icon: "mdi:refresh", label: "Disabled", disabled: true }} />
<Story name="Active" args={{ icon: "mdi:refresh", label: "Active", active: true }} />
<Story name="Focus Visible" asChild>
<FocusPreview>
<IconButton icon="mdi:keyboard" label="Focus visible" />
</FocusPreview>
</Story>

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { IconButton } from "../components/IconButton";
const meta = {
title: "UI/IconButton",
component: IconButton,
args: {
icon: "mdi:refresh",
label: "Refresh status",
},
} satisfies Meta<typeof IconButton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,21 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import IconGlyph from "../components/IconGlyph.svelte";
const { Story } = defineMeta({
title: "Base UI/IconGlyph",
component: IconGlyph,
tags: ["autodocs"],
});
</script>
<Story name="Valid Icon" args={{ name: "mdi:server-network" }} />
<Story name="Missing Icon Fallback" args={{ name: undefined }} />
<Story name="Small" args={{ name: "mdi:radar", size: "sm" }} />
<Story name="Medium" args={{ name: "mdi:radar", size: "md" }} />
<Story name="Large" args={{ name: "mdi:radar", size: "lg" }} />
<Story name="Severity Color" asChild>
<div style="color: var(--ui-color-warning)">
<IconGlyph name="mdi:alert-outline" size="lg" />
</div>
</Story>

View file

@ -0,0 +1,17 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { IconGlyph } from "../components/IconGlyph";
const meta = {
title: "UI/IconGlyph",
component: IconGlyph,
args: {
name: "mdi:server-network",
label: "Generic service",
size: "md",
},
} satisfies Meta<typeof IconGlyph>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,13 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import LineChart from "../components/LineChart.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/LineChart",
component: LineChart,
});
</script>
<Story name="Neutral Trend" args={{ values: [12, 18, 15, 28, 24, 32] }} />
<Story name="Warning Trend" args={{ values: [22, 34, 48, 62, 74, 70], severity: "warning" }} />
<Story name="Danger Trend" args={{ values: [20, 40, 64, 82, 90, 94], severity: "danger" }} />

View file

@ -0,0 +1,17 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { LineChart } from "../components/LineChart";
const meta = {
title: "UI/LineChart",
component: LineChart,
args: {
values: [12, 18, 24, 20, 36, 44],
severity: "ok",
label: "Generic trend",
},
} satisfies Meta<typeof LineChart>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Trend: Story = {};

View file

@ -1,15 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ModuleCard from "../components/ModuleCard.svelte";
import { moduleBlocks } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ModuleCard",
component: ModuleCard,
tags: ["autodocs"],
});
</script>
<Story name="Compact Summary" args={{ module: moduleBlocks.compact }} />
<Story name="Unavailable" args={{ module: moduleBlocks.unavailable }} />
<Story name="Long Value" args={{ module: moduleBlocks.long }} />

View file

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ModuleCard } from "../components/ModuleCard";
import { moduleBlocks } from "./story-data";
const meta = {
title: "UI/ModuleCard",
component: ModuleCard,
args: {
module: moduleBlocks.compact,
},
} satisfies Meta<typeof ModuleCard>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Compact: Story = {};
export const Long: Story = {
args: {
module: moduleBlocks.long,
},
};

View file

@ -1,42 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Panel from "../components/Panel.svelte";
import SystemState from "../components/SystemState.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/Panel",
component: Panel,
tags: ["autodocs"],
});
</script>
<Story name="Default" asChild>
<Panel title="Generic Panel">
<p>Reusable panel content.</p>
</Panel>
</Story>
<Story name="Dense" asChild>
<Panel title="Dense Panel" density="compact">
<p>Compact repeated content.</p>
</Panel>
</Story>
<Story name="Warning" asChild>
<Panel title="Warning Panel">
<SystemState title="Warning" detail="Generic warning state" severity="warning" />
</Panel>
</Story>
<Story name="Danger" asChild>
<Panel title="Danger Panel">
<SystemState title="Danger" detail="Generic danger state" severity="danger" />
</Panel>
</Story>
<Story name="Empty" asChild>
<Panel title="Empty Panel">
<SystemState title="Empty" detail="No generic records available" severity="neutral" />
</Panel>
</Story>
<Story name="Nested Content Guard" asChild>
<Panel title="Outer Panel">
<SystemState title="Nested content" detail="No nested card styling required" />
</Panel>
</Story>

View file

@ -0,0 +1,23 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Panel } from "../components/Panel";
import { ServiceRow } from "../components/ServiceRow";
import { serviceRows } from "./story-data";
const meta = {
title: "UI/Panel",
component: Panel,
render: (args) => (
<Panel {...args}>
<ServiceRow service={serviceRows.normal} />
<ServiceRow service={serviceRows.degraded} />
</Panel>
),
args: {
title: "Generic Panel",
},
} satisfies Meta<typeof Panel>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Dense: Story = {};

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ProgressMeter from "../components/ProgressMeter.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/ProgressMeter",
component: ProgressMeter,
tags: ["autodocs"],
});
</script>
<Story name="Low" args={{ value: 12, label: "Low progress" }} />
<Story name="Normal" args={{ value: 63, severity: "ok", label: "Normal progress" }} />
<Story name="Warn" args={{ value: 76, severity: "warning", label: "Warning progress" }} />
<Story name="Danger" args={{ value: 94, severity: "danger", label: "Danger progress" }} />
<Story name="Empty" args={{ value: undefined, label: "Missing progress" }} />

View file

@ -0,0 +1,17 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ProgressMeter } from "../components/ProgressMeter";
const meta = {
title: "UI/ProgressMeter",
component: ProgressMeter,
args: {
value: 72,
severity: "ok",
label: "Capacity",
},
} satisfies Meta<typeof ProgressMeter>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Filled: Story = {};

View file

@ -1,20 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ScanlineField from "../components/ScanlineField.svelte";
import Panel from "../components/Panel.svelte";
const { Story } = defineMeta({
title: "Style Primitives/ScanlineField",
component: ScanlineField,
tags: ["autodocs"],
});
</script>
<Story name="Soft Neutral" args={{ intensity: "soft", tone: "neutral", size: "md" }} />
<Story name="Medium Accent" args={{ intensity: "medium", tone: "accent", size: "md" }} />
<Story name="Hard Warning" args={{ intensity: "hard", tone: "warning", size: "lg" }} />
<Story name="Panel Backdrop" asChild>
<Panel title="Signal Surface">
<ScanlineField intensity="hard" tone="accent" />
</Panel>
</Story>

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ScanlineField } from "../components/ScanlineField";
const meta = {
title: "UI/ScanlineField",
component: ScanlineField,
args: {
tone: "accent",
intensity: "medium",
},
} satisfies Meta<typeof ScanlineField>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,21 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Separator from "../components/Separator.svelte";
import Panel from "../components/Panel.svelte";
const { Story } = defineMeta({
title: "Base UI/Separator",
component: Separator,
tags: ["autodocs"],
});
</script>
<Story name="Horizontal" args={{ orientation: "horizontal" }} />
<Story name="Vertical" args={{ orientation: "vertical" }} />
<Story name="Dense Panel Use" asChild>
<Panel title="Dense Panel">
<p>Generic section one</p>
<Separator dense />
<p>Generic section two</p>
</Panel>
</Story>

View file

@ -0,0 +1,15 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Separator } from "../components/Separator";
const meta = {
title: "UI/Separator",
component: Separator,
args: {
orientation: "horizontal",
},
} satisfies Meta<typeof Separator>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Horizontal: Story = {};

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ServiceGroupPanel from "../components/ServiceGroupPanel.svelte";
import { serviceGroups } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ServiceGroupPanel",
component: ServiceGroupPanel,
tags: ["autodocs"],
});
</script>
<Story name="Short List" args={{ group: serviceGroups.short }} />
<Story name="Long List" args={{ group: serviceGroups.long }} />
<Story name="Empty Group" args={{ group: serviceGroups.empty }} />
<Story name="Mixed Statuses" args={{ group: serviceGroups.mixed }} />

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ServiceGroupPanel } from "../components/ServiceGroupPanel";
import { serviceGroups } from "./story-data";
const meta = {
title: "UI/ServiceGroupPanel",
component: ServiceGroupPanel,
args: {
group: serviceGroups.mixed,
},
} satisfies Meta<typeof ServiceGroupPanel>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Mixed: Story = {};

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ServicePanel from "../components/ServicePanel.svelte";
import { serviceGroups } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ServicePanel",
component: ServicePanel,
tags: ["autodocs"],
});
</script>
<Story name="Short List" args={{ group: serviceGroups.short }} />
<Story name="Long List" args={{ group: serviceGroups.long }} />
<Story name="Empty Group" args={{ group: serviceGroups.empty }} />
<Story name="Mixed Statuses" args={{ group: serviceGroups.mixed }} />

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ServicePanel } from "../components/ServicePanel";
import { serviceGroups } from "./story-data";
const meta = {
title: "UI/ServicePanel",
component: ServicePanel,
args: {
group: serviceGroups.long,
},
} satisfies Meta<typeof ServicePanel>;
export default meta;
type Story = StoryObj<typeof meta>;
export const LongList: Story = {};

View file

@ -1,24 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ServiceRow from "../components/ServiceRow.svelte";
import FocusPreview from "./FocusPreview.svelte";
import { serviceRows } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/ServiceRow",
component: ServiceRow,
tags: ["autodocs"],
});
</script>
<Story name="Normal" args={{ service: serviceRows.normal }} />
<Story name="Down" args={{ service: serviceRows.down }} />
<Story name="Degraded" args={{ service: serviceRows.degraded }} />
<Story name="Stale" args={{ service: serviceRows.stale }} />
<Story name="No Link" args={{ service: serviceRows.noLink }} />
<Story name="Long Title Description" args={{ service: serviceRows.long }} />
<Story name="Keyboard Focus" asChild>
<FocusPreview selector="a">
<ServiceRow service={serviceRows.normal} />
</FocusPreview>
</Story>

View file

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ServiceRow } from "../components/ServiceRow";
import { serviceRows } from "./story-data";
const meta = {
title: "UI/ServiceRow",
component: ServiceRow,
args: {
service: serviceRows.normal,
},
} satisfies Meta<typeof ServiceRow>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Normal: Story = {};
export const Warning: Story = {
args: {
service: serviceRows.degraded,
},
};

View file

@ -1,21 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import SignalTrace from "../components/SignalTrace.svelte";
import Panel from "../components/Panel.svelte";
const { Story } = defineMeta({
title: "Style Primitives/SignalTrace",
component: SignalTrace,
tags: ["autodocs"],
});
</script>
<Story name="Horizontal Accent" args={{ tone: "accent", orientation: "horizontal" }} />
<Story name="Horizontal Warning Tight" args={{ tone: "warning", density: "tight" }} />
<Story name="Vertical Neutral" args={{ tone: "neutral", orientation: "vertical" }} />
<Story name="Panel Trace" asChild>
<Panel title="Generic Flow">
<SignalTrace tone="accent" />
<SignalTrace tone="warning" density="tight" />
</Panel>
</Story>

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { SignalTrace } from "../components/SignalTrace";
const meta = {
title: "UI/SignalTrace",
component: SignalTrace,
args: {
tone: "accent",
orientation: "horizontal",
},
} satisfies Meta<typeof SignalTrace>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Horizontal: Story = {};

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Sparkline from "../components/Sparkline.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/Sparkline",
component: Sparkline,
tags: ["autodocs"],
});
</script>
<Story name="Flat" args={{ values: [20, 20, 20, 20, 20, 20] }} />
<Story name="Rising" args={{ values: [4, 12, 18, 28, 38, 54], severity: "ok" }} />
<Story name="Spiky" args={{ values: [8, 50, 12, 64, 18, 72], severity: "warning" }} />
<Story name="Missing Data" args={{ values: [] }} />
<Story name="Stale" args={{ values: [32, 31, 31, 30, 30, 30], severity: "stale" }} />

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Sparkline } from "../components/Sparkline";
const meta = {
title: "UI/Sparkline",
component: Sparkline,
args: {
values: [8, 14, 12, 24, 18, 30],
severity: "ok",
},
} satisfies Meta<typeof Sparkline>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,18 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import StatusBadge from "../components/StatusBadge.svelte";
const { Story } = defineMeta({
title: "Base UI/StatusBadge",
component: StatusBadge,
tags: ["autodocs"],
});
</script>
<Story name="Neutral" args={{ label: "Neutral" }} />
<Story name="Ok" args={{ label: "Operational", severity: "ok" }} />
<Story name="Warning" args={{ label: "Warning", severity: "warning" }} />
<Story name="Danger" args={{ label: "Danger", severity: "danger" }} />
<Story name="Stale" args={{ label: "Stale", severity: "stale" }} />
<Story name="Unavailable" args={{ label: "Unavailable", severity: "unavailable" }} />
<Story name="Long Label" args={{ label: "Long Generic Badge Label", severity: "neutral" }} />

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { StatusBadge } from "../components/StatusBadge";
const meta = {
title: "UI/StatusBadge",
component: StatusBadge,
args: {
label: "Ready",
severity: "ok",
},
} satisfies Meta<typeof StatusBadge>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Ready: Story = {};

View file

@ -1,27 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import StatusStrip from "../components/StatusStrip.svelte";
import { mixedStatusStrip, statusItems } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/StatusStrip",
component: StatusStrip,
tags: ["autodocs"],
});
</script>
<Story
name="All Operational"
args={{
items: [
{ ...statusItems.ok, id: "status-ok-a" },
{ ...statusItems.ok, id: "status-ok-b", label: "Queue" },
{ ...statusItems.ok, id: "status-ok-c", label: "Sync" },
],
}}
/>
<Story name="Degraded" args={{ items: [statusItems.ok, statusItems.degraded, statusItems.stale] }} />
<Story name="Incident" args={{ items: [statusItems.ok, statusItems.incident, statusItems.degraded] }} />
<Story name="Syncing" args={{ items: [statusItems.syncing, statusItems.ok, statusItems.action] }} />
<Story name="Stale" args={{ items: [statusItems.stale, statusItems.long] }} />
<Story name="Mixed Compact" args={{ items: mixedStatusStrip, compact: true }} />

View file

@ -0,0 +1,17 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { StatusStrip } from "../components/StatusStrip";
import { mixedStatusStrip } from "./story-data";
const meta = {
title: "UI/StatusStrip",
component: StatusStrip,
args: {
id: "generic-status",
items: mixedStatusStrip,
},
} satisfies Meta<typeof StatusStrip>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Mixed: Story = {};

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import SystemState from "../components/SystemState.svelte";
const { Story } = defineMeta({
title: "Dashboard Primitives/SystemState",
component: SystemState,
tags: ["autodocs"],
});
</script>
<Story name="Loading" args={{ title: "Loading", detail: "Waiting for records", severity: "loading", icon: "mdi:progress-clock" }} />
<Story name="Empty" args={{ title: "Empty", detail: "No generic records available", icon: "mdi:tray" }} />
<Story name="Error" args={{ title: "Error", detail: "Generic request failed", severity: "danger", icon: "mdi:alert-octagon-outline" }} />
<Story name="Unavailable" args={{ title: "Unavailable", detail: "External signal unavailable", severity: "unavailable", icon: "mdi:cloud-off-outline" }} />
<Story name="Validation Failure" args={{ title: "Validation Failure", detail: "Document did not match schema", severity: "warning", icon: "mdi:file-alert-outline" }} />

View file

@ -0,0 +1,18 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { SystemState } from "../components/SystemState";
const meta = {
title: "UI/SystemState",
component: SystemState,
args: {
title: "Loading Dashboard",
detail: "Fetching active model",
severity: "loading",
icon: "mdi:progress-clock",
},
} satisfies Meta<typeof SystemState>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Loading: Story = {};

View file

@ -1,21 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import TelemetryCard from "../components/TelemetryCard.svelte";
import { telemetryCards } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/TelemetryCard",
component: TelemetryCard,
tags: ["autodocs"],
});
</script>
<Story name="Percent" args={{ card: telemetryCards.percent }} />
<Story name="Bytes" args={{ card: telemetryCards.bytes }} />
<Story name="Temperature" args={{ card: telemetryCards.temperature }} />
<Story name="Latency" args={{ card: telemetryCards.latency }} />
<Story name="No Data" args={{ card: telemetryCards.unavailable }} />
<Story name="Stale" args={{ card: telemetryCards.stale }} />
<Story name="Warning" args={{ card: telemetryCards.latency }} />
<Story name="Danger" args={{ card: telemetryCards.danger }} />
<Story name="Long Source Label" args={{ card: telemetryCards.long }} />

View file

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { TelemetryCard } from "../components/TelemetryCard";
import { telemetryCards } from "./story-data";
const meta = {
title: "UI/TelemetryCard",
component: TelemetryCard,
args: {
card: telemetryCards.percent,
},
} satisfies Meta<typeof TelemetryCard>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Percent: Story = {};
export const Danger: Story = {
args: {
card: telemetryCards.danger,
},
};

View file

@ -1,15 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import TelemetryGrid from "../components/TelemetryGrid.svelte";
import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/TelemetryGrid",
component: TelemetryGrid,
tags: ["autodocs"],
});
</script>
<Story name="Eight Card Density" args={{ cards: eightTelemetryCards }} />
<Story name="Sixteen Card Density" args={{ cards: sixteenTelemetryCards }} />
<Story name="Empty" args={{ cards: [] }} />

View file

@ -0,0 +1,21 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { TelemetryGrid } from "../components/TelemetryGrid";
import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
const meta = {
title: "UI/TelemetryGrid",
component: TelemetryGrid,
args: {
cards: eightTelemetryCards,
},
} satisfies Meta<typeof TelemetryGrid>;
export default meta;
type Story = StoryObj<typeof meta>;
export const EightCards: Story = {};
export const SixteenCards: Story = {
args: {
cards: sixteenTelemetryCards,
},
};

View file

@ -1,15 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import TelemetryStrip from "../components/TelemetryStrip.svelte";
import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data";
const { Story } = defineMeta({
title: "Composition/TelemetryStrip",
component: TelemetryStrip,
tags: ["autodocs"],
});
</script>
<Story name="Eight Card Density" args={{ cards: eightTelemetryCards }} />
<Story name="Sixteen Card Density" args={{ cards: sixteenTelemetryCards }} />
<Story name="Mixed Severity Wrap" args={{ cards: eightTelemetryCards }} />

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { TelemetryStrip } from "../components/TelemetryStrip";
import { eightTelemetryCards } from "./story-data";
const meta = {
title: "UI/TelemetryStrip",
component: TelemetryStrip,
args: {
cards: eightTelemetryCards,
},
} satisfies Meta<typeof TelemetryStrip>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -1,16 +0,0 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import WeatherModule from "../components/WeatherModule.svelte";
import { moduleBlocks } from "./story-data";
const { Story } = defineMeta({
title: "Dashboard Primitives/WeatherModule",
component: WeatherModule,
tags: ["autodocs"],
});
</script>
<Story name="Compact Summary" args={{ module: moduleBlocks.compact }} />
<Story name="Unavailable" args={{ module: moduleBlocks.unavailable }} />
<Story name="Long Location Value" args={{ module: moduleBlocks.long }} />
<Story name="Generic Module Variant" args={{ module: { ...moduleBlocks.compact, title: "Signal Module", icon: "mdi:radar" } }} />

View file

@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { WeatherModule } from "../components/WeatherModule";
import { moduleBlocks } from "./story-data";
const meta = {
title: "UI/WeatherModule",
component: WeatherModule,
args: {
module: moduleBlocks.compact,
},
} satisfies Meta<typeof WeatherModule>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View file

@ -7,35 +7,36 @@ const componentsDir = join(root, "src/lib/ui/components");
const storiesDir = join(root, "src/lib/ui/stories"); const storiesDir = join(root, "src/lib/ui/stories");
const requiredStoryFiles = [ const requiredStoryFiles = [
"Badge.stories.svelte", "Badge.stories.tsx",
"Button.stories.svelte", "Button.stories.tsx",
"DashboardFrame.stories.svelte", "DashboardFrame.stories.tsx",
"DashboardHeader.stories.svelte", "DashboardHeader.stories.tsx",
"DashboardOnePager.stories.svelte", "DashboardOnePager.stories.tsx",
"CornerBracketFrame.stories.svelte", "CornerBracketFrame.stories.tsx",
"DiagonalStripeField.stories.svelte", "DiagonalStripeField.stories.tsx",
"FooterCell.stories.svelte", "FooterCell.stories.tsx",
"FooterStatusCell.stories.svelte", "FooterStatusCell.stories.tsx",
"GridFrame.stories.svelte", "GridFrame.stories.tsx",
"IconButton.stories.svelte", "IconButton.stories.tsx",
"IconGlyph.stories.svelte", "IconGlyph.stories.tsx",
"ModuleCard.stories.svelte", "LineChart.stories.tsx",
"Panel.stories.svelte", "ModuleCard.stories.tsx",
"ProgressMeter.stories.svelte", "Panel.stories.tsx",
"Separator.stories.svelte", "ProgressMeter.stories.tsx",
"ServiceGroupPanel.stories.svelte", "Separator.stories.tsx",
"ServicePanel.stories.svelte", "ServiceGroupPanel.stories.tsx",
"ServiceRow.stories.svelte", "ServicePanel.stories.tsx",
"SignalTrace.stories.svelte", "ServiceRow.stories.tsx",
"Sparkline.stories.svelte", "SignalTrace.stories.tsx",
"StatusBadge.stories.svelte", "Sparkline.stories.tsx",
"StatusStrip.stories.svelte", "StatusBadge.stories.tsx",
"SystemState.stories.svelte", "StatusStrip.stories.tsx",
"ScanlineField.stories.svelte", "SystemState.stories.tsx",
"TelemetryCard.stories.svelte", "ScanlineField.stories.tsx",
"TelemetryGrid.stories.svelte", "TelemetryCard.stories.tsx",
"TelemetryStrip.stories.svelte", "TelemetryGrid.stories.tsx",
"WeatherModule.stories.svelte", "TelemetryStrip.stories.tsx",
"WeatherModule.stories.tsx",
] as const; ] as const;
const forbiddenStoryContent = [ const forbiddenStoryContent = [
@ -64,8 +65,8 @@ describe("Storybook inventory", () => {
test("keeps component and story files paired as the UI inventory changes", () => { test("keeps component and story files paired as the UI inventory changes", () => {
const componentStoryFiles = readdirSync(componentsDir) const componentStoryFiles = readdirSync(componentsDir)
.filter((filename) => filename.endsWith(".svelte")) .filter((filename) => filename.endsWith(".tsx") && !filename.endsWith(".test.tsx"))
.map((filename) => filename.replace(".svelte", ".stories.svelte")); .map((filename) => filename.replace(".tsx", ".stories.tsx"));
for (const filename of componentStoryFiles) { for (const filename of componentStoryFiles) {
expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true); expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true);
@ -74,7 +75,7 @@ describe("Storybook inventory", () => {
test("keeps Storybook fixtures generic and content-free", () => { test("keeps Storybook fixtures generic and content-free", () => {
const storyText = readdirSync(storiesDir) const storyText = readdirSync(storiesDir)
.filter((filename) => filename.endsWith(".svelte") || filename.endsWith(".ts")) .filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts"))
.map((filename) => readFileSync(join(storiesDir, filename), "utf8").toLowerCase()) .map((filename) => readFileSync(join(storiesDir, filename), "utf8").toLowerCase())
.join("\n"); .join("\n");
@ -90,12 +91,12 @@ describe("Storybook inventory", () => {
"ScanlineField", "ScanlineField",
"SignalTrace", "SignalTrace",
]) { ]) {
expect(existsSync(join(componentsDir, `${component}.svelte`)), `${component} is missing`).toBe( expect(existsSync(join(componentsDir, `${component}.tsx`)), `${component} is missing`).toBe(
true, true,
); );
expect( expect(
existsSync(join(storiesDir, `${component}.stories.svelte`)), existsSync(join(storiesDir, `${component}.stories.tsx`)),
`${component}.stories.svelte is missing`, `${component}.stories.tsx is missing`,
).toBe(true); ).toBe(true);
} }
}); });
@ -107,7 +108,7 @@ describe("Storybook inventory", () => {
"ScanlineField", "ScanlineField",
"SignalTrace", "SignalTrace",
]) { ]) {
const source = readFileSync(join(componentsDir, `${component}.svelte`), "utf8"); const source = readFileSync(join(componentsDir, `${component}.tsx`), "utf8");
expect(source).toContain('aria-hidden="true"'); expect(source).toContain('aria-hidden="true"');
} }
@ -115,8 +116,16 @@ describe("Storybook inventory", () => {
test("does not add deferred form/navigation primitives", () => { test("does not add deferred form/navigation primitives", () => {
for (const component of ["Input", "ToggleGroup", "ScrollArea"]) { for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
expect(existsSync(join(root, `src/lib/ui/components/${component}.svelte`))).toBe(false); expect(existsSync(join(root, `src/lib/ui/components/${component}.tsx`))).toBe(false);
expect(existsSync(join(storiesDir, `${component}.stories.svelte`))).toBe(false); expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false);
} }
}); });
test("does not keep Svelte stories in the React Storybook inventory", () => {
const svelteStories = readdirSync(storiesDir).filter((filename) =>
filename.endsWith(".stories.svelte"),
);
expect(svelteStories).toEqual([]);
});
}); });