@grackle-ai/web-components 0.107.2
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/.rush/temp/3ae72563f781afd72723475938136f113846603e.untar.log +10 -0
- package/.rush/temp/bc1d5bf9201ce71abeaeaddd096deb9b0805d703.untar.log +10 -0
- package/.rush/temp/operation/_phase_build/all.log +18 -0
- package/.rush/temp/operation/_phase_build/log-chunks.jsonl +18 -0
- package/.rush/temp/operation/_phase_build/state.json +3 -0
- package/.rush/temp/operation/_phase_test/all.log +121 -0
- package/.rush/temp/operation/_phase_test/log-chunks.jsonl +121 -0
- package/.rush/temp/operation/_phase_test/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +938 -0
- package/.storybook/main.ts +22 -0
- package/.storybook/preview.tsx +30 -0
- package/config/rig.json +4 -0
- package/config/rush-project.json +12 -0
- package/dist/index.css +1 -0
- package/dist/index.js +39221 -0
- package/eslint.config.cjs +5 -0
- package/package.json +83 -0
- package/rush-logs/web-components._phase_build.cache.log +4 -0
- package/rush-logs/web-components._phase_test.cache.log +4 -0
- package/src/components/chat/ChatInput.module.scss +81 -0
- package/src/components/chat/ChatInput.stories.tsx +91 -0
- package/src/components/chat/ChatInput.tsx +168 -0
- package/src/components/chat/index.ts +6 -0
- package/src/components/dag/DagView.module.scss +149 -0
- package/src/components/dag/DagView.stories.tsx +125 -0
- package/src/components/dag/DagView.tsx +109 -0
- package/src/components/dag/TaskNode.stories.tsx +133 -0
- package/src/components/dag/TaskNode.tsx +40 -0
- package/src/components/dag/useDagLayout.ts +139 -0
- package/src/components/display/Breadcrumbs.module.scss +71 -0
- package/src/components/display/Breadcrumbs.stories.tsx +80 -0
- package/src/components/display/Breadcrumbs.tsx +46 -0
- package/src/components/display/Button.module.scss +110 -0
- package/src/components/display/Button.stories.tsx +88 -0
- package/src/components/display/Button.tsx +40 -0
- package/src/components/display/ConfirmDialog.module.scss +67 -0
- package/src/components/display/ConfirmDialog.stories.tsx +81 -0
- package/src/components/display/ConfirmDialog.tsx +88 -0
- package/src/components/display/CopyButton.module.scss +41 -0
- package/src/components/display/CopyButton.stories.tsx +78 -0
- package/src/components/display/CopyButton.tsx +64 -0
- package/src/components/display/DemoBanner.module.scss +37 -0
- package/src/components/display/DemoBanner.stories.tsx +40 -0
- package/src/components/display/DemoBanner.tsx +23 -0
- package/src/components/display/EventHoverRow.module.scss +102 -0
- package/src/components/display/EventHoverRow.stories.tsx +99 -0
- package/src/components/display/EventHoverRow.tsx +154 -0
- package/src/components/display/EventRenderer.module.scss +272 -0
- package/src/components/display/EventRenderer.stories.tsx +186 -0
- package/src/components/display/EventRenderer.tsx +271 -0
- package/src/components/display/EventStream.module.scss +93 -0
- package/src/components/display/EventStream.stories.tsx +249 -0
- package/src/components/display/EventStream.tsx +369 -0
- package/src/components/display/FloatingActionBar.module.scss +107 -0
- package/src/components/display/FloatingActionBar.stories.tsx +122 -0
- package/src/components/display/FloatingActionBar.tsx +119 -0
- package/src/components/display/SessionAttemptSelector.module.scss +50 -0
- package/src/components/display/SessionAttemptSelector.stories.tsx +78 -0
- package/src/components/display/SessionAttemptSelector.tsx +49 -0
- package/src/components/display/SessionPicker.module.scss +200 -0
- package/src/components/display/SessionPicker.stories.tsx +169 -0
- package/src/components/display/SessionPicker.tsx +214 -0
- package/src/components/display/Skeleton.module.scss +58 -0
- package/src/components/display/Skeleton.stories.tsx +94 -0
- package/src/components/display/Skeleton.tsx +127 -0
- package/src/components/display/Spinner.module.scss +41 -0
- package/src/components/display/Spinner.stories.tsx +66 -0
- package/src/components/display/Spinner.tsx +32 -0
- package/src/components/display/SplashScreen.module.scss +20 -0
- package/src/components/display/SplashScreen.stories.tsx +26 -0
- package/src/components/display/SplashScreen.tsx +16 -0
- package/src/components/display/SplitButton.module.scss +166 -0
- package/src/components/display/SplitButton.stories.tsx +95 -0
- package/src/components/display/SplitButton.tsx +128 -0
- package/src/components/display/Tooltip.module.scss +84 -0
- package/src/components/display/Tooltip.stories.tsx +240 -0
- package/src/components/display/Tooltip.tsx +184 -0
- package/src/components/display/extractText.test.tsx +48 -0
- package/src/components/display/index.ts +20 -0
- package/src/components/editable/EditableCheckbox.stories.tsx +54 -0
- package/src/components/editable/EditableCheckbox.tsx +39 -0
- package/src/components/editable/EditableField.module.scss +135 -0
- package/src/components/editable/EditableSelect.tsx +164 -0
- package/src/components/editable/EditableTextArea.stories.tsx +50 -0
- package/src/components/editable/EditableTextArea.tsx +148 -0
- package/src/components/editable/EditableTextField.stories.tsx +62 -0
- package/src/components/editable/EditableTextField.tsx +153 -0
- package/src/components/editable/EnvironmentSelect.module.scss +17 -0
- package/src/components/editable/EnvironmentSelect.stories.tsx +61 -0
- package/src/components/editable/EnvironmentSelect.tsx +87 -0
- package/src/components/editable/index.ts +13 -0
- package/src/components/editable/useEditableField.test.tsx +233 -0
- package/src/components/editable/useEditableField.ts +173 -0
- package/src/components/index.ts +20 -0
- package/src/components/knowledge/KnowledgeDetailPanel.module.scss +162 -0
- package/src/components/knowledge/KnowledgeDetailPanel.stories.tsx +208 -0
- package/src/components/knowledge/KnowledgeDetailPanel.tsx +122 -0
- package/src/components/knowledge/KnowledgeGraph.module.scss +110 -0
- package/src/components/knowledge/KnowledgeGraph.stories.tsx +180 -0
- package/src/components/knowledge/KnowledgeGraph.tsx +455 -0
- package/src/components/knowledge/KnowledgeNav.module.scss +130 -0
- package/src/components/knowledge/KnowledgeNav.stories.tsx +108 -0
- package/src/components/knowledge/KnowledgeNav.tsx +138 -0
- package/src/components/knowledge/index.ts +3 -0
- package/src/components/layout/AppNav.module.scss +82 -0
- package/src/components/layout/AppNav.stories.tsx +115 -0
- package/src/components/layout/AppNav.tsx +133 -0
- package/src/components/layout/BottomStatusBar.module.scss +58 -0
- package/src/components/layout/BottomStatusBar.stories.tsx +35 -0
- package/src/components/layout/BottomStatusBar.tsx +206 -0
- package/src/components/layout/Sidebar.module.scss +60 -0
- package/src/components/layout/Sidebar.stories.tsx +46 -0
- package/src/components/layout/Sidebar.tsx +84 -0
- package/src/components/layout/StatusBar.module.scss +108 -0
- package/src/components/layout/StatusBar.stories.tsx +119 -0
- package/src/components/layout/StatusBar.tsx +70 -0
- package/src/components/layout/index.ts +9 -0
- package/src/components/lists/EnvironmentNav.module.scss +118 -0
- package/src/components/lists/EnvironmentNav.stories.tsx +121 -0
- package/src/components/lists/EnvironmentNav.tsx +133 -0
- package/src/components/lists/FindingsNav.module.scss +126 -0
- package/src/components/lists/FindingsNav.tsx +146 -0
- package/src/components/lists/TaskList.module.scss +206 -0
- package/src/components/lists/TaskList.stories.tsx +401 -0
- package/src/components/lists/TaskList.tsx +509 -0
- package/src/components/lists/index.ts +6 -0
- package/src/components/lists/listHelpers.tsx +130 -0
- package/src/components/notifications/Callout.module.scss +83 -0
- package/src/components/notifications/Callout.stories.tsx +81 -0
- package/src/components/notifications/Callout.tsx +64 -0
- package/src/components/notifications/Toast.module.scss +86 -0
- package/src/components/notifications/Toast.stories.tsx +71 -0
- package/src/components/notifications/Toast.tsx +51 -0
- package/src/components/notifications/ToastContainer.module.scss +23 -0
- package/src/components/notifications/ToastContainer.stories.tsx +66 -0
- package/src/components/notifications/ToastContainer.tsx +29 -0
- package/src/components/notifications/UpdateBanner.stories.tsx +77 -0
- package/src/components/notifications/UpdateBanner.test.tsx +64 -0
- package/src/components/notifications/UpdateBanner.tsx +44 -0
- package/src/components/notifications/index.ts +8 -0
- package/src/components/panels/AboutPanel.stories.tsx +70 -0
- package/src/components/panels/AboutPanel.tsx +66 -0
- package/src/components/panels/AppearancePanel.stories.tsx +45 -0
- package/src/components/panels/AppearancePanel.tsx +97 -0
- package/src/components/panels/CredentialProvidersPanel.stories.tsx +62 -0
- package/src/components/panels/CredentialProvidersPanel.tsx +111 -0
- package/src/components/panels/EnvironmentEditPanel.module.scss +170 -0
- package/src/components/panels/EnvironmentEditPanel.stories.tsx +206 -0
- package/src/components/panels/EnvironmentEditPanel.tsx +785 -0
- package/src/components/panels/FindingsPanel.module.scss +94 -0
- package/src/components/panels/FindingsPanel.stories.tsx +109 -0
- package/src/components/panels/FindingsPanel.tsx +76 -0
- package/src/components/panels/KeyboardShortcutsPanel.module.scss +65 -0
- package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +40 -0
- package/src/components/panels/KeyboardShortcutsPanel.tsx +104 -0
- package/src/components/panels/PluginsPanel.tsx +77 -0
- package/src/components/panels/SettingsPanel.module.scss +336 -0
- package/src/components/panels/TaskActionButtons.module.scss +22 -0
- package/src/components/panels/TaskActionButtons.stories.tsx +125 -0
- package/src/components/panels/TaskActionButtons.tsx +87 -0
- package/src/components/panels/TaskEditPanel.module.scss +202 -0
- package/src/components/panels/TaskEditPanel.stories.tsx +75 -0
- package/src/components/panels/TaskEditPanel.tsx +328 -0
- package/src/components/panels/TaskOverviewPanel.module.scss +236 -0
- package/src/components/panels/TaskOverviewPanel.stories.tsx +219 -0
- package/src/components/panels/TaskOverviewPanel.tsx +270 -0
- package/src/components/panels/TokensPanel.stories.tsx +131 -0
- package/src/components/panels/TokensPanel.tsx +143 -0
- package/src/components/panels/WorkpadPanel.module.scss +39 -0
- package/src/components/panels/WorkpadPanel.stories.tsx +56 -0
- package/src/components/panels/WorkpadPanel.tsx +63 -0
- package/src/components/panels/index.ts +13 -0
- package/src/components/personas/McpToolSelector.module.scss +109 -0
- package/src/components/personas/McpToolSelector.stories.tsx +129 -0
- package/src/components/personas/McpToolSelector.tsx +180 -0
- package/src/components/personas/PersonaManager.module.scss +233 -0
- package/src/components/personas/PersonaManager.stories.tsx +139 -0
- package/src/components/personas/PersonaManager.tsx +122 -0
- package/src/components/schedules/ScheduleManager.module.scss +98 -0
- package/src/components/schedules/ScheduleManager.stories.tsx +78 -0
- package/src/components/schedules/ScheduleManager.tsx +160 -0
- package/src/components/settings/SettingsNav.module.scss +82 -0
- package/src/components/settings/SettingsNav.stories.tsx +83 -0
- package/src/components/settings/SettingsNav.tsx +104 -0
- package/src/components/streams/StreamDetailPanel.module.scss +206 -0
- package/src/components/streams/StreamDetailPanel.stories.tsx +132 -0
- package/src/components/streams/StreamDetailPanel.tsx +119 -0
- package/src/components/streams/StreamList.module.scss +92 -0
- package/src/components/streams/StreamList.stories.tsx +99 -0
- package/src/components/streams/StreamList.tsx +114 -0
- package/src/components/streams/index.ts +10 -0
- package/src/components/tools/AgentToolCard.module.scss +118 -0
- package/src/components/tools/AgentToolCard.stories.tsx +304 -0
- package/src/components/tools/AgentToolCard.tsx +247 -0
- package/src/components/tools/FileEditCard.stories.tsx +138 -0
- package/src/components/tools/FileEditCard.tsx +160 -0
- package/src/components/tools/FileReadCard.stories.tsx +120 -0
- package/src/components/tools/FileReadCard.tsx +106 -0
- package/src/components/tools/FindingCard.stories.tsx +124 -0
- package/src/components/tools/FindingCard.tsx +178 -0
- package/src/components/tools/GenericToolCard.stories.tsx +80 -0
- package/src/components/tools/GenericToolCard.tsx +111 -0
- package/src/components/tools/IpcCard.stories.tsx +129 -0
- package/src/components/tools/IpcCard.tsx +178 -0
- package/src/components/tools/KnowledgeCard.stories.tsx +112 -0
- package/src/components/tools/KnowledgeCard.tsx +165 -0
- package/src/components/tools/MetadataCard.stories.tsx +32 -0
- package/src/components/tools/MetadataCard.tsx +39 -0
- package/src/components/tools/SearchCard.stories.tsx +74 -0
- package/src/components/tools/SearchCard.tsx +86 -0
- package/src/components/tools/ShellCard.stories.tsx +112 -0
- package/src/components/tools/ShellCard.tsx +106 -0
- package/src/components/tools/TaskCard.stories.tsx +123 -0
- package/src/components/tools/TaskCard.tsx +203 -0
- package/src/components/tools/TodoCard.module.scss +131 -0
- package/src/components/tools/TodoCard.stories.tsx +202 -0
- package/src/components/tools/TodoCard.tsx +200 -0
- package/src/components/tools/ToolCard.stories.tsx +177 -0
- package/src/components/tools/ToolCard.tsx +60 -0
- package/src/components/tools/ToolCardProps.ts +20 -0
- package/src/components/tools/ToolSearchCard.stories.tsx +81 -0
- package/src/components/tools/ToolSearchCard.tsx +86 -0
- package/src/components/tools/WorkpadCard.stories.tsx +106 -0
- package/src/components/tools/WorkpadCard.tsx +125 -0
- package/src/components/tools/classifyTool.test.ts +44 -0
- package/src/components/tools/classifyTool.ts +134 -0
- package/src/components/tools/parseDiff.ts +95 -0
- package/src/components/tools/parseShellOutput.ts +28 -0
- package/src/components/tools/toolCardHelpers.test.ts +53 -0
- package/src/components/tools/toolCards.module.scss +234 -0
- package/src/components/workspace/WorkspaceBoard.module.scss +238 -0
- package/src/components/workspace/WorkspaceBoard.stories.tsx +240 -0
- package/src/components/workspace/WorkspaceBoard.tsx +232 -0
- package/src/components/workspace/WorkspaceFormFields.module.scss +79 -0
- package/src/components/workspace/WorkspaceFormFields.stories.tsx +133 -0
- package/src/components/workspace/WorkspaceFormFields.tsx +185 -0
- package/src/context/GrackleContext.ts +28 -0
- package/src/context/GrackleContextTypes.ts +64 -0
- package/src/context/SidebarContext.tsx +53 -0
- package/src/context/ThemeContext.tsx +21 -0
- package/src/context/ToastContext.tsx +56 -0
- package/src/hooks/types.ts +864 -0
- package/src/hooks/useEventSelection.test.ts +204 -0
- package/src/hooks/useEventSelection.ts +158 -0
- package/src/hooks/useSmartScroll.ts +151 -0
- package/src/hooks/useTheme.ts +228 -0
- package/src/index.ts +210 -0
- package/src/mocks/MockGrackleProvider.tsx +1397 -0
- package/src/mocks/mockData.ts +1966 -0
- package/src/mocks/mockKnowledgeData.ts +294 -0
- package/src/scss.d.ts +12 -0
- package/src/styles/global.scss +244 -0
- package/src/styles/mixins.scss +278 -0
- package/src/styles/prism-theme.scss +148 -0
- package/src/styles/theme.scss +1102 -0
- package/src/test-utils/storybook-decorators.tsx +50 -0
- package/src/test-utils/storybook-helpers.ts +262 -0
- package/src/themes.ts +142 -0
- package/src/utils/boardColumns.ts +141 -0
- package/src/utils/breadcrumbs.test.ts +285 -0
- package/src/utils/breadcrumbs.ts +222 -0
- package/src/utils/dashboard.test.ts +156 -0
- package/src/utils/dashboard.ts +195 -0
- package/src/utils/eventContent.test.ts +353 -0
- package/src/utils/eventContent.ts +209 -0
- package/src/utils/findingCategory.ts +33 -0
- package/src/utils/format.ts +27 -0
- package/src/utils/iconSize.ts +18 -0
- package/src/utils/navigation.ts +205 -0
- package/src/utils/route-config.test.ts +128 -0
- package/src/utils/scrollUtils.test.ts +65 -0
- package/src/utils/scrollUtils.ts +49 -0
- package/src/utils/sessionEvents.test.ts +302 -0
- package/src/utils/sessionEvents.ts +233 -0
- package/src/utils/taskStatus.tsx +137 -0
- package/src/utils/time.ts +92 -0
- package/tsconfig.json +8 -0
- package/vite.config.ts +20 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Callout — inline contextual alert for persistent information within a panel
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
.callout {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: flex-start;
|
|
8
|
+
gap: var(--space-sm);
|
|
9
|
+
padding: var(--space-sm) var(--space-md);
|
|
10
|
+
border-radius: var(--radius-md);
|
|
11
|
+
border: 1px solid transparent;
|
|
12
|
+
font-size: var(--font-size-sm);
|
|
13
|
+
line-height: var(--line-height);
|
|
14
|
+
|
|
15
|
+
// Variant: success (green)
|
|
16
|
+
&.success {
|
|
17
|
+
background: var(--accent-green-dim);
|
|
18
|
+
border-color: var(--accent-green-border);
|
|
19
|
+
|
|
20
|
+
.icon {
|
|
21
|
+
color: var(--accent-green);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Variant: error (red)
|
|
26
|
+
&.error {
|
|
27
|
+
background: var(--accent-red-dim);
|
|
28
|
+
border-color: var(--accent-red-border);
|
|
29
|
+
|
|
30
|
+
.icon {
|
|
31
|
+
color: var(--accent-red);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Variant: warning (yellow)
|
|
36
|
+
&.warning {
|
|
37
|
+
background: var(--accent-yellow-dim);
|
|
38
|
+
border-color: var(--accent-yellow-border);
|
|
39
|
+
|
|
40
|
+
.icon {
|
|
41
|
+
color: var(--accent-yellow);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Variant: info (blue)
|
|
46
|
+
&.info {
|
|
47
|
+
background: var(--accent-blue-dim);
|
|
48
|
+
border-color: var(--accent-blue-border);
|
|
49
|
+
|
|
50
|
+
.icon {
|
|
51
|
+
color: var(--accent-blue);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.icon {
|
|
57
|
+
flex-shrink: 0;
|
|
58
|
+
display: inline-flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
width: 16px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.content {
|
|
65
|
+
flex: 1;
|
|
66
|
+
color: var(--text-primary);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.close {
|
|
70
|
+
background: none;
|
|
71
|
+
border: none;
|
|
72
|
+
color: var(--text-tertiary);
|
|
73
|
+
font-size: var(--font-size-lg);
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
padding: 0 var(--space-xs);
|
|
76
|
+
line-height: 1;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
transition: color var(--transition-fast);
|
|
79
|
+
|
|
80
|
+
&:hover {
|
|
81
|
+
color: var(--text-primary);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, userEvent } from "@storybook/test";
|
|
3
|
+
import { Callout } from "./Callout.js";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Callout> = {
|
|
6
|
+
component: Callout,
|
|
7
|
+
title: "Primitives/Notifications/Callout",
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
args: {
|
|
10
|
+
children: "This is a callout message.",
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
/** Default info callout. */
|
|
17
|
+
export const Info: Story = {
|
|
18
|
+
play: async ({ canvas }) => {
|
|
19
|
+
const callout = canvas.getByRole("status");
|
|
20
|
+
await expect(callout).toBeInTheDocument();
|
|
21
|
+
await expect(callout).toHaveTextContent("This is a callout message.");
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Error callout uses alert role for screen readers. */
|
|
26
|
+
export const Error: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
variant: "error",
|
|
29
|
+
children: "Something went wrong.",
|
|
30
|
+
},
|
|
31
|
+
play: async ({ canvas }) => {
|
|
32
|
+
const callout = canvas.getByRole("alert");
|
|
33
|
+
await expect(callout).toBeInTheDocument();
|
|
34
|
+
await expect(callout).toHaveTextContent("Something went wrong.");
|
|
35
|
+
await expect(callout.className).toContain("error");
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Warning callout uses alert role for screen readers. */
|
|
40
|
+
export const Warning: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
variant: "warning",
|
|
43
|
+
children: "Check your settings.",
|
|
44
|
+
},
|
|
45
|
+
play: async ({ canvas }) => {
|
|
46
|
+
const callout = canvas.getByRole("alert");
|
|
47
|
+
await expect(callout).toBeInTheDocument();
|
|
48
|
+
await expect(callout).toHaveTextContent("Check your settings.");
|
|
49
|
+
await expect(callout.className).toContain("warning");
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Success callout. */
|
|
54
|
+
export const Success: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
variant: "success",
|
|
57
|
+
children: "Operation completed.",
|
|
58
|
+
},
|
|
59
|
+
play: async ({ canvas }) => {
|
|
60
|
+
const callout = canvas.getByRole("status");
|
|
61
|
+
await expect(callout).toBeInTheDocument();
|
|
62
|
+
await expect(callout).toHaveTextContent("Operation completed.");
|
|
63
|
+
await expect(callout.className).toContain("success");
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Dismissible callout can be closed by clicking the dismiss button. */
|
|
68
|
+
export const Dismissible: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
dismissible: true,
|
|
71
|
+
children: "You can dismiss this.",
|
|
72
|
+
},
|
|
73
|
+
play: async ({ canvas }) => {
|
|
74
|
+
const callout = canvas.getByRole("status");
|
|
75
|
+
await expect(callout).toBeInTheDocument();
|
|
76
|
+
const dismiss = canvas.getByLabelText("Dismiss");
|
|
77
|
+
await expect(dismiss).toBeInTheDocument();
|
|
78
|
+
await userEvent.click(dismiss);
|
|
79
|
+
await expect(canvas.queryByRole("status")).not.toBeInTheDocument();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useState, type ReactNode, type JSX } from "react";
|
|
2
|
+
import { AlertTriangle, Check, Info, X } from "lucide-react";
|
|
3
|
+
import styles from "./Callout.module.scss";
|
|
4
|
+
import { ICON_LG, ICON_MD } from "../../utils/iconSize.js";
|
|
5
|
+
|
|
6
|
+
/** Visual style variant for a callout. */
|
|
7
|
+
export type CalloutVariant = "success" | "error" | "warning" | "info";
|
|
8
|
+
|
|
9
|
+
interface CalloutProps {
|
|
10
|
+
/** Controls color scheme and icon. Defaults to "info". */
|
|
11
|
+
variant?: CalloutVariant;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** Show a dismiss button. Defaults to false. */
|
|
14
|
+
dismissible?: boolean;
|
|
15
|
+
/** Optional extra class name for layout overrides. */
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const VARIANT_ICONS: Record<CalloutVariant, ReactNode> = {
|
|
20
|
+
success: <Check size={ICON_LG} />,
|
|
21
|
+
error: <X size={ICON_LG} />,
|
|
22
|
+
warning: <AlertTriangle size={ICON_LG} />,
|
|
23
|
+
info: <Info size={ICON_LG} />,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Inline contextual alert for persistent, location-specific information.
|
|
28
|
+
* Use for things like blocked dependencies, validation errors, or
|
|
29
|
+
* status messages that belong within a specific panel rather than a toast.
|
|
30
|
+
*/
|
|
31
|
+
export function Callout({
|
|
32
|
+
variant = "info",
|
|
33
|
+
children,
|
|
34
|
+
dismissible = false,
|
|
35
|
+
className,
|
|
36
|
+
}: CalloutProps): JSX.Element {
|
|
37
|
+
const [dismissed, setDismissed] = useState(false);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
{!dismissed && (
|
|
42
|
+
<div
|
|
43
|
+
className={[styles.callout, styles[variant], className].filter(Boolean).join(" ")}
|
|
44
|
+
role={variant === "error" || variant === "warning" ? "alert" : "status"}
|
|
45
|
+
>
|
|
46
|
+
<span className={styles.icon} aria-hidden="true">
|
|
47
|
+
{VARIANT_ICONS[variant]}
|
|
48
|
+
</span>
|
|
49
|
+
<span className={styles.content}>{children}</span>
|
|
50
|
+
{dismissible && (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
className={styles.close}
|
|
54
|
+
onClick={() => setDismissed(true)}
|
|
55
|
+
aria-label="Dismiss"
|
|
56
|
+
>
|
|
57
|
+
<X size={ICON_MD} aria-hidden="true" />
|
|
58
|
+
</button>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
@use '../../styles/mixins' as *;
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Toast — individual transient notification badge
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
.toast {
|
|
8
|
+
@include surface-card;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: var(--space-sm);
|
|
12
|
+
padding: var(--space-sm) var(--space-md);
|
|
13
|
+
min-width: 240px;
|
|
14
|
+
max-width: 440px;
|
|
15
|
+
border-left: 3px solid transparent;
|
|
16
|
+
box-shadow: var(--shadow-lg);
|
|
17
|
+
|
|
18
|
+
// Variant: success (green)
|
|
19
|
+
&.success {
|
|
20
|
+
border-left-color: var(--accent-green);
|
|
21
|
+
box-shadow: var(--shadow-lg);
|
|
22
|
+
|
|
23
|
+
.icon {
|
|
24
|
+
color: var(--accent-green);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Variant: error (red)
|
|
29
|
+
&.error {
|
|
30
|
+
border-left-color: var(--accent-red);
|
|
31
|
+
box-shadow: var(--shadow-lg);
|
|
32
|
+
|
|
33
|
+
.icon {
|
|
34
|
+
color: var(--accent-red);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Variant: warning (yellow)
|
|
39
|
+
&.warning {
|
|
40
|
+
border-left-color: var(--accent-yellow);
|
|
41
|
+
|
|
42
|
+
.icon {
|
|
43
|
+
color: var(--accent-yellow);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Variant: info (blue)
|
|
48
|
+
&.info {
|
|
49
|
+
border-left-color: var(--accent-blue);
|
|
50
|
+
|
|
51
|
+
.icon {
|
|
52
|
+
color: var(--accent-blue);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.icon {
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
display: inline-flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
width: 16px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.message {
|
|
66
|
+
flex: 1;
|
|
67
|
+
font-size: var(--font-size-sm);
|
|
68
|
+
color: var(--text-primary);
|
|
69
|
+
line-height: var(--line-height);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.close {
|
|
73
|
+
background: none;
|
|
74
|
+
border: none;
|
|
75
|
+
color: var(--text-tertiary);
|
|
76
|
+
font-size: var(--font-size-lg);
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
padding: 0 var(--space-xs);
|
|
79
|
+
line-height: 1;
|
|
80
|
+
flex-shrink: 0;
|
|
81
|
+
transition: color var(--transition-fast);
|
|
82
|
+
|
|
83
|
+
&:hover {
|
|
84
|
+
color: var(--text-primary);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, fn } from "@storybook/test";
|
|
3
|
+
import { Toast } from "./Toast.js";
|
|
4
|
+
|
|
5
|
+
/** Very long duration to prevent auto-dismiss during testing. */
|
|
6
|
+
const TEST_DURATION: number = 999999;
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Toast> = {
|
|
9
|
+
component: Toast,
|
|
10
|
+
title: "Primitives/Notifications/Toast",
|
|
11
|
+
tags: ["autodocs"],
|
|
12
|
+
args: {
|
|
13
|
+
onDismiss: fn(),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
/** Info toast notification. */
|
|
20
|
+
export const Info: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
toast: { id: "t-info", message: "Something happened", variant: "info", duration: TEST_DURATION },
|
|
23
|
+
},
|
|
24
|
+
play: async ({ canvas }) => {
|
|
25
|
+
const toast = canvas.getByRole("status");
|
|
26
|
+
await expect(toast).toBeInTheDocument();
|
|
27
|
+
await expect(toast).toHaveTextContent("Something happened");
|
|
28
|
+
// Verify dismiss button exists
|
|
29
|
+
const dismiss = canvas.getByLabelText("Dismiss notification");
|
|
30
|
+
await expect(dismiss).toBeInTheDocument();
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Success toast notification. */
|
|
35
|
+
export const Success: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
toast: { id: "t-success", message: "Changes saved", variant: "success", duration: TEST_DURATION },
|
|
38
|
+
},
|
|
39
|
+
play: async ({ canvas }) => {
|
|
40
|
+
const toast = canvas.getByRole("status");
|
|
41
|
+
await expect(toast).toBeInTheDocument();
|
|
42
|
+
await expect(toast).toHaveTextContent("Changes saved");
|
|
43
|
+
await expect(toast.className).toContain("success");
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Error toast notification. */
|
|
48
|
+
export const Error: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
toast: { id: "t-error", message: "Failed to save", variant: "error", duration: TEST_DURATION },
|
|
51
|
+
},
|
|
52
|
+
play: async ({ canvas }) => {
|
|
53
|
+
const toast = canvas.getByRole("status");
|
|
54
|
+
await expect(toast).toBeInTheDocument();
|
|
55
|
+
await expect(toast).toHaveTextContent("Failed to save");
|
|
56
|
+
await expect(toast.className).toContain("error");
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Warning toast notification. */
|
|
61
|
+
export const Warning: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
toast: { id: "t-warn", message: "Connection unstable", variant: "warning", duration: TEST_DURATION },
|
|
64
|
+
},
|
|
65
|
+
play: async ({ canvas }) => {
|
|
66
|
+
const toast = canvas.getByRole("status");
|
|
67
|
+
await expect(toast).toBeInTheDocument();
|
|
68
|
+
await expect(toast).toHaveTextContent("Connection unstable");
|
|
69
|
+
await expect(toast.className).toContain("warning");
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect, type ReactNode, type JSX } from "react";
|
|
2
|
+
import { AlertTriangle, Check, Info, X } from "lucide-react";
|
|
3
|
+
import { motion } from "motion/react";
|
|
4
|
+
import type { ToastItem } from "../../context/ToastContext.js";
|
|
5
|
+
import styles from "./Toast.module.scss";
|
|
6
|
+
import { ICON_LG, ICON_MD } from "../../utils/iconSize.js";
|
|
7
|
+
|
|
8
|
+
const VARIANT_ICONS: Record<ToastItem["variant"], ReactNode> = {
|
|
9
|
+
success: <Check size={ICON_LG} />,
|
|
10
|
+
error: <X size={ICON_LG} />,
|
|
11
|
+
warning: <AlertTriangle size={ICON_LG} />,
|
|
12
|
+
info: <Info size={ICON_LG} />,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface ToastProps {
|
|
16
|
+
toast: ToastItem;
|
|
17
|
+
onDismiss: (id: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Animated individual toast notification. Auto-dismisses after toast.duration ms. */
|
|
21
|
+
export function Toast({ toast, onDismiss }: ToastProps): JSX.Element {
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const timer = setTimeout(() => onDismiss(toast.id), toast.duration);
|
|
24
|
+
return () => clearTimeout(timer);
|
|
25
|
+
}, [toast.id, toast.duration, onDismiss]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<motion.div
|
|
29
|
+
className={`${styles.toast} ${styles[toast.variant]}`}
|
|
30
|
+
role="status"
|
|
31
|
+
initial={{ opacity: 0, y: -16, scale: 0.94 }}
|
|
32
|
+
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
33
|
+
exit={{ opacity: 0, y: -8, scale: 0.94 }}
|
|
34
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
35
|
+
layout
|
|
36
|
+
>
|
|
37
|
+
<span className={styles.icon} aria-hidden="true">
|
|
38
|
+
{VARIANT_ICONS[toast.variant]}
|
|
39
|
+
</span>
|
|
40
|
+
<span className={styles.message}>{toast.message}</span>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
className={styles.close}
|
|
44
|
+
onClick={() => onDismiss(toast.id)}
|
|
45
|
+
aria-label="Dismiss notification"
|
|
46
|
+
>
|
|
47
|
+
<X size={ICON_MD} aria-hidden="true" />
|
|
48
|
+
</button>
|
|
49
|
+
</motion.div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// ToastContainer — fixed overlay at top-center for stacking toast notifications
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
position: fixed;
|
|
7
|
+
top: var(--space-xl);
|
|
8
|
+
left: 50%;
|
|
9
|
+
transform: translateX(-50%);
|
|
10
|
+
// Keep below ConfirmDialog modal overlay (z-index: 1000) so toasts cannot
|
|
11
|
+
// be interacted with while a blocking modal is open.
|
|
12
|
+
z-index: 900;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: var(--space-sm);
|
|
16
|
+
align-items: center;
|
|
17
|
+
// Container itself is transparent to mouse events; individual toasts are not
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
|
|
20
|
+
> * {
|
|
21
|
+
pointer-events: auto;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, fn } from "@storybook/test";
|
|
3
|
+
import { ToastContainer } from "./ToastContainer.js";
|
|
4
|
+
|
|
5
|
+
/** Very long duration to prevent auto-dismiss during testing. */
|
|
6
|
+
const TEST_DURATION: number = 999999;
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof ToastContainer> = {
|
|
9
|
+
component: ToastContainer,
|
|
10
|
+
title: "Primitives/Notifications/ToastContainer",
|
|
11
|
+
tags: ["autodocs"],
|
|
12
|
+
args: {
|
|
13
|
+
onDismiss: fn(),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
/** Empty container with no toasts. */
|
|
20
|
+
export const Empty: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
toasts: [],
|
|
23
|
+
},
|
|
24
|
+
play: async ({ canvas }) => {
|
|
25
|
+
const container = canvas.getByTestId("toast-container");
|
|
26
|
+
await expect(container).toBeInTheDocument();
|
|
27
|
+
// No toast items visible
|
|
28
|
+
const statuses = canvas.queryAllByRole("status");
|
|
29
|
+
await expect(statuses.length).toBe(0);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Container with a single toast. */
|
|
34
|
+
export const SingleToast: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
toasts: [
|
|
37
|
+
{ id: "t1", message: "File saved", variant: "success", duration: TEST_DURATION },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
play: async ({ canvas }) => {
|
|
41
|
+
const container = canvas.getByTestId("toast-container");
|
|
42
|
+
await expect(container).toBeInTheDocument();
|
|
43
|
+
const toast = canvas.getByRole("status");
|
|
44
|
+
await expect(toast).toHaveTextContent("File saved");
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Container rendering multiple toasts simultaneously. */
|
|
49
|
+
export const MultipleToasts: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
toasts: [
|
|
52
|
+
{ id: "t1", message: "First toast", variant: "info", duration: TEST_DURATION },
|
|
53
|
+
{ id: "t2", message: "Second toast", variant: "success", duration: TEST_DURATION },
|
|
54
|
+
{ id: "t3", message: "Third toast", variant: "error", duration: TEST_DURATION },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
play: async ({ canvas }) => {
|
|
58
|
+
const container = canvas.getByTestId("toast-container");
|
|
59
|
+
await expect(container).toBeInTheDocument();
|
|
60
|
+
const statuses = canvas.getAllByRole("status");
|
|
61
|
+
await expect(statuses.length).toBe(3);
|
|
62
|
+
await expect(canvas.getByText("First toast")).toBeInTheDocument();
|
|
63
|
+
await expect(canvas.getByText("Second toast")).toBeInTheDocument();
|
|
64
|
+
await expect(canvas.getByText("Third toast")).toBeInTheDocument();
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AnimatePresence } from "motion/react";
|
|
2
|
+
import type { JSX } from "react";
|
|
3
|
+
import type { ToastItem } from "../../context/ToastContext.js";
|
|
4
|
+
import { Toast } from "./Toast.js";
|
|
5
|
+
import styles from "./ToastContainer.module.scss";
|
|
6
|
+
|
|
7
|
+
/** Props for the ToastContainer component. */
|
|
8
|
+
export interface ToastContainerProps {
|
|
9
|
+
/** Active toast notifications to render. */
|
|
10
|
+
toasts: ToastItem[];
|
|
11
|
+
/** Callback to dismiss a toast by id. */
|
|
12
|
+
onDismiss: (id: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fixed overlay that renders all active toasts at the top-center of the viewport.
|
|
17
|
+
* Mount this once alongside your main app layout and pass toasts from context.
|
|
18
|
+
*/
|
|
19
|
+
export function ToastContainer({ toasts, onDismiss }: ToastContainerProps): JSX.Element {
|
|
20
|
+
return (
|
|
21
|
+
<div className={styles.container} data-testid="toast-container">
|
|
22
|
+
<AnimatePresence>
|
|
23
|
+
{toasts.map((toast) => (
|
|
24
|
+
<Toast key={toast.id} toast={toast} onDismiss={onDismiss} />
|
|
25
|
+
))}
|
|
26
|
+
</AnimatePresence>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { expect, userEvent, within } from "@storybook/test";
|
|
3
|
+
import { UpdateBanner } from "./UpdateBanner.js";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof UpdateBanner> = {
|
|
6
|
+
title: "App/Notifications/UpdateBanner",
|
|
7
|
+
component: UpdateBanner,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof UpdateBanner>;
|
|
12
|
+
|
|
13
|
+
/** npm user sees an update available with install instructions. */
|
|
14
|
+
export const NpmUpdate: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
currentVersion: "0.76.0",
|
|
17
|
+
latestVersion: "0.77.0",
|
|
18
|
+
updateAvailable: true,
|
|
19
|
+
isDocker: false,
|
|
20
|
+
},
|
|
21
|
+
play: async ({ canvasElement }) => {
|
|
22
|
+
const canvas = within(canvasElement);
|
|
23
|
+
await expect(canvas.getByTestId("update-banner")).toBeInTheDocument();
|
|
24
|
+
await expect(canvas.getByText(/npm install/)).toBeInTheDocument();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Docker user sees an update available with pull instructions. */
|
|
29
|
+
export const DockerUpdate: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
currentVersion: "0.76.0",
|
|
32
|
+
latestVersion: "0.77.0",
|
|
33
|
+
updateAvailable: true,
|
|
34
|
+
isDocker: true,
|
|
35
|
+
},
|
|
36
|
+
play: async ({ canvasElement }) => {
|
|
37
|
+
const canvas = within(canvasElement);
|
|
38
|
+
await expect(canvas.getByTestId("update-banner")).toBeInTheDocument();
|
|
39
|
+
await expect(canvas.getByText(/docker pull/)).toBeInTheDocument();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** No update available — banner is hidden. */
|
|
44
|
+
export const NoUpdate: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
currentVersion: "0.76.0",
|
|
47
|
+
latestVersion: "0.76.0",
|
|
48
|
+
updateAvailable: false,
|
|
49
|
+
isDocker: false,
|
|
50
|
+
},
|
|
51
|
+
play: async ({ canvasElement }) => {
|
|
52
|
+
const canvas = within(canvasElement);
|
|
53
|
+
await expect(canvas.queryByTestId("update-banner")).not.toBeInTheDocument();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** User dismisses the banner — Callout hides its content after clicking the close button. */
|
|
58
|
+
export const Dismissed: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
currentVersion: "0.76.0",
|
|
61
|
+
latestVersion: "0.77.0",
|
|
62
|
+
updateAvailable: true,
|
|
63
|
+
isDocker: false,
|
|
64
|
+
},
|
|
65
|
+
play: async ({ canvasElement }) => {
|
|
66
|
+
const canvas = within(canvasElement);
|
|
67
|
+
const banner = canvas.getByTestId("update-banner");
|
|
68
|
+
await expect(banner).toBeInTheDocument();
|
|
69
|
+
|
|
70
|
+
// Click dismiss — Callout hides its content via internal state
|
|
71
|
+
const dismissButton = canvas.getByLabelText("Dismiss");
|
|
72
|
+
await userEvent.click(dismissButton);
|
|
73
|
+
|
|
74
|
+
// After dismiss, the Callout renders empty — verify the info text is gone
|
|
75
|
+
await expect(canvas.queryByText(/0\.77\.0/)).toBeNull();
|
|
76
|
+
},
|
|
77
|
+
};
|