@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.
- package/.storybook/main.ts +22 -0
- package/.storybook/manager-head.html +2 -0
- package/.storybook/mock-access-api.ts +5 -0
- package/.storybook/preview.css +5 -0
- package/.storybook/preview.tsx +73 -0
- package/CHANGELOG.md +8 -0
- package/package.json +22 -8
- package/postcss.config.js +6 -0
- package/stories/AccessDenied.stories.tsx +17 -0
- package/stories/AccessGate.stories.tsx +49 -0
- package/stories/Accordion.stories.tsx +56 -0
- package/stories/Alert.stories.tsx +88 -0
- package/stories/AmbientBackground.stories.tsx +28 -0
- package/stories/AnimatedCounter.stories.tsx +41 -0
- package/stories/AnimatedNumber.stories.tsx +39 -0
- package/stories/BackLink.stories.tsx +32 -0
- package/stories/Badge.stories.tsx +37 -0
- package/stories/Button.stories.tsx +57 -0
- package/stories/Card.stories.tsx +39 -0
- package/stories/Checkbox.stories.tsx +45 -0
- package/stories/CodeEditor.stories.tsx +67 -0
- package/stories/ColorPicker.stories.tsx +24 -0
- package/stories/CommandPalette.stories.tsx +36 -0
- package/stories/ConfirmationModal.stories.tsx +40 -0
- package/stories/DateRangeFilter.stories.tsx +30 -0
- package/stories/DateTimePicker.stories.tsx +26 -0
- package/stories/Dialog.stories.tsx +62 -0
- package/stories/DynamicForm.stories.tsx +83 -0
- package/stories/DynamicIcon.stories.tsx +52 -0
- package/stories/EditableText.stories.tsx +29 -0
- package/stories/Feedback.stories.tsx +45 -0
- package/stories/IDELayout.stories.tsx +70 -0
- package/stories/InfoBanner.stories.tsx +41 -0
- package/stories/Input.stories.tsx +29 -0
- package/stories/InstanceScopeBanner.stories.tsx +35 -0
- package/stories/Introduction.mdx +50 -0
- package/stories/Label.stories.tsx +21 -0
- package/stories/LinksEditor.stories.tsx +37 -0
- package/stories/Markdown.stories.tsx +35 -0
- package/stories/Menu.stories.tsx +35 -0
- package/stories/MetricTile.stories.tsx +31 -0
- package/stories/NavItem.stories.tsx +29 -0
- package/stories/NotFound.stories.tsx +17 -0
- package/stories/Page.stories.tsx +29 -0
- package/stories/PageLayout.stories.tsx +59 -0
- package/stories/PaginatedList.stories.tsx +44 -0
- package/stories/Pagination.stories.tsx +31 -0
- package/stories/PerformanceProvider.stories.tsx +33 -0
- package/stories/PluginConfigForm.stories.tsx +64 -0
- package/stories/Popover.stories.tsx +30 -0
- package/stories/SectionHeader.stories.tsx +20 -0
- package/stories/Select.stories.tsx +36 -0
- package/stories/Sheet.stories.tsx +59 -0
- package/stories/Slider.stories.tsx +24 -0
- package/stories/StatusCard.stories.tsx +30 -0
- package/stories/StatusUpdateTimeline.stories.tsx +77 -0
- package/stories/StrategyConfigCard.stories.tsx +57 -0
- package/stories/SubscribeButton.stories.tsx +27 -0
- package/stories/Table.stories.tsx +56 -0
- package/stories/Tabs.stories.tsx +40 -0
- package/stories/TerminalFeed.stories.tsx +47 -0
- package/stories/Textarea.stories.tsx +25 -0
- package/stories/ThemeProvider.stories.tsx +38 -0
- package/stories/Toast.stories.tsx +32 -0
- package/stories/Toggle.stories.tsx +43 -0
- package/stories/Tooltip.stories.tsx +20 -0
- package/stories/UserMenu.stories.tsx +35 -0
- 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="lg"</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 /> };
|