@checkstack/ui 1.8.0 → 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 (68) 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 +8 -0
  7. package/package.json +22 -8
  8. package/postcss.config.js +6 -0
  9. package/stories/AccessDenied.stories.tsx +17 -0
  10. package/stories/AccessGate.stories.tsx +49 -0
  11. package/stories/Accordion.stories.tsx +56 -0
  12. package/stories/Alert.stories.tsx +88 -0
  13. package/stories/AmbientBackground.stories.tsx +28 -0
  14. package/stories/AnimatedCounter.stories.tsx +41 -0
  15. package/stories/AnimatedNumber.stories.tsx +39 -0
  16. package/stories/BackLink.stories.tsx +32 -0
  17. package/stories/Badge.stories.tsx +37 -0
  18. package/stories/Button.stories.tsx +57 -0
  19. package/stories/Card.stories.tsx +39 -0
  20. package/stories/Checkbox.stories.tsx +45 -0
  21. package/stories/CodeEditor.stories.tsx +67 -0
  22. package/stories/ColorPicker.stories.tsx +24 -0
  23. package/stories/CommandPalette.stories.tsx +36 -0
  24. package/stories/ConfirmationModal.stories.tsx +40 -0
  25. package/stories/DateRangeFilter.stories.tsx +30 -0
  26. package/stories/DateTimePicker.stories.tsx +26 -0
  27. package/stories/Dialog.stories.tsx +62 -0
  28. package/stories/DynamicForm.stories.tsx +83 -0
  29. package/stories/DynamicIcon.stories.tsx +52 -0
  30. package/stories/EditableText.stories.tsx +29 -0
  31. package/stories/Feedback.stories.tsx +45 -0
  32. package/stories/IDELayout.stories.tsx +70 -0
  33. package/stories/InfoBanner.stories.tsx +41 -0
  34. package/stories/Input.stories.tsx +29 -0
  35. package/stories/InstanceScopeBanner.stories.tsx +35 -0
  36. package/stories/Introduction.mdx +50 -0
  37. package/stories/Label.stories.tsx +21 -0
  38. package/stories/LinksEditor.stories.tsx +37 -0
  39. package/stories/Markdown.stories.tsx +35 -0
  40. package/stories/Menu.stories.tsx +35 -0
  41. package/stories/MetricTile.stories.tsx +31 -0
  42. package/stories/NavItem.stories.tsx +29 -0
  43. package/stories/NotFound.stories.tsx +17 -0
  44. package/stories/Page.stories.tsx +29 -0
  45. package/stories/PageLayout.stories.tsx +59 -0
  46. package/stories/PaginatedList.stories.tsx +44 -0
  47. package/stories/Pagination.stories.tsx +31 -0
  48. package/stories/PerformanceProvider.stories.tsx +33 -0
  49. package/stories/PluginConfigForm.stories.tsx +64 -0
  50. package/stories/Popover.stories.tsx +30 -0
  51. package/stories/SectionHeader.stories.tsx +20 -0
  52. package/stories/Select.stories.tsx +36 -0
  53. package/stories/Sheet.stories.tsx +59 -0
  54. package/stories/Slider.stories.tsx +24 -0
  55. package/stories/StatusCard.stories.tsx +30 -0
  56. package/stories/StatusUpdateTimeline.stories.tsx +77 -0
  57. package/stories/StrategyConfigCard.stories.tsx +57 -0
  58. package/stories/SubscribeButton.stories.tsx +27 -0
  59. package/stories/Table.stories.tsx +56 -0
  60. package/stories/Tabs.stories.tsx +40 -0
  61. package/stories/TerminalFeed.stories.tsx +47 -0
  62. package/stories/Textarea.stories.tsx +25 -0
  63. package/stories/ThemeProvider.stories.tsx +38 -0
  64. package/stories/Toast.stories.tsx +32 -0
  65. package/stories/Toggle.stories.tsx +43 -0
  66. package/stories/Tooltip.stories.tsx +20 -0
  67. package/stories/UserMenu.stories.tsx +35 -0
  68. package/tailwind.config.js +74 -0
