@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.
Files changed (71) hide show
  1. package/.storybook/main.ts +43 -0
  2. package/CHANGELOG.md +326 -0
  3. package/package.json +23 -18
  4. package/scripts/generate-stdlib-types.ts +23 -0
  5. package/src/components/Accordion.tsx +17 -9
  6. package/src/components/ActionCard.tsx +99 -11
  7. package/src/components/BrandIcon.tsx +57 -0
  8. package/src/components/CodeEditor/CodeEditor.tsx +159 -14
  9. package/src/components/CodeEditor/TypefoxEditor.tsx +537 -168
  10. package/src/components/CodeEditor/editorTheme.test.ts +41 -0
  11. package/src/components/CodeEditor/editorTheme.ts +26 -0
  12. package/src/components/CodeEditor/generated/builtin-modules.json +1 -0
  13. package/src/components/CodeEditor/importSpecifiers.test.ts +286 -0
  14. package/src/components/CodeEditor/importSpecifiers.ts +267 -0
  15. package/src/components/CodeEditor/index.ts +26 -0
  16. package/src/components/CodeEditor/monacoGuard.ts +76 -0
  17. package/src/components/CodeEditor/monacoTsService.ts +185 -0
  18. package/src/components/CodeEditor/popoutTitle.test.ts +37 -0
  19. package/src/components/CodeEditor/popoutTitle.ts +31 -0
  20. package/src/components/CodeEditor/scriptContext.test.ts +15 -7
  21. package/src/components/CodeEditor/scriptContext.ts +12 -18
  22. package/src/components/CodeEditor/scriptDiagnostics.test.ts +135 -0
  23. package/src/components/CodeEditor/scriptDiagnostics.ts +172 -0
  24. package/src/components/CodeEditor/types.ts +79 -0
  25. package/src/components/CodeEditor/validateScripts.ts +172 -0
  26. package/src/components/CodeEditor/vscodeServicesSignal.ts +72 -0
  27. package/src/components/ConfirmationModal.tsx +7 -1
  28. package/src/components/Dialog.tsx +32 -11
  29. package/src/components/DurationInput.tsx +121 -0
  30. package/src/components/DynamicForm/DynamicForm.tsx +119 -47
  31. package/src/components/DynamicForm/DynamicOptionsField.tsx +19 -14
  32. package/src/components/DynamicForm/FormField.tsx +183 -15
  33. package/src/components/DynamicForm/MultiTypeEditorField.tsx +78 -2
  34. package/src/components/DynamicForm/SecretEnvEditor.tsx +315 -0
  35. package/src/components/DynamicForm/index.ts +20 -0
  36. package/src/components/DynamicForm/secretEnv.logic.test.ts +126 -0
  37. package/src/components/DynamicForm/secretEnv.logic.ts +87 -0
  38. package/src/components/DynamicForm/types.ts +134 -1
  39. package/src/components/DynamicForm/utils.test.ts +38 -0
  40. package/src/components/DynamicForm/utils.ts +54 -0
  41. package/src/components/DynamicForm/validation.logic.test.ts +255 -0
  42. package/src/components/DynamicForm/validation.logic.ts +210 -0
  43. package/src/components/DynamicIcon.tsx +39 -17
  44. package/src/components/Markdown.tsx +68 -2
  45. package/src/components/Popover.tsx +6 -1
  46. package/src/components/ScriptTestPanel.logic.test.ts +139 -0
  47. package/src/components/ScriptTestPanel.logic.ts +137 -0
  48. package/src/components/ScriptTestPanel.tsx +394 -0
  49. package/src/components/Sheet.tsx +21 -6
  50. package/src/components/Spinner.tsx +56 -0
  51. package/src/components/StatusBadge.tsx +78 -0
  52. package/src/components/StrategyConfigCard.tsx +3 -3
  53. package/src/components/Tabs.tsx +7 -1
  54. package/src/components/TimeOfDayInput.tsx +116 -0
  55. package/src/components/UserMenu.logic.test.ts +37 -0
  56. package/src/components/UserMenu.logic.ts +30 -0
  57. package/src/components/UserMenu.tsx +40 -12
  58. package/src/components/comboboxInteraction.ts +39 -0
  59. package/src/components/iconRegistry.tsx +27 -0
  60. package/src/components/portalContainer.ts +24 -0
  61. package/src/index.ts +7 -0
  62. package/stories/ActionCard.stories.tsx +60 -0
  63. package/stories/CodeEditor.stories.tsx +47 -2
  64. package/stories/DurationInput.stories.tsx +59 -0
  65. package/stories/Introduction.mdx +1 -1
  66. package/stories/Markdown.stories.tsx +56 -0
  67. package/stories/ScriptTestPanel.stories.tsx +106 -0
  68. package/stories/SecretEnvEditor.stories.tsx +80 -0
  69. package/stories/Spinner.stories.tsx +90 -0
  70. package/stories/TimeOfDayInput.stories.tsx +34 -0
  71. 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
  }