@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,156 @@
1
+ import { useState } from "react";
2
+ import { ExternalLink, Plus, Trash2 } from "lucide-react";
3
+ import { Button } from "./Button";
4
+ import { Input } from "./Input";
5
+ import { Label } from "./Label";
6
+
7
+ export interface HotLink {
8
+ id: string;
9
+ label: string | null;
10
+ url: string;
11
+ }
12
+
13
+ export interface LinksEditorProps<T extends HotLink> {
14
+ /** Currently attached links. */
15
+ links: T[];
16
+ /** Whether the current user is allowed to add/remove links. */
17
+ canManage?: boolean;
18
+ /** Called when the user submits a new link. Should resolve when persisted. */
19
+ onAdd: (props: { label?: string; url: string }) => Promise<void> | void;
20
+ /** Called when the user removes a link. */
21
+ onRemove: (link: T) => Promise<void> | void;
22
+ /** Whether an add or remove mutation is currently in flight. */
23
+ busy?: boolean;
24
+ /** Heading shown above the list. Defaults to "Hotlinks". */
25
+ title?: string;
26
+ /** Help text under the heading. */
27
+ description?: string;
28
+ }
29
+
30
+ /**
31
+ * Inline editor for a list of free-form URL "hotlinks" (e.g. Jira tickets,
32
+ * dashboards, runbooks). Used by the incident, maintenance, and catalog
33
+ * plugins — the parent owns the data + mutation wiring; this component is
34
+ * pure presentation.
35
+ */
36
+ export function LinksEditor<T extends HotLink>({
37
+ links,
38
+ canManage = true,
39
+ onAdd,
40
+ onRemove,
41
+ busy,
42
+ title = "Hotlinks",
43
+ description,
44
+ }: LinksEditorProps<T>) {
45
+ const [label, setLabel] = useState("");
46
+ const [url, setUrl] = useState("");
47
+ const [error, setError] = useState<string | undefined>();
48
+
49
+ const handleAdd = async () => {
50
+ const trimmedUrl = url.trim();
51
+ if (!trimmedUrl) {
52
+ setError("URL is required");
53
+ return;
54
+ }
55
+ try {
56
+ // Triggers a synchronous URL parse error early so users get a clear
57
+ // message instead of waiting for the server validation roundtrip.
58
+ new URL(trimmedUrl);
59
+ } catch {
60
+ setError("Must be a valid URL (include http:// or https://)");
61
+ return;
62
+ }
63
+ setError(undefined);
64
+ await onAdd({ label: label.trim() || undefined, url: trimmedUrl });
65
+ setLabel("");
66
+ setUrl("");
67
+ };
68
+
69
+ return (
70
+ <div className="space-y-3">
71
+ <div>
72
+ <Label>{title}</Label>
73
+ {description && (
74
+ <p className="text-xs text-muted-foreground mt-1">{description}</p>
75
+ )}
76
+ </div>
77
+
78
+ {links.length > 0 ? (
79
+ <div className="border rounded-lg divide-y">
80
+ {links.map((link) => (
81
+ <div
82
+ key={link.id}
83
+ className="flex items-center justify-between p-3 gap-2"
84
+ >
85
+ <div className="flex items-center gap-2 min-w-0 flex-1">
86
+ <ExternalLink className="h-4 w-4 text-muted-foreground shrink-0" />
87
+ <div className="min-w-0">
88
+ <a
89
+ href={link.url}
90
+ target="_blank"
91
+ rel="noopener noreferrer"
92
+ className="text-sm text-primary hover:underline truncate block"
93
+ >
94
+ {link.label ?? link.url}
95
+ </a>
96
+ {link.label && (
97
+ <span className="text-xs text-muted-foreground truncate block">
98
+ {link.url}
99
+ </span>
100
+ )}
101
+ </div>
102
+ </div>
103
+ {canManage && (
104
+ <Button
105
+ variant="ghost"
106
+ size="sm"
107
+ onClick={() => void onRemove(link)}
108
+ disabled={busy}
109
+ aria-label="Remove link"
110
+ >
111
+ <Trash2 className="h-4 w-4" />
112
+ </Button>
113
+ )}
114
+ </div>
115
+ ))}
116
+ </div>
117
+ ) : (
118
+ <p className="text-sm text-muted-foreground">No links attached</p>
119
+ )}
120
+
121
+ {canManage && (
122
+ <div className="border rounded-lg p-4 space-y-3">
123
+ <div className="grid sm:grid-cols-[1fr_2fr] gap-3">
124
+ <div className="space-y-1">
125
+ <Label htmlFor="hotlink-label">Label (optional)</Label>
126
+ <Input
127
+ id="hotlink-label"
128
+ placeholder="e.g. Jira ticket"
129
+ value={label}
130
+ onChange={(e) => setLabel(e.target.value)}
131
+ />
132
+ </div>
133
+ <div className="space-y-1">
134
+ <Label htmlFor="hotlink-url">URL</Label>
135
+ <Input
136
+ id="hotlink-url"
137
+ placeholder="https://example.com/..."
138
+ value={url}
139
+ onChange={(e) => setUrl(e.target.value)}
140
+ />
141
+ </div>
142
+ </div>
143
+ {error && <p className="text-xs text-destructive">{error}</p>}
144
+ <Button
145
+ onClick={() => void handleAdd()}
146
+ disabled={!url.trim() || busy}
147
+ size="sm"
148
+ >
149
+ <Plus className="h-4 w-4 mr-1" />
150
+ Add Link
151
+ </Button>
152
+ </div>
153
+ )}
154
+ </div>
155
+ );
156
+ }
@@ -94,7 +94,7 @@ export const UserMenu: React.FC<UserMenuProps> = ({
94
94
  <SheetHeader className="px-4 py-3 border-b border-border">
95
95
  <SheetTitle className="text-base">Menu</SheetTitle>
96
96
  </SheetHeader>
97
- <div className="flex-1 overflow-y-auto p-2 grid grid-cols-1 gap-2">
97
+ <div className="flex-1 min-h-0 overflow-y-auto p-2 flex flex-col gap-1 [&>*]:shrink-0">
98
98
  {userInfo}
99
99
  <DropdownMenuSeparator />
100
100
  {children}
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ export * from "./components/Toggle";
25
25
  export * from "./components/Checkbox";
26
26
  export * from "./components/Alert";
27
27
  export * from "./components/InfoBanner";
28
+ export * from "./components/InstanceScopeBanner";
28
29
  export * from "./components/DynamicForm";
29
30
  export * from "./components/PageLayout";
30
31
  export * from "./components/PluginConfigForm";
@@ -43,6 +44,7 @@ export * from "./components/DateTimePicker";
43
44
  export * from "./components/DateRangeFilter";
44
45
  export * from "./components/BackLink";
45
46
  export * from "./components/StatusUpdateTimeline";
47
+ export * from "./components/LinksEditor";
46
48
  export * from "./components/Slider";
47
49
  export * from "./components/DynamicIcon";
48
50
  export * from "./components/StrategyConfigCard";
@@ -0,0 +1,17 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AccessDenied } from "../src/components/AccessDenied";
3
+
4
+ const meta: Meta<typeof AccessDenied> = {
5
+ title: "Components/Feedback/AccessDenied",
6
+ component: AccessDenied,
7
+ tags: ["autodocs"],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof AccessDenied>;
12
+
13
+ export const Default: Story = {};
14
+
15
+ export const CustomMessage: Story = {
16
+ args: { message: "You need 'catalog.manage' to view this page." },
17
+ };
@@ -0,0 +1,49 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AccessGate } from "../src/components/AccessGate";
3
+
4
+ const allowed = () => ({ loading: false, allowed: true });
5
+ const denied = () => ({ loading: false, allowed: false });
6
+ const loading = () => ({ loading: true, allowed: false });
7
+
8
+ const meta: Meta<typeof AccessGate> = {
9
+ title: "Components/Feedback/AccessGate",
10
+ component: AccessGate,
11
+ tags: ["autodocs"],
12
+ };
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof AccessGate>;
16
+
17
+ export const Allowed: Story = {
18
+ args: {
19
+ accessRuleId: "demo.read",
20
+ useAccess: allowed,
21
+ children: <p className="text-sm">Gated content visible.</p>,
22
+ },
23
+ };
24
+
25
+ export const Hidden: Story = {
26
+ args: {
27
+ accessRuleId: "demo.read",
28
+ useAccess: denied,
29
+ children: <p className="text-sm">You should not see this.</p>,
30
+ },
31
+ };
32
+
33
+ export const ShowDenied: Story = {
34
+ args: {
35
+ accessRuleId: "demo.read",
36
+ useAccess: denied,
37
+ showDenied: true,
38
+ deniedMessage: "Demo access denied.",
39
+ children: <p>Hidden</p>,
40
+ },
41
+ };
42
+
43
+ export const Loading: Story = {
44
+ args: {
45
+ accessRuleId: "demo.read",
46
+ useAccess: loading,
47
+ children: <p>Resolves once access loads.</p>,
48
+ },
49
+ };
@@ -0,0 +1,56 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import {
3
+ Accordion,
4
+ AccordionContent,
5
+ AccordionItem,
6
+ AccordionTrigger,
7
+ } from "../src/components/Accordion";
8
+
9
+ const meta: Meta<typeof Accordion> = {
10
+ title: "Components/Display/Accordion",
11
+ component: Accordion,
12
+ tags: ["autodocs"],
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof Accordion>;
17
+
18
+ export const Single: Story = {
19
+ render: () => (
20
+ <Accordion type="single" collapsible className="max-w-md">
21
+ <AccordionItem value="a">
22
+ <AccordionTrigger>What is a health check?</AccordionTrigger>
23
+ <AccordionContent>
24
+ A periodic probe that asserts a system property and reports a status.
25
+ </AccordionContent>
26
+ </AccordionItem>
27
+ <AccordionItem value="b">
28
+ <AccordionTrigger>What is a strategy?</AccordionTrigger>
29
+ <AccordionContent>
30
+ A pluggable provider that defines how a check is executed.
31
+ </AccordionContent>
32
+ </AccordionItem>
33
+ <AccordionItem value="c">
34
+ <AccordionTrigger>What is a satellite?</AccordionTrigger>
35
+ <AccordionContent>
36
+ A standalone agent that runs checks from a remote network location.
37
+ </AccordionContent>
38
+ </AccordionItem>
39
+ </Accordion>
40
+ ),
41
+ };
42
+
43
+ export const Multiple: Story = {
44
+ render: () => (
45
+ <Accordion type="multiple" className="max-w-md">
46
+ <AccordionItem value="a">
47
+ <AccordionTrigger>Section one</AccordionTrigger>
48
+ <AccordionContent>Content one.</AccordionContent>
49
+ </AccordionItem>
50
+ <AccordionItem value="b">
51
+ <AccordionTrigger>Section two</AccordionTrigger>
52
+ <AccordionContent>Content two.</AccordionContent>
53
+ </AccordionItem>
54
+ </Accordion>
55
+ ),
56
+ };
@@ -0,0 +1,88 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { CircleAlert, CircleCheck, Info, TriangleAlert } from "lucide-react";
3
+ import {
4
+ Alert,
5
+ AlertContent,
6
+ AlertDescription,
7
+ AlertIcon,
8
+ AlertTitle,
9
+ } from "../src/components/Alert";
10
+
11
+ const meta: Meta<typeof Alert> = {
12
+ title: "Components/Feedback/Alert",
13
+ component: Alert,
14
+ tags: ["autodocs"],
15
+ argTypes: {
16
+ variant: {
17
+ control: "select",
18
+ options: ["default", "success", "warning", "error", "info"],
19
+ },
20
+ },
21
+ };
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof Alert>;
25
+
26
+ export const Default: Story = {
27
+ args: { variant: "default" },
28
+ render: (args) => (
29
+ <Alert {...args}>
30
+ <AlertIcon>
31
+ <Info className="h-4 w-4" />
32
+ </AlertIcon>
33
+ <AlertContent>
34
+ <AlertTitle>Heads up</AlertTitle>
35
+ <AlertDescription>
36
+ This is an informational alert for plugin developers.
37
+ </AlertDescription>
38
+ </AlertContent>
39
+ </Alert>
40
+ ),
41
+ };
42
+
43
+ export const Success: Story = {
44
+ args: { variant: "success" },
45
+ render: (args) => (
46
+ <Alert {...args}>
47
+ <AlertIcon>
48
+ <CircleCheck className="h-4 w-4" />
49
+ </AlertIcon>
50
+ <AlertContent>
51
+ <AlertTitle>Saved</AlertTitle>
52
+ <AlertDescription>Plugin configuration was saved.</AlertDescription>
53
+ </AlertContent>
54
+ </Alert>
55
+ ),
56
+ };
57
+
58
+ export const Warning: Story = {
59
+ args: { variant: "warning" },
60
+ render: (args) => (
61
+ <Alert {...args}>
62
+ <AlertIcon>
63
+ <TriangleAlert className="h-4 w-4" />
64
+ </AlertIcon>
65
+ <AlertContent>
66
+ <AlertTitle>Heads up</AlertTitle>
67
+ <AlertDescription>
68
+ The satellite token will expire in 24 hours.
69
+ </AlertDescription>
70
+ </AlertContent>
71
+ </Alert>
72
+ ),
73
+ };
74
+
75
+ export const Error: Story = {
76
+ args: { variant: "error" },
77
+ render: (args) => (
78
+ <Alert {...args}>
79
+ <AlertIcon>
80
+ <CircleAlert className="h-4 w-4" />
81
+ </AlertIcon>
82
+ <AlertContent>
83
+ <AlertTitle>Something went wrong</AlertTitle>
84
+ <AlertDescription>The health check failed to start.</AlertDescription>
85
+ </AlertContent>
86
+ </Alert>
87
+ ),
88
+ };
@@ -0,0 +1,28 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AmbientBackground } from "../src/components/AmbientBackground";
3
+
4
+ const meta: Meta<typeof AmbientBackground> = {
5
+ title: "Components/Layout/AmbientBackground",
6
+ component: AmbientBackground,
7
+ tags: ["autodocs"],
8
+ parameters: {
9
+ layout: "fullscreen",
10
+ },
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof AmbientBackground>;
15
+
16
+ export const Default: Story = {
17
+ render: () => (
18
+ <AmbientBackground>
19
+ <div className="max-w-3xl mx-auto py-24 px-6">
20
+ <h1 className="text-4xl font-bold tracking-tight">Inverse glow grid</h1>
21
+ <p className="mt-4 text-muted-foreground">
22
+ Aurora blobs and a grid mask, with automatic graceful degradation when{" "}
23
+ <code>usePerformance().isLowPower</code> is true.
24
+ </p>
25
+ </div>
26
+ </AmbientBackground>
27
+ ),
28
+ };
@@ -0,0 +1,41 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useEffect, useState } from "react";
3
+ import { AnimatedCounter } from "../src/components/AnimatedCounter";
4
+
5
+ const meta: Meta<typeof AnimatedCounter> = {
6
+ title: "Components/Display/AnimatedCounter",
7
+ component: AnimatedCounter,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof AnimatedCounter>;
13
+
14
+ const Demo = () => {
15
+ const [value, setValue] = useState(0);
16
+ useEffect(() => {
17
+ const id = setInterval(
18
+ () => setValue((v) => (v >= 1000 ? 0 : v + Math.floor(Math.random() * 200))),
19
+ 1500,
20
+ );
21
+ return () => clearInterval(id);
22
+ }, []);
23
+ return (
24
+ <div className="text-4xl font-bold">
25
+ <AnimatedCounter value={value} />
26
+ </div>
27
+ );
28
+ };
29
+
30
+ export const TickingUp: Story = {
31
+ render: () => <Demo />,
32
+ };
33
+
34
+ export const StaticValue: Story = {
35
+ args: { value: 1042 },
36
+ render: (args) => (
37
+ <span className="text-3xl font-bold">
38
+ <AnimatedCounter {...args} />
39
+ </span>
40
+ ),
41
+ };
@@ -0,0 +1,39 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useEffect, useState } from "react";
3
+ import { AnimatedNumber } from "../src/components/AnimatedNumber";
4
+
5
+ const meta: Meta<typeof AnimatedNumber> = {
6
+ title: "Components/Display/AnimatedNumber",
7
+ component: AnimatedNumber,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof AnimatedNumber>;
13
+
14
+ const Demo = () => {
15
+ const [value, setValue] = useState(99.95);
16
+ useEffect(() => {
17
+ const id = setInterval(
18
+ () => setValue(95 + Math.random() * 5),
19
+ 1500,
20
+ );
21
+ return () => clearInterval(id);
22
+ }, []);
23
+ return (
24
+ <div className="text-3xl font-bold text-success tabular-nums">
25
+ <AnimatedNumber value={value} suffix="%" decimals={2} />
26
+ </div>
27
+ );
28
+ };
29
+
30
+ export const Uptime: Story = { render: () => <Demo /> };
31
+
32
+ export const NA: Story = {
33
+ args: { value: undefined },
34
+ render: (args) => (
35
+ <span className="text-2xl font-bold">
36
+ <AnimatedNumber {...args} />
37
+ </span>
38
+ ),
39
+ };
@@ -0,0 +1,32 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { BackLink } from "../src/components/BackLink";
3
+
4
+ const meta: Meta<typeof BackLink> = {
5
+ title: "Components/Navigation/BackLink",
6
+ component: BackLink,
7
+ tags: ["autodocs"],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof BackLink>;
12
+
13
+ export const Default: Story = {
14
+ args: {
15
+ onClick: () => {
16
+ /* navigate back */
17
+ },
18
+ },
19
+ };
20
+
21
+ export const CustomLabel: Story = {
22
+ args: {
23
+ children: "Back to systems",
24
+ onClick: () => {
25
+ /* navigate back */
26
+ },
27
+ },
28
+ };
29
+
30
+ export const AsLink: Story = {
31
+ args: { to: "/catalog" },
32
+ };
@@ -0,0 +1,37 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Badge } from "../src/components/Badge";
3
+
4
+ const meta: Meta<typeof Badge> = {
5
+ title: "Components/Display/Badge",
6
+ component: Badge,
7
+ tags: ["autodocs"],
8
+ argTypes: {
9
+ variant: {
10
+ control: "select",
11
+ options: ["default", "secondary", "destructive", "outline", "success", "warning", "info"],
12
+ },
13
+ },
14
+ args: {
15
+ children: "Badge",
16
+ variant: "default",
17
+ },
18
+ };
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof Badge>;
22
+
23
+ export const Default: Story = {};
24
+
25
+ export const Variants: Story = {
26
+ render: () => (
27
+ <div className="flex flex-wrap gap-2">
28
+ <Badge variant="default">Default</Badge>
29
+ <Badge variant="secondary">Secondary</Badge>
30
+ <Badge variant="destructive">Destructive</Badge>
31
+ <Badge variant="outline">Outline</Badge>
32
+ <Badge variant="success">Success</Badge>
33
+ <Badge variant="warning">Warning</Badge>
34
+ <Badge variant="info">Info</Badge>
35
+ </div>
36
+ ),
37
+ };
@@ -0,0 +1,57 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "../src/components/Button";
3
+
4
+ const meta: Meta<typeof Button> = {
5
+ title: "Components/Inputs/Button",
6
+ component: Button,
7
+ tags: ["autodocs"],
8
+ argTypes: {
9
+ variant: {
10
+ control: "select",
11
+ options: ["primary", "secondary", "outline", "ghost", "destructive", "link"],
12
+ },
13
+ size: {
14
+ control: "select",
15
+ options: ["default", "sm", "lg", "icon"],
16
+ },
17
+ disabled: { control: "boolean" },
18
+ },
19
+ args: {
20
+ children: "Click me",
21
+ variant: "primary",
22
+ size: "default",
23
+ },
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof Button>;
28
+
29
+ export const Primary: Story = {};
30
+
31
+ export const Secondary: Story = { args: { variant: "secondary" } };
32
+
33
+ export const Outline: Story = { args: { variant: "outline" } };
34
+
35
+ export const Ghost: Story = { args: { variant: "ghost" } };
36
+
37
+ export const Destructive: Story = { args: { variant: "destructive" } };
38
+
39
+ export const Link: Story = { args: { variant: "link" } };
40
+
41
+ export const AllSizes: Story = {
42
+ render: (args) => (
43
+ <div className="flex items-center gap-3">
44
+ <Button {...args} size="sm">
45
+ Small
46
+ </Button>
47
+ <Button {...args} size="default">
48
+ Default
49
+ </Button>
50
+ <Button {...args} size="lg">
51
+ Large
52
+ </Button>
53
+ </div>
54
+ ),
55
+ };
56
+
57
+ export const Disabled: Story = { args: { disabled: true } };
@@ -0,0 +1,39 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "../src/components/Button";
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardFooter,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "../src/components/Card";
11
+
12
+ const meta: Meta<typeof Card> = {
13
+ title: "Components/Display/Card",
14
+ component: Card,
15
+ tags: ["autodocs"],
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof Card>;
20
+
21
+ export const Basic: Story = {
22
+ render: () => (
23
+ <Card className="max-w-md">
24
+ <CardHeader>
25
+ <CardTitle>HTTP Health Check</CardTitle>
26
+ <CardDescription>Probe a public endpoint every 60 seconds.</CardDescription>
27
+ </CardHeader>
28
+ <CardContent>
29
+ <p className="text-sm text-muted-foreground">
30
+ A card is the standard container for plugin panels.
31
+ </p>
32
+ </CardContent>
33
+ <CardFooter className="justify-end gap-2">
34
+ <Button variant="ghost">Cancel</Button>
35
+ <Button>Save</Button>
36
+ </CardFooter>
37
+ </Card>
38
+ ),
39
+ };