@@ -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
+ };
@@ -0,0 +1,45 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Checkbox } from "../src/components/Checkbox";
4
+ import { Label } from "../src/components/Label";
5
+
6
+ const meta: Meta<typeof Checkbox> = {
7
+ title: "Components/Inputs/Checkbox",
8
+ component: Checkbox,
9
+ tags: ["autodocs"],
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof Checkbox>;
14
+
15
+ const Interactive = () => {
16
+ const [checked, setChecked] = useState(false);
17
+ return (
18
+ <div className="flex items-center gap-2">
19
+ <Checkbox
20
+ id="cb"
21
+ checked={checked}
22
+ onCheckedChange={setChecked}
23
+ />
24
+ <Label htmlFor="cb">I agree</Label>
25
+ </div>
26
+ );
27
+ };
28
+
29
+ export const Default: Story = { render: () => <Interactive /> };
30
+
31
+ export const Disabled: Story = {
32
+ render: () => (
33
+ <div className="flex items-center gap-2">
34
+ <Checkbox
35
+ id="cb-d"
36
+ checked
37
+ disabled
38
+ onChange={() => {
39
+ /* noop */
40
+ }}
41
+ />
42
+ <Label htmlFor="cb-d">Disabled (checked)</Label>
43
+ </div>
44
+ ),
45
+ };
@@ -0,0 +1,67 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { CodeEditor } from "../src/components/CodeEditor";
4
+
5
+ const meta: Meta<typeof CodeEditor> = {
6
+ title: "Components/Inputs/CodeEditor",
7
+ component: CodeEditor,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof CodeEditor>;
13
+
14
+ const Demo = ({
15
+ language,
16
+ initial,
17
+ }: {
18
+ language: "json" | "yaml" | "javascript";
19
+ initial: string;
20
+ }) => {
21
+ const [value, setValue] = useState(initial);
22
+ return (
23
+ <CodeEditor
24
+ value={value}
25
+ onChange={setValue}
26
+ language={language}
27
+ minHeight="280px"
28
+ />
29
+ );
30
+ };
31
+
32
+ export const Json: Story = {
33
+ render: () => (
34
+ <Demo
35
+ language="json"
36
+ initial={`{
37
+ "url": "https://api.example.com/health",
38
+ "timeoutMs": 5000
39
+ }`}
40
+ />
41
+ ),
42
+ };
43
+
44
+ export const Yaml: Story = {
45
+ render: () => (
46
+ <Demo
47
+ language="yaml"
48
+ initial={`url: https://api.example.com/health
49
+ timeoutMs: 5000
50
+ assertions:
51
+ - status: 200`}
52
+ />
53
+ ),
54
+ };
55
+
56
+ export const JavaScript: Story = {
57
+ render: () => (
58
+ <Demo
59
+ language="javascript"
60
+ initial={`// Custom assertion
61
+ export default async function check({ fetch }) {
62
+ const res = await fetch("https://example.com");
63
+ return res.status === 200;
64
+ }`}
65
+ />
66
+ ),
67
+ };
@@ -0,0 +1,24 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { ColorPicker } from "../src/components/ColorPicker";
4
+
5
+ const meta: Meta<typeof ColorPicker> = {
6
+ title: "Components/Inputs/ColorPicker",
7
+ component: ColorPicker,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof ColorPicker>;
13
+
14
+ const Demo = () => {
15
+ const [color, setColor] = useState("#7c3aed");
16
+ return (
17
+ <div className="space-y-3 max-w-sm">
18
+ <ColorPicker value={color} onChange={setColor} />
19
+ <p className="text-sm text-muted-foreground">Selected: <code>{color}</code></p>
20
+ </div>
21
+ );
22
+ };
23
+
24
+ export const Default: Story = { render: () => <Demo /> };
@@ -0,0 +1,36 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { CommandPalette } from "../src/components/CommandPalette";
3
+
4
+ const meta: Meta<typeof CommandPalette> = {
5
+ title: "Components/Navigation/CommandPalette",
6
+ component: CommandPalette,
7
+ tags: ["autodocs"],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof CommandPalette>;
12
+
13
+ export const Default: Story = {
14
+ render: () => (
15
+ <div className="max-w-2xl">
16
+ <CommandPalette
17
+ onClick={() => {
18
+ /* open palette */
19
+ }}
20
+ />
21
+ </div>
22
+ ),
23
+ };
24
+
25
+ export const CustomPlaceholder: Story = {
26
+ render: () => (
27
+ <div className="max-w-2xl">
28
+ <CommandPalette
29
+ placeholder="Find a health check, satellite, or runbook…"
30
+ onClick={() => {
31
+ /* open palette */
32
+ }}
33
+ />
34
+ </div>
35
+ ),
36
+ };
@@ -0,0 +1,40 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Button } from "../src/components/Button";
4
+ import { ConfirmationModal } from "../src/components/ConfirmationModal";
5
+
6
+ const meta: Meta<typeof ConfirmationModal> = {
7
+ title: "Components/Overlays/ConfirmationModal",
8
+ component: ConfirmationModal,
9
+ tags: ["autodocs"],
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof ConfirmationModal>;
14
+
15
+ const Demo = ({ variant }: { variant: "danger" | "warning" | "info" }) => {
16
+ const [open, setOpen] = useState(false);
17
+ return (
18
+ <>
19
+ <Button
20
+ variant={variant === "danger" ? "destructive" : "primary"}
21
+ onClick={() => setOpen(true)}
22
+ >
23
+ Open {variant} modal
24
+ </Button>
25
+ <ConfirmationModal
26
+ isOpen={open}
27
+ onClose={() => setOpen(false)}
28
+ onConfirm={() => setOpen(false)}
29
+ title="Delete satellite?"
30
+ message="This will revoke its token and delete recorded health-check history."
31
+ confirmText="Delete"
32
+ variant={variant}
33
+ />
34
+ </>
35
+ );
36
+ };
37
+
38
+ export const Danger: Story = { render: () => <Demo variant="danger" /> };
39
+ export const Warning: Story = { render: () => <Demo variant="warning" /> };
40
+ export const Info: Story = { render: () => <Demo variant="info" /> };
@@ -0,0 +1,30 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import {
4
+ DateRangeFilter,
5
+ type DateRange,
6
+ getDefaultDateRange,
7
+ } from "../src/components/DateRangeFilter";
8
+
9
+ const meta: Meta<typeof DateRangeFilter> = {
10
+ title: "Components/Inputs/DateRangeFilter",
11
+ component: DateRangeFilter,
12
+ tags: ["autodocs"],
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof DateRangeFilter>;
17
+
18
+ const Demo = () => {
19
+ const [range, setRange] = useState<DateRange>(getDefaultDateRange());
20
+ return (
21
+ <div className="space-y-3">
22
+ <DateRangeFilter value={range} onChange={setRange} />
23
+ <p className="text-xs text-muted-foreground">
24
+ {range.startDate.toISOString()} → {range.endDate.toISOString()}
25
+ </p>
26
+ </div>
27
+ );
28
+ };
29
+
30
+ export const Default: Story = { render: () => <Demo /> };
@@ -0,0 +1,26 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { DateTimePicker } from "../src/components/DateTimePicker";
4
+
5
+ const meta: Meta<typeof DateTimePicker> = {
6
+ title: "Components/Inputs/DateTimePicker",
7
+ component: DateTimePicker,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof DateTimePicker>;
13
+
14
+ const Demo = () => {
15
+ const [value, setValue] = useState<Date | undefined>(new Date());
16
+ return (
17
+ <div className="space-y-3 max-w-md">
18
+ <DateTimePicker value={value} onChange={setValue} />
19
+ <p className="text-xs text-muted-foreground">
20
+ {value ? value.toISOString() : "no date"}
21
+ </p>
22
+ </div>
23
+ );
24
+ };
25
+
26
+ export const Default: Story = { render: () => <Demo /> };
@@ -0,0 +1,62 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "../src/components/Button";
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ DialogTrigger,
11
+ } from "../src/components/Dialog";
12
+
13
+ const meta: Meta = {
14
+ title: "Components/Overlays/Dialog",
15
+ };
16
+
17
+ export default meta;
18
+ type Story = StoryObj;
19
+
20
+ export const Default: Story = {
21
+ render: () => (
22
+ <Dialog>
23
+ <DialogTrigger asChild>
24
+ <Button>Open dialog</Button>
25
+ </DialogTrigger>
26
+ <DialogContent>
27
+ <DialogHeader>
28
+ <DialogTitle>Edit display name</DialogTitle>
29
+ <DialogDescription>
30
+ Renaming a system updates how it appears across all dashboards.
31
+ </DialogDescription>
32
+ </DialogHeader>
33
+ <div className="py-4 text-sm text-muted-foreground">
34
+ Form contents go here.
35
+ </div>
36
+ <DialogFooter>
37
+ <Button variant="ghost">Cancel</Button>
38
+ <Button>Save</Button>
39
+ </DialogFooter>
40
+ </DialogContent>
41
+ </Dialog>
42
+ ),
43
+ };
44
+
45
+ export const LargeSize: Story = {
46
+ render: () => (
47
+ <Dialog>
48
+ <DialogTrigger asChild>
49
+ <Button variant="outline">Open large dialog</Button>
50
+ </DialogTrigger>
51
+ <DialogContent size="lg">
52
+ <DialogHeader>
53
+ <DialogTitle>Large dialog</DialogTitle>
54
+ <DialogDescription>
55
+ Use <code>size=&quot;lg&quot;</code> for forms with many fields.
56
+ </DialogDescription>
57
+ </DialogHeader>
58
+ <div className="py-4 text-sm text-muted-foreground">…</div>
59
+ </DialogContent>
60
+ </Dialog>
61
+ ),
62
+ };
@@ -0,0 +1,83 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import {
4
+ DynamicForm,
5
+ KeyValueEditor,
6
+ type JsonSchema,
7
+ type KeyValuePair,
8
+ } from "../src/components/DynamicForm";
9
+
10
+ const meta: Meta = {
11
+ title: "Components/Forms/DynamicForm",
12
+ };
13
+
14
+ export default meta;
15
+ type Story = StoryObj;
16
+
17
+ const schema: JsonSchema = {
18
+ type: "object",
19
+ required: ["url", "method"],
20
+ properties: {
21
+ url: { type: "string", title: "URL", format: "uri", description: "Endpoint to probe" },
22
+ method: {
23
+ type: "string",
24
+ title: "Method",
25
+ enum: ["GET", "POST", "HEAD"],
26
+ default: "GET",
27
+ },
28
+ timeoutMs: {
29
+ type: "number",
30
+ title: "Timeout (ms)",
31
+ default: 5000,
32
+ minimum: 100,
33
+ maximum: 60_000,
34
+ },
35
+ followRedirects: {
36
+ type: "boolean",
37
+ title: "Follow redirects",
38
+ default: true,
39
+ },
40
+ apiKey: {
41
+ type: "string",
42
+ title: "API key",
43
+ "x-secret": true,
44
+ },
45
+ },
46
+ };
47
+
48
+ const Demo = () => {
49
+ const [value, setValue] = useState<Record<string, unknown>>({});
50
+ const [valid, setValid] = useState(false);
51
+ return (
52
+ <div className="max-w-xl space-y-4">
53
+ <DynamicForm
54
+ schema={schema}
55
+ value={value}
56
+ onChange={setValue}
57
+ onValidChange={setValid}
58
+ />
59
+ <p className="text-xs text-muted-foreground">
60
+ Valid: <strong>{String(valid)}</strong>
61
+ </p>
62
+ <pre className="text-xs bg-muted/50 rounded p-3 overflow-auto">
63
+ {JSON.stringify(value, undefined, 2)}
64
+ </pre>
65
+ </div>
66
+ );
67
+ };
68
+
69
+ export const FromSchema: Story = { render: () => <Demo /> };
70
+
71
+ const KvDemo = () => {
72
+ const [pairs, setPairs] = useState<KeyValuePair[]>([
73
+ { key: "Authorization", value: "Bearer ***" },
74
+ { key: "X-Trace-Id", value: "abc-123" },
75
+ ]);
76
+ return (
77
+ <div className="max-w-xl">
78
+ <KeyValueEditor id="headers" value={pairs} onChange={setPairs} />
79
+ </div>
80
+ );
81
+ };
82
+
83
+ export const KeyValue: Story = { render: () => <KvDemo /> };
@@ -0,0 +1,52 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { DynamicIcon } from "../src/components/DynamicIcon";
3
+
4
+ const meta: Meta<typeof DynamicIcon> = {
5
+ title: "Components/Display/DynamicIcon",
6
+ component: DynamicIcon,
7
+ tags: ["autodocs"],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof DynamicIcon>;
12
+
13
+ export const Named: Story = {
14
+ args: { name: "HeartPulse", className: "h-8 w-8 text-primary" },
15
+ };
16
+
17
+ export const Fallback: Story = {
18
+ render: () => (
19
+ <div className="flex items-center gap-3">
20
+ <DynamicIcon name={undefined} className="h-6 w-6" />
21
+ <span className="text-sm text-muted-foreground">
22
+ Falls back to <code>Settings</code> when no name is given.
23
+ </span>
24
+ </div>
25
+ ),
26
+ };
27
+
28
+ export const Gallery: Story = {
29
+ render: () => (
30
+ <div className="grid grid-cols-6 gap-4">
31
+ {[
32
+ "AlertCircle",
33
+ "Activity",
34
+ "Bell",
35
+ "Cloud",
36
+ "Database",
37
+ "Heart",
38
+ "ShieldCheck",
39
+ "Server",
40
+ "Terminal",
41
+ "Settings",
42
+ "Wrench",
43
+ "Zap",
44
+ ].map((name) => (
45
+ <div key={name} className="flex flex-col items-center gap-2 text-xs">
46
+ <DynamicIcon name={name as never} className="h-6 w-6" />
47
+ <span className="text-muted-foreground">{name}</span>
48
+ </div>
49
+ ))}
50
+ </div>
51
+ ),
52
+ };
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { EditableText } from "../src/components/EditableText";
4
+
5
+ const meta: Meta<typeof EditableText> = {
6
+ title: "Components/Inputs/EditableText",
7
+ component: EditableText,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof EditableText>;
13
+
14
+ const Demo = () => {
15
+ const [value, setValue] = useState("My system");
16
+ return (
17
+ <div className="max-w-sm">
18
+ <EditableText
19
+ value={value}
20
+ onSave={async (next) => {
21
+ await new Promise((r) => setTimeout(r, 300));
22
+ setValue(next);
23
+ }}
24
+ />
25
+ </div>
26
+ );
27
+ };
28
+
29
+ export const Default: Story = { render: () => <Demo /> };
@@ -0,0 +1,45 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Inbox } from "lucide-react";
3
+ import { Button } from "../src/components/Button";
4
+ import { EmptyState } from "../src/components/EmptyState";
5
+ import { HealthBadge } from "../src/components/HealthBadge";
6
+ import { LoadingSpinner } from "../src/components/LoadingSpinner";
7
+
8
+ const meta: Meta = {
9
+ title: "Components/Feedback/Status & Empty",
10
+ };
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj;
15
+
16
+ export const HealthBadges: Story = {
17
+ render: () => (
18
+ <div className="flex flex-wrap gap-3">
19
+ <HealthBadge status="healthy" />
20
+ <HealthBadge status="degraded" />
21
+ <HealthBadge status="unhealthy" />
22
+ <HealthBadge status="healthy" variant="compact" />
23
+ </div>
24
+ ),
25
+ };
26
+
27
+ export const Spinner: Story = {
28
+ render: () => (
29
+ <div className="flex items-center gap-3">
30
+ <LoadingSpinner />
31
+ <span className="text-sm text-muted-foreground">Loading…</span>
32
+ </div>
33
+ ),
34
+ };
35
+
36
+ export const Empty: Story = {
37
+ render: () => (
38
+ <EmptyState
39
+ title="No health checks yet"
40
+ description="Plugins can register checks at runtime. Create one to see it here."
41
+ icon={<Inbox className="h-10 w-10" />}
42
+ actions={<Button>Create check</Button>}
43
+ />
44
+ ),
45
+ };
@@ -0,0 +1,70 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { File, Folder } from "lucide-react";
4
+ import {
5
+ IDELayout,
6
+ IDETreeNode,
7
+ IDETreeSection,
8
+ type ValidationIssue,
9
+ } from "../src/components/IDELayout";
10
+
11
+ const meta: Meta = {
12
+ title: "Components/Layout/IDELayout",
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj;
17
+
18
+ const issues: ValidationIssue[] = [
19
+ { nodeId: "node-2", message: "URL is required" },
20
+ ];
21
+
22
+ const Demo = () => {
23
+ const [selected, setSelected] = useState("node-1");
24
+ return (
25
+ <IDELayout
26
+ issues={issues}
27
+ onIssueClick={setSelected}
28
+ tree={
29
+ <div className="py-2">
30
+ <IDETreeSection label="Check items" />
31
+ <IDETreeNode
32
+ nodeId="node-1"
33
+ label="api.checkstack.io"
34
+ icon={File}
35
+ selected={selected === "node-1"}
36
+ onClick={() => setSelected("node-1")}
37
+ issues={issues}
38
+ />
39
+ <IDETreeNode
40
+ nodeId="node-2"
41
+ label="db.primary"
42
+ icon={File}
43
+ selected={selected === "node-2"}
44
+ onClick={() => setSelected("node-2")}
45
+ issues={issues}
46
+ />
47
+ <IDETreeSection label="Permissions" />
48
+ <IDETreeNode
49
+ nodeId="node-3"
50
+ label="readers"
51
+ icon={Folder}
52
+ selected={selected === "node-3"}
53
+ onClick={() => setSelected("node-3")}
54
+ />
55
+ </div>
56
+ }
57
+ panel={
58
+ <div className="p-6">
59
+ <h3 className="font-semibold">Editing: {selected}</h3>
60
+ <p className="text-sm text-muted-foreground mt-2">
61
+ The right panel renders whatever editor is appropriate for the
62
+ selected tree node.
63
+ </p>
64
+ </div>
65
+ }
66
+ />
67
+ );
68
+ };
69
+
70
+ export const Default: Story = { render: () => <Demo /> };