@checkstack/ui 1.7.1 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.storybook/main.ts +22 -0
  2. package/.storybook/manager-head.html +2 -0
  3. package/.storybook/mock-access-api.ts +5 -0
  4. package/.storybook/preview.css +5 -0
  5. package/.storybook/preview.tsx +73 -0
  6. package/CHANGELOG.md +234 -0
  7. package/package.json +24 -10
  8. package/postcss.config.js +6 -0
  9. package/src/components/Card.tsx +4 -3
  10. package/src/components/Dialog.tsx +2 -2
  11. package/src/components/EmptyState.tsx +48 -3
  12. package/src/components/InstanceScopeBanner.tsx +46 -0
  13. package/src/components/LinksEditor.tsx +156 -0
  14. package/src/components/UserMenu.tsx +1 -1
  15. package/src/index.ts +2 -0
  16. package/stories/AccessDenied.stories.tsx +17 -0
  17. package/stories/AccessGate.stories.tsx +49 -0
  18. package/stories/Accordion.stories.tsx +56 -0
  19. package/stories/Alert.stories.tsx +88 -0
  20. package/stories/AmbientBackground.stories.tsx +28 -0
  21. package/stories/AnimatedCounter.stories.tsx +41 -0
  22. package/stories/AnimatedNumber.stories.tsx +39 -0
  23. package/stories/BackLink.stories.tsx +32 -0
  24. package/stories/Badge.stories.tsx +37 -0
  25. package/stories/Button.stories.tsx +57 -0
  26. package/stories/Card.stories.tsx +39 -0
  27. package/stories/Checkbox.stories.tsx +45 -0
  28. package/stories/CodeEditor.stories.tsx +67 -0
  29. package/stories/ColorPicker.stories.tsx +24 -0
  30. package/stories/CommandPalette.stories.tsx +36 -0
  31. package/stories/ConfirmationModal.stories.tsx +40 -0
  32. package/stories/DateRangeFilter.stories.tsx +30 -0
  33. package/stories/DateTimePicker.stories.tsx +26 -0
  34. package/stories/Dialog.stories.tsx +62 -0
  35. package/stories/DynamicForm.stories.tsx +83 -0
  36. package/stories/DynamicIcon.stories.tsx +52 -0
  37. package/stories/EditableText.stories.tsx +29 -0
  38. package/stories/Feedback.stories.tsx +45 -0
  39. package/stories/IDELayout.stories.tsx +70 -0
  40. package/stories/InfoBanner.stories.tsx +41 -0
  41. package/stories/Input.stories.tsx +29 -0
  42. package/stories/InstanceScopeBanner.stories.tsx +35 -0
  43. package/stories/Introduction.mdx +50 -0
  44. package/stories/Label.stories.tsx +21 -0
  45. package/stories/LinksEditor.stories.tsx +37 -0
  46. package/stories/Markdown.stories.tsx +35 -0
  47. package/stories/Menu.stories.tsx +35 -0
  48. package/stories/MetricTile.stories.tsx +31 -0
  49. package/stories/NavItem.stories.tsx +29 -0
  50. package/stories/NotFound.stories.tsx +17 -0
  51. package/stories/Page.stories.tsx +29 -0
  52. package/stories/PageLayout.stories.tsx +59 -0
  53. package/stories/PaginatedList.stories.tsx +44 -0
  54. package/stories/Pagination.stories.tsx +31 -0
  55. package/stories/PerformanceProvider.stories.tsx +33 -0
  56. package/stories/PluginConfigForm.stories.tsx +64 -0
  57. package/stories/Popover.stories.tsx +30 -0
  58. package/stories/SectionHeader.stories.tsx +20 -0
  59. package/stories/Select.stories.tsx +36 -0
  60. package/stories/Sheet.stories.tsx +59 -0
  61. package/stories/Slider.stories.tsx +24 -0
  62. package/stories/StatusCard.stories.tsx +30 -0
  63. package/stories/StatusUpdateTimeline.stories.tsx +77 -0
  64. package/stories/StrategyConfigCard.stories.tsx +57 -0
  65. package/stories/SubscribeButton.stories.tsx +27 -0
  66. package/stories/Table.stories.tsx +56 -0
  67. package/stories/Tabs.stories.tsx +40 -0
  68. package/stories/TerminalFeed.stories.tsx +47 -0
  69. package/stories/Textarea.stories.tsx +25 -0
  70. package/stories/ThemeProvider.stories.tsx +38 -0
  71. package/stories/Toast.stories.tsx +32 -0
  72. package/stories/Toggle.stories.tsx +43 -0
  73. package/stories/Tooltip.stories.tsx +20 -0
  74. package/stories/UserMenu.stories.tsx +35 -0
  75. package/tailwind.config.js +74 -0
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { InstanceScopeBanner } from "../src/components/InstanceScopeBanner";
3
+
4
+ const meta: Meta<typeof InstanceScopeBanner> = {
5
+ title: "Components/Feedback/InstanceScopeBanner",
6
+ component: InstanceScopeBanner,
7
+ tags: ["autodocs"],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof InstanceScopeBanner>;
12
+
13
+ export const InstanceScope: Story = {
14
+ args: {
15
+ scope: "instance",
16
+ subject: "Queue",
17
+ recommendation: "Switch to a Redis-backed queue to see cluster-wide totals.",
18
+ },
19
+ };
20
+
21
+ export const ClusterScope: Story = {
22
+ args: {
23
+ scope: "cluster",
24
+ subject: "Queue",
25
+ recommendation: "Banner is hidden when scope is 'cluster'.",
26
+ },
27
+ render: (args) => (
28
+ <div>
29
+ <InstanceScopeBanner {...args} />
30
+ <p className="text-sm text-muted-foreground">
31
+ Renders nothing — confirm by inspecting the DOM.
32
+ </p>
33
+ </div>
34
+ ),
35
+ };
@@ -0,0 +1,50 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Introduction" />
4
+
5
+ # Checkstack UI
6
+
7
+ This is the component library for Checkstack plugin developers. Every primitive
8
+ exported from `@checkstack/ui` is intended to be safe for plugin use — both in
9
+ core plugins shipped in this repo and in third-party plugins published to npm.
10
+
11
+ ## Why this exists
12
+
13
+ Plugins ship their own React components but render *inside* the host frontend's
14
+ React tree, so they must use the host's design tokens, theme, and spacing scale.
15
+ The `@checkstack/ui` package is the public surface for that. If a primitive
16
+ isn't documented here, treat it as an internal implementation detail.
17
+
18
+ ## How to use
19
+
20
+ ```tsx
21
+ import { Button, Card, CardHeader, CardTitle, CardContent } from "@checkstack/ui";
22
+
23
+ export function MyPanel() {
24
+ return (
25
+ <Card>
26
+ <CardHeader>
27
+ <CardTitle>My Plugin</CardTitle>
28
+ </CardHeader>
29
+ <CardContent>
30
+ <Button variant="primary">Do the thing</Button>
31
+ </CardContent>
32
+ </Card>
33
+ );
34
+ }
35
+ ```
36
+
37
+ ## Theming
38
+
39
+ All components consume CSS custom properties defined in `themes.css`. The host
40
+ frontend wraps the entire app in `ThemeProvider`, so plugins automatically
41
+ inherit light/dark mode. Use semantic Tailwind classes (`bg-primary`,
42
+ `text-muted-foreground`, …) — never hardcoded colors.
43
+
44
+ ## Accessibility & performance
45
+
46
+ - Components are built on Radix primitives where keyboard / focus / ARIA
47
+ behavior is non-trivial.
48
+ - Animation-heavy components respect the `usePerformance` hook's `isLowPower`
49
+ flag — see [the performance rule](https://github.com/enyineer/checkstack/blob/main/.agent/rules/performance.md)
50
+ for the full contract plugins are expected to follow.
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Input } from "../src/components/Input";
3
+ import { Label } from "../src/components/Label";
4
+
5
+ const meta: Meta<typeof Label> = {
6
+ title: "Components/Inputs/Label",
7
+ component: Label,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof Label>;
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <div className="space-y-2 max-w-sm">
17
+ <Label htmlFor="email">Email</Label>
18
+ <Input id="email" type="email" placeholder="you@example.com" />
19
+ </div>
20
+ ),
21
+ };
@@ -0,0 +1,37 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { LinksEditor, type HotLink } from "../src/components/LinksEditor";
4
+
5
+ const meta: Meta = {
6
+ title: "Components/Inputs/LinksEditor",
7
+ };
8
+
9
+ export default meta;
10
+ type Story = StoryObj;
11
+
12
+ const Demo = ({ canManage }: { canManage: boolean }) => {
13
+ const [links, setLinks] = useState<HotLink[]>([
14
+ { id: "1", label: "Runbook", url: "https://example.com/runbook" },
15
+ { id: "2", label: null, url: "https://example.com/dashboards/api" },
16
+ ]);
17
+ return (
18
+ <div className="max-w-md">
19
+ <LinksEditor
20
+ links={links}
21
+ canManage={canManage}
22
+ onAdd={({ label, url }) => {
23
+ setLinks((prev) => [
24
+ ...prev,
25
+ { id: String(prev.length + 1), label: label ?? null, url },
26
+ ]);
27
+ }}
28
+ onRemove={(link) => {
29
+ setLinks((prev) => prev.filter((l) => l.id !== link.id));
30
+ }}
31
+ />
32
+ </div>
33
+ );
34
+ };
35
+
36
+ export const Editable: Story = { render: () => <Demo canManage /> };
37
+ export const ReadOnly: Story = { render: () => <Demo canManage={false} /> };
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Markdown, MarkdownBlock } from "../src/components/Markdown";
3
+
4
+ const meta: Meta = {
5
+ title: "Components/Display/Markdown",
6
+ };
7
+
8
+ export default meta;
9
+ type Story = StoryObj;
10
+
11
+ export const Inline: Story = {
12
+ render: () => (
13
+ <p>
14
+ <Markdown>
15
+ {"Build **fast**, ship **safe**. See the [docs](https://example.com)."}
16
+ </Markdown>
17
+ </p>
18
+ ),
19
+ };
20
+
21
+ export const Block: Story = {
22
+ render: () => (
23
+ <MarkdownBlock>
24
+ {`# Health checks
25
+
26
+ A *health check* probes a system and asserts an outcome.
27
+
28
+ - HTTP probes
29
+ - TCP probes
30
+ - Custom strategies
31
+
32
+ Read more in the [strategies guide](https://example.com).`}
33
+ </MarkdownBlock>
34
+ ),
35
+ };
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { LogOut, Settings, User } from "lucide-react";
3
+ import {
4
+ DropdownMenuItem,
5
+ DropdownMenuLabel,
6
+ DropdownMenuSeparator,
7
+ } from "../src/components/Menu";
8
+
9
+ const meta: Meta = {
10
+ title: "Components/Overlays/Menu (Dropdown items)",
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj;
15
+
16
+ export const Items: Story = {
17
+ render: () => (
18
+ <div className="w-64 rounded-md border border-border bg-popover p-1 shadow-md">
19
+ <DropdownMenuLabel>Account</DropdownMenuLabel>
20
+ <DropdownMenuItem icon={<User className="h-4 w-4" />}>
21
+ Profile
22
+ </DropdownMenuItem>
23
+ <DropdownMenuItem
24
+ icon={<Settings className="h-4 w-4" />}
25
+ description="Theme, notifications, integrations"
26
+ >
27
+ Settings
28
+ </DropdownMenuItem>
29
+ <DropdownMenuSeparator />
30
+ <DropdownMenuItem icon={<LogOut className="h-4 w-4" />}>
31
+ Sign out
32
+ </DropdownMenuItem>
33
+ </div>
34
+ ),
35
+ };
@@ -0,0 +1,31 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Activity, Cpu, MemoryStick, Network } from "lucide-react";
3
+ import { MetricTile } from "../src/components/MetricTile";
4
+
5
+ const meta: Meta<typeof MetricTile> = {
6
+ title: "Components/Display/MetricTile",
7
+ component: MetricTile,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof MetricTile>;
13
+
14
+ export const Single: Story = {
15
+ args: {
16
+ icon: Activity,
17
+ label: "Throughput",
18
+ value: "1.2k req/s",
19
+ },
20
+ };
21
+
22
+ export const Grid: Story = {
23
+ render: () => (
24
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-3 max-w-3xl">
25
+ <MetricTile icon={Cpu} label="CPU" value="42%" subtitle="across 4 cores" />
26
+ <MetricTile icon={MemoryStick} label="Memory" value="3.1 GB" variant="warning" />
27
+ <MetricTile icon={Network} label="Network" value="220 Mbps" variant="success" />
28
+ <MetricTile icon={Activity} label="Errors" value="14" variant="destructive" />
29
+ </div>
30
+ ),
31
+ };
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Activity, Bell, Cog } from "lucide-react";
3
+ import { NavItem } from "../src/components/NavItem";
4
+
5
+ const meta: Meta<typeof NavItem> = {
6
+ title: "Components/Navigation/NavItem",
7
+ component: NavItem,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof NavItem>;
13
+
14
+ export const TopLevel: Story = {
15
+ args: {
16
+ to: "/health",
17
+ label: "Health",
18
+ icon: <Activity className="h-4 w-4" />,
19
+ },
20
+ };
21
+
22
+ export const Nested: Story = {
23
+ render: () => (
24
+ <NavItem label="Settings" icon={<Cog className="h-4 w-4" />}>
25
+ <NavItem to="/settings/notifications" label="Notifications" icon={<Bell className="h-4 w-4" />} />
26
+ <NavItem to="/settings/general" label="General" icon={<Cog className="h-4 w-4" />} />
27
+ </NavItem>
28
+ ),
29
+ };
@@ -0,0 +1,17 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { NotFound } from "../src/components/NotFound";
3
+
4
+ const meta: Meta<typeof NotFound> = {
5
+ title: "Components/Feedback/NotFound",
6
+ component: NotFound,
7
+ tags: ["autodocs"],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof NotFound>;
12
+
13
+ export const Default: Story = {};
14
+
15
+ export const CustomMessage: Story = {
16
+ args: { message: "We couldn't find the catalog system you requested." },
17
+ };
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Activity } from "lucide-react";
3
+ import { Button } from "../src/components/Button";
4
+ import { Page, PageContent, PageHeader } from "../src/components/Page";
5
+
6
+ const meta: Meta = {
7
+ title: "Components/Layout/Page",
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj;
12
+
13
+ export const Default: Story = {
14
+ render: () => (
15
+ <Page className="h-[60vh]">
16
+ <PageHeader
17
+ title="Health"
18
+ subtitle="Live system signals across all environments."
19
+ icon={Activity}
20
+ actions={<Button>New check</Button>}
21
+ />
22
+ <PageContent>
23
+ <p className="text-sm text-muted-foreground">
24
+ Page content scrolls independently of the header.
25
+ </p>
26
+ </PageContent>
27
+ </Page>
28
+ ),
29
+ };
@@ -0,0 +1,59 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { ShieldCheck } from "lucide-react";
3
+ import { Button } from "../src/components/Button";
4
+ import { PageLayout } from "../src/components/PageLayout";
5
+
6
+ const meta: Meta<typeof PageLayout> = {
7
+ title: "Components/Layout/PageLayout",
8
+ component: PageLayout,
9
+ tags: ["autodocs"],
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof PageLayout>;
14
+
15
+ export const Allowed: Story = {
16
+ render: () => (
17
+ <div className="h-[60vh]">
18
+ <PageLayout
19
+ title="Access rules"
20
+ subtitle="Define who can do what."
21
+ icon={ShieldCheck}
22
+ allowed
23
+ actions={<Button>New rule</Button>}
24
+ >
25
+ <p className="text-sm text-muted-foreground">Rules table…</p>
26
+ </PageLayout>
27
+ </div>
28
+ ),
29
+ };
30
+
31
+ export const Denied: Story = {
32
+ render: () => (
33
+ <div className="h-[60vh]">
34
+ <PageLayout
35
+ title="Access rules"
36
+ subtitle="Define who can do what."
37
+ icon={ShieldCheck}
38
+ allowed={false}
39
+ >
40
+ <p>(Hidden)</p>
41
+ </PageLayout>
42
+ </div>
43
+ ),
44
+ };
45
+
46
+ export const Loading: Story = {
47
+ render: () => (
48
+ <div className="h-[60vh]">
49
+ <PageLayout
50
+ title="Access rules"
51
+ subtitle="Define who can do what."
52
+ icon={ShieldCheck}
53
+ loading
54
+ >
55
+ <p>(Hidden while loading)</p>
56
+ </PageLayout>
57
+ </div>
58
+ ),
59
+ };
@@ -0,0 +1,44 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useEffect, useMemo } from "react";
3
+ import { PaginatedList } from "../src/components/PaginatedList";
4
+ import { usePagination } from "../src/hooks/usePagination";
5
+
6
+ const meta: Meta = {
7
+ title: "Components/Data/PaginatedList",
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj;
12
+
13
+ const ITEMS = Array.from({ length: 73 }, (_, i) => ({
14
+ id: String(i + 1),
15
+ name: `System ${i + 1}`,
16
+ }));
17
+
18
+ const Demo = () => {
19
+ const pagination = usePagination({ defaultLimit: 10 });
20
+ useEffect(() => {
21
+ pagination.setTotal(ITEMS.length);
22
+ }, [pagination]);
23
+ const slice = useMemo(
24
+ () => ITEMS.slice(pagination.offset, pagination.offset + pagination.limit),
25
+ [pagination.offset, pagination.limit],
26
+ );
27
+ return (
28
+ <div className="max-w-md">
29
+ <PaginatedList items={slice} loading={false} pagination={pagination}>
30
+ {(items) => (
31
+ <ul className="divide-y rounded-md border border-border">
32
+ {items.map((item) => (
33
+ <li key={item.id} className="px-3 py-2 text-sm">
34
+ {item.name}
35
+ </li>
36
+ ))}
37
+ </ul>
38
+ )}
39
+ </PaginatedList>
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export const Default: Story = { render: () => <Demo /> };
@@ -0,0 +1,31 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Pagination } from "../src/components/Pagination";
4
+
5
+ const meta: Meta<typeof Pagination> = {
6
+ title: "Components/Navigation/Pagination",
7
+ component: Pagination,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof Pagination>;
13
+
14
+ const Demo = () => {
15
+ const [page, setPage] = useState(1);
16
+ const [limit, setLimit] = useState(20);
17
+ return (
18
+ <Pagination
19
+ page={page}
20
+ totalPages={12}
21
+ onPageChange={setPage}
22
+ total={234}
23
+ limit={limit}
24
+ onPageSizeChange={setLimit}
25
+ showPageSize
26
+ showTotal
27
+ />
28
+ );
29
+ };
30
+
31
+ export const Default: Story = { render: () => <Demo /> };
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "../src/components/Button";
3
+ import { usePerformance } from "../src/components/PerformanceProvider";
4
+
5
+ const meta: Meta = {
6
+ title: "Foundations/PerformanceProvider",
7
+ };
8
+
9
+ export default meta;
10
+ type Story = StoryObj;
11
+
12
+ const Demo = () => {
13
+ const { isLowPower, manualLowPower, toggleManualLowPower } = usePerformance();
14
+ return (
15
+ <div className="space-y-3 max-w-md">
16
+ <p className="text-sm">
17
+ <code>isLowPower</code>: <strong>{String(isLowPower)}</strong>
18
+ </p>
19
+ <p className="text-sm">
20
+ <code>manualLowPower</code>: <strong>{String(manualLowPower)}</strong>
21
+ </p>
22
+ <Button onClick={toggleManualLowPower}>
23
+ Toggle low-power mode
24
+ </Button>
25
+ <p className="text-xs text-muted-foreground">
26
+ Plugins should consume <code>usePerformance().isLowPower</code> to
27
+ disable expensive animations / blurs.
28
+ </p>
29
+ </div>
30
+ );
31
+ };
32
+
33
+ export const Toggle: Story = { render: () => <Demo /> };
@@ -0,0 +1,64 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import {
4
+ PluginConfigForm,
5
+ type PluginOption,
6
+ } from "../src/components/PluginConfigForm";
7
+
8
+ const meta: Meta = {
9
+ title: "Components/Forms/PluginConfigForm",
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj;
14
+
15
+ const plugins: PluginOption[] = [
16
+ {
17
+ id: "http-probe",
18
+ displayName: "HTTP Probe",
19
+ description: "Probes a public HTTP endpoint and asserts a 2xx response.",
20
+ configSchema: {
21
+ type: "object",
22
+ required: ["url"],
23
+ properties: {
24
+ url: { type: "string", title: "URL", format: "uri" },
25
+ timeoutMs: { type: "number", title: "Timeout (ms)", default: 5000 },
26
+ },
27
+ },
28
+ },
29
+ {
30
+ id: "tcp-probe",
31
+ displayName: "TCP Probe",
32
+ description: "Opens a TCP connection to verify the port is reachable.",
33
+ configSchema: {
34
+ type: "object",
35
+ required: ["host", "port"],
36
+ properties: {
37
+ host: { type: "string", title: "Host" },
38
+ port: { type: "number", title: "Port" },
39
+ },
40
+ },
41
+ },
42
+ ];
43
+
44
+ const Demo = () => {
45
+ const [pluginId, setPluginId] = useState("http-probe");
46
+ const [config, setConfig] = useState<Record<string, unknown>>({});
47
+ return (
48
+ <div className="max-w-xl">
49
+ <PluginConfigForm
50
+ label="Strategy"
51
+ plugins={plugins}
52
+ selectedPluginId={pluginId}
53
+ onPluginChange={(id) => {
54
+ setPluginId(id);
55
+ setConfig({});
56
+ }}
57
+ config={config}
58
+ onConfigChange={setConfig}
59
+ />
60
+ </div>
61
+ );
62
+ };
63
+
64
+ export const Default: Story = { render: () => <Demo /> };
@@ -0,0 +1,30 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "../src/components/Button";
3
+ import {
4
+ Popover,
5
+ PopoverContent,
6
+ PopoverTrigger,
7
+ } from "../src/components/Popover";
8
+
9
+ const meta: Meta = {
10
+ title: "Components/Overlays/Popover",
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj;
15
+
16
+ export const Default: Story = {
17
+ render: () => (
18
+ <Popover>
19
+ <PopoverTrigger asChild>
20
+ <Button variant="outline">Open popover</Button>
21
+ </PopoverTrigger>
22
+ <PopoverContent className="w-80 p-4">
23
+ <h4 className="font-medium leading-none mb-2">Filter</h4>
24
+ <p className="text-sm text-muted-foreground">
25
+ Use this for filter chips, info hovers, or quick edits.
26
+ </p>
27
+ </PopoverContent>
28
+ </Popover>
29
+ ),
30
+ };
@@ -0,0 +1,20 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Activity } from "lucide-react";
3
+ import { SectionHeader } from "../src/components/SectionHeader";
4
+
5
+ const meta: Meta<typeof SectionHeader> = {
6
+ title: "Components/Layout/SectionHeader",
7
+ component: SectionHeader,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof SectionHeader>;
13
+
14
+ export const Default: Story = {
15
+ args: {
16
+ title: "Recent activity",
17
+ description: "Last 24 hours of health-check transitions.",
18
+ icon: <Activity className="h-5 w-5" />,
19
+ },
20
+ };
@@ -0,0 +1,36 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from "../src/components/Select";
10
+
11
+ const meta: Meta = {
12
+ title: "Components/Inputs/Select",
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj;
17
+
18
+ const Demo = () => {
19
+ const [value, setValue] = useState("");
20
+ return (
21
+ <div className="max-w-xs">
22
+ <Select value={value} onValueChange={setValue}>
23
+ <SelectTrigger>
24
+ <SelectValue placeholder="Pick an environment" />
25
+ </SelectTrigger>
26
+ <SelectContent>
27
+ <SelectItem value="prod">Production</SelectItem>
28
+ <SelectItem value="staging">Staging</SelectItem>
29
+ <SelectItem value="dev">Development</SelectItem>
30
+ </SelectContent>
31
+ </Select>
32
+ </div>
33
+ );
34
+ };
35
+
36
+ export const Default: Story = { render: () => <Demo /> };