@checkstack/ui 1.11.0 → 1.13.0
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 +43 -0
- package/CHANGELOG.md +326 -0
- package/package.json +23 -18
- package/scripts/generate-stdlib-types.ts +23 -0
- package/src/components/Accordion.tsx +17 -9
- package/src/components/ActionCard.tsx +99 -11
- package/src/components/BrandIcon.tsx +57 -0
- package/src/components/CodeEditor/CodeEditor.tsx +159 -14
- package/src/components/CodeEditor/TypefoxEditor.tsx +537 -168
- package/src/components/CodeEditor/editorTheme.test.ts +41 -0
- package/src/components/CodeEditor/editorTheme.ts +26 -0
- package/src/components/CodeEditor/generated/builtin-modules.json +1 -0
- package/src/components/CodeEditor/importSpecifiers.test.ts +286 -0
- package/src/components/CodeEditor/importSpecifiers.ts +267 -0
- package/src/components/CodeEditor/index.ts +26 -0
- package/src/components/CodeEditor/monacoGuard.ts +76 -0
- package/src/components/CodeEditor/monacoTsService.ts +185 -0
- package/src/components/CodeEditor/popoutTitle.test.ts +37 -0
- package/src/components/CodeEditor/popoutTitle.ts +31 -0
- package/src/components/CodeEditor/scriptContext.test.ts +15 -7
- package/src/components/CodeEditor/scriptContext.ts +12 -18
- package/src/components/CodeEditor/scriptDiagnostics.test.ts +135 -0
- package/src/components/CodeEditor/scriptDiagnostics.ts +172 -0
- package/src/components/CodeEditor/types.ts +79 -0
- package/src/components/CodeEditor/validateScripts.ts +172 -0
- package/src/components/CodeEditor/vscodeServicesSignal.ts +72 -0
- package/src/components/ConfirmationModal.tsx +7 -1
- package/src/components/Dialog.tsx +32 -11
- package/src/components/DurationInput.tsx +121 -0
- package/src/components/DynamicForm/DynamicForm.tsx +119 -47
- package/src/components/DynamicForm/DynamicOptionsField.tsx +19 -14
- package/src/components/DynamicForm/FormField.tsx +183 -15
- package/src/components/DynamicForm/MultiTypeEditorField.tsx +78 -2
- package/src/components/DynamicForm/SecretEnvEditor.tsx +315 -0
- package/src/components/DynamicForm/index.ts +20 -0
- package/src/components/DynamicForm/secretEnv.logic.test.ts +126 -0
- package/src/components/DynamicForm/secretEnv.logic.ts +87 -0
- package/src/components/DynamicForm/types.ts +134 -1
- package/src/components/DynamicForm/utils.test.ts +38 -0
- package/src/components/DynamicForm/utils.ts +54 -0
- package/src/components/DynamicForm/validation.logic.test.ts +255 -0
- package/src/components/DynamicForm/validation.logic.ts +210 -0
- package/src/components/DynamicIcon.tsx +39 -17
- package/src/components/Markdown.tsx +68 -2
- package/src/components/Popover.tsx +6 -1
- package/src/components/ScriptTestPanel.logic.test.ts +139 -0
- package/src/components/ScriptTestPanel.logic.ts +137 -0
- package/src/components/ScriptTestPanel.tsx +394 -0
- package/src/components/Sheet.tsx +21 -6
- package/src/components/Spinner.tsx +56 -0
- package/src/components/StatusBadge.tsx +78 -0
- package/src/components/StrategyConfigCard.tsx +3 -3
- package/src/components/Tabs.tsx +7 -1
- package/src/components/TimeOfDayInput.tsx +116 -0
- package/src/components/UserMenu.logic.test.ts +37 -0
- package/src/components/UserMenu.logic.ts +30 -0
- package/src/components/UserMenu.tsx +40 -12
- package/src/components/comboboxInteraction.ts +39 -0
- package/src/components/iconRegistry.tsx +27 -0
- package/src/components/portalContainer.ts +24 -0
- package/src/index.ts +7 -0
- package/stories/ActionCard.stories.tsx +60 -0
- package/stories/CodeEditor.stories.tsx +47 -2
- package/stories/DurationInput.stories.tsx +59 -0
- package/stories/Introduction.mdx +1 -1
- package/stories/Markdown.stories.tsx +56 -0
- package/stories/ScriptTestPanel.stories.tsx +106 -0
- package/stories/SecretEnvEditor.stories.tsx +80 -0
- package/stories/Spinner.stories.tsx +90 -0
- package/stories/TimeOfDayInput.stories.tsx +34 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import {
|
|
4
|
+
ScriptTestPanel,
|
|
5
|
+
ContextSampleEditor,
|
|
6
|
+
type ScriptTestPanelResult,
|
|
7
|
+
} from "../src/components/ScriptTestPanel";
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof ScriptTestPanel> = {
|
|
10
|
+
title: "Components/Editors/ScriptTestPanel",
|
|
11
|
+
component: ScriptTestPanel,
|
|
12
|
+
tags: ["autodocs"],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof ScriptTestPanel>;
|
|
17
|
+
|
|
18
|
+
function delay<T>(value: T, ms = 400): Promise<T> {
|
|
19
|
+
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const successResult: ScriptTestPanelResult = {
|
|
23
|
+
result: { id: "INC-42", ok: true },
|
|
24
|
+
stdout: "Resolved incident INC-42\nDone.",
|
|
25
|
+
stderr: "",
|
|
26
|
+
durationMs: 128,
|
|
27
|
+
timedOut: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const shellSuccessResult: ScriptTestPanelResult = {
|
|
31
|
+
stdout: "INC-42",
|
|
32
|
+
stderr: "",
|
|
33
|
+
exitCode: 0,
|
|
34
|
+
durationMs: 42,
|
|
35
|
+
timedOut: false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const failureResult: ScriptTestPanelResult = {
|
|
39
|
+
stdout: "",
|
|
40
|
+
stderr: "TypeError: Cannot read properties of undefined (reading 'id')",
|
|
41
|
+
durationMs: 31,
|
|
42
|
+
timedOut: false,
|
|
43
|
+
error: "Cannot read properties of undefined (reading 'id')",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const CollapsedByDefault: Story = {
|
|
47
|
+
render: () => (
|
|
48
|
+
<div className="max-w-xl">
|
|
49
|
+
<ScriptTestPanel onRun={() => delay(successResult)} />
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const TypeScriptSuccess: Story = {
|
|
55
|
+
render: () => (
|
|
56
|
+
<div className="max-w-xl">
|
|
57
|
+
<ScriptTestPanel onRun={() => delay(successResult)} defaultOpen />
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const ShellSuccess: Story = {
|
|
63
|
+
render: () => (
|
|
64
|
+
<div className="max-w-xl">
|
|
65
|
+
<ScriptTestPanel onRun={() => delay(shellSuccessResult)} defaultOpen />
|
|
66
|
+
</div>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Failure: Story = {
|
|
71
|
+
render: () => (
|
|
72
|
+
<div className="max-w-xl">
|
|
73
|
+
<ScriptTestPanel onRun={() => delay(failureResult)} defaultOpen />
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const WithSampleContextEditor: Story = {
|
|
79
|
+
render: () => {
|
|
80
|
+
const Wrapper: React.FC = () => {
|
|
81
|
+
const [context, setContext] = React.useState(
|
|
82
|
+
'{\n "trigger": {\n "event": "incident.created",\n "payload": { "id": "INC-42" }\n }\n}',
|
|
83
|
+
);
|
|
84
|
+
return (
|
|
85
|
+
<div className="max-w-xl">
|
|
86
|
+
<ScriptTestPanel
|
|
87
|
+
onRun={() => delay(successResult)}
|
|
88
|
+
defaultOpen
|
|
89
|
+
contextEditor={
|
|
90
|
+
<ContextSampleEditor value={context} onChange={setContext} />
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
return <Wrapper />;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const Disabled: Story = {
|
|
101
|
+
render: () => (
|
|
102
|
+
<div className="max-w-xl">
|
|
103
|
+
<ScriptTestPanel onRun={() => delay(successResult)} defaultOpen disabled />
|
|
104
|
+
</div>
|
|
105
|
+
),
|
|
106
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { SecretEnvEditor } from "../src/components/DynamicForm/SecretEnvEditor";
|
|
4
|
+
import { Label } from "../src/components/Label";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof SecretEnvEditor> = {
|
|
7
|
+
title: "Components/Inputs/SecretEnvEditor",
|
|
8
|
+
component: SecretEnvEditor,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof SecretEnvEditor>;
|
|
14
|
+
|
|
15
|
+
const Demo = ({
|
|
16
|
+
initial,
|
|
17
|
+
secretNames,
|
|
18
|
+
}: {
|
|
19
|
+
initial?: Record<string, string>;
|
|
20
|
+
secretNames?: string[];
|
|
21
|
+
}) => {
|
|
22
|
+
const [value, setValue] = useState<Record<string, string>>(initial ?? {});
|
|
23
|
+
return (
|
|
24
|
+
<div className="max-w-xl space-y-2">
|
|
25
|
+
<Label>Secret environment variables</Label>
|
|
26
|
+
<SecretEnvEditor
|
|
27
|
+
id="demo-secret-env"
|
|
28
|
+
value={value}
|
|
29
|
+
onChange={setValue}
|
|
30
|
+
secretNames={secretNames}
|
|
31
|
+
/>
|
|
32
|
+
<pre className="text-xs text-muted-foreground">
|
|
33
|
+
{JSON.stringify(value, null, 2)}
|
|
34
|
+
</pre>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Empty editor with secret-name suggestions available. */
|
|
40
|
+
export const Empty: Story = {
|
|
41
|
+
render: () => (
|
|
42
|
+
<Demo secretNames={["jira_token", "db_password", "smtp_api_key"]} />
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Pre-populated mapping; the stored value is the `${{ secrets.NAME }}` template. */
|
|
47
|
+
export const WithValues: Story = {
|
|
48
|
+
render: () => (
|
|
49
|
+
<Demo
|
|
50
|
+
initial={{
|
|
51
|
+
API_TOKEN: "${{ secrets.jira_token }}",
|
|
52
|
+
DB_PASSWORD: "${{ secrets.db_password }}",
|
|
53
|
+
}}
|
|
54
|
+
secretNames={["jira_token", "db_password", "smtp_api_key"]}
|
|
55
|
+
/>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** No name suggestions available — the secret field is a plain free-text
|
|
60
|
+
* combobox and no existence warning is shown (the list is unknown). */
|
|
61
|
+
export const NoSuggestions: Story = {
|
|
62
|
+
render: () => <Demo initial={{ TOKEN: "${{ secrets.some_secret }}" }} />,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A row references a secret that the loaded list doesn't contain — the row
|
|
67
|
+
* shows a non-blocking warning (red border + message). The secret may have
|
|
68
|
+
* been deleted/renamed or be created later, so the value still round-trips.
|
|
69
|
+
*/
|
|
70
|
+
export const UnknownSecret: Story = {
|
|
71
|
+
render: () => (
|
|
72
|
+
<Demo
|
|
73
|
+
initial={{
|
|
74
|
+
API_TOKEN: "${{ secrets.jira_token }}",
|
|
75
|
+
GHOST: "${{ secrets.deleted_secret }}",
|
|
76
|
+
}}
|
|
77
|
+
secretNames={["jira_token", "db_password"]}
|
|
78
|
+
/>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Loader2 } from "lucide-react";
|
|
3
|
+
import { Spinner } from "../src/components/Spinner";
|
|
4
|
+
import { Button } from "../src/components/Button";
|
|
5
|
+
import { cn } from "../src/utils";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Spinner> = {
|
|
8
|
+
title: "Components/Feedback/Spinner",
|
|
9
|
+
component: Spinner,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
argTypes: {
|
|
12
|
+
size: {
|
|
13
|
+
control: "select",
|
|
14
|
+
options: ["sm", "md", "lg"],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
size: "sm",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof Spinner>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {};
|
|
26
|
+
|
|
27
|
+
export const Sizes: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<div className="flex items-center gap-6">
|
|
30
|
+
<div className="flex flex-col items-center gap-2">
|
|
31
|
+
<Spinner size="sm" />
|
|
32
|
+
<span className="text-xs text-muted-foreground">sm (h-4 w-4)</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div className="flex flex-col items-center gap-2">
|
|
35
|
+
<Spinner size="md" />
|
|
36
|
+
<span className="text-xs text-muted-foreground">md (h-5 w-5)</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="flex flex-col items-center gap-2">
|
|
39
|
+
<Spinner size="lg" />
|
|
40
|
+
<span className="text-xs text-muted-foreground">lg (h-6 w-6)</span>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const InButton: Story = {
|
|
47
|
+
render: () => (
|
|
48
|
+
<div className="flex items-center gap-3">
|
|
49
|
+
<Button disabled>
|
|
50
|
+
<Spinner size="sm" className="mr-2" />
|
|
51
|
+
Saving...
|
|
52
|
+
</Button>
|
|
53
|
+
<Button variant="outline" disabled>
|
|
54
|
+
<Spinner size="sm" className="mr-2" />
|
|
55
|
+
Loading
|
|
56
|
+
</Button>
|
|
57
|
+
</div>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const WithAriaLabel: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
"aria-label": "Loading",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* On low-power devices (or when the OS requests reduced motion) the spinner
|
|
69
|
+
* renders the static icon with no spin. The component handles this internally
|
|
70
|
+
* via `usePerformance().isLowPower`; this story renders the static icon
|
|
71
|
+
* directly to illustrate the result, since stories run with motion enabled.
|
|
72
|
+
*/
|
|
73
|
+
export const LowPowerStatic: Story = {
|
|
74
|
+
render: () => (
|
|
75
|
+
<div className="flex items-center gap-6">
|
|
76
|
+
<div className="flex flex-col items-center gap-2">
|
|
77
|
+
<Loader2 className={cn("h-4 w-4")} aria-hidden />
|
|
78
|
+
<span className="text-xs text-muted-foreground">sm (no spin)</span>
|
|
79
|
+
</div>
|
|
80
|
+
<div className="flex flex-col items-center gap-2">
|
|
81
|
+
<Loader2 className={cn("h-5 w-5")} aria-hidden />
|
|
82
|
+
<span className="text-xs text-muted-foreground">md (no spin)</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="flex flex-col items-center gap-2">
|
|
85
|
+
<Loader2 className={cn("h-6 w-6")} aria-hidden />
|
|
86
|
+
<span className="text-xs text-muted-foreground">lg (no spin)</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
),
|
|
90
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { TimeOfDayInput } from "../src/components/TimeOfDayInput";
|
|
4
|
+
import { Label } from "../src/components/Label";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof TimeOfDayInput> = {
|
|
7
|
+
title: "Components/Inputs/TimeOfDayInput",
|
|
8
|
+
component: TimeOfDayInput,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof TimeOfDayInput>;
|
|
14
|
+
|
|
15
|
+
const Demo = ({ initial }: { initial?: string }) => {
|
|
16
|
+
const [value, setValue] = useState<string | undefined>(initial);
|
|
17
|
+
return (
|
|
18
|
+
<div className="max-w-sm space-y-2">
|
|
19
|
+
<Label>On-call window starts at</Label>
|
|
20
|
+
<TimeOfDayInput value={value} onChange={setValue} />
|
|
21
|
+
<pre className="text-xs text-muted-foreground">
|
|
22
|
+
{JSON.stringify(value ?? null)}
|
|
23
|
+
</pre>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Empty: Story = { render: () => <Demo /> };
|
|
29
|
+
|
|
30
|
+
export const Set: Story = { render: () => <Demo initial="22:00" /> };
|
|
31
|
+
|
|
32
|
+
export const Disabled: Story = {
|
|
33
|
+
render: () => <TimeOfDayInput value="09:30" onChange={() => {}} disabled />,
|
|
34
|
+
};
|
package/tsconfig.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"include": [
|
|
4
4
|
"src",
|
|
5
5
|
"src/components/CodeEditor/generated/stdlib-types.json",
|
|
6
|
+
"src/components/CodeEditor/generated/builtin-modules.json",
|
|
6
7
|
"scripts"
|
|
7
8
|
],
|
|
8
9
|
"references": [
|
|
@@ -12,6 +13,9 @@
|
|
|
12
13
|
{
|
|
13
14
|
"path": "../frontend-api"
|
|
14
15
|
},
|
|
16
|
+
{
|
|
17
|
+
"path": "../template-engine"
|
|
18
|
+
},
|
|
15
19
|
{
|
|
16
20
|
"path": "../test-utils-frontend"
|
|
17
21
|
}
|