From a9a21877cbd40fcaca5ae646061562f08b8e555f Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Wed, 24 Jun 2026 10:25:00 +0100 Subject: [PATCH 1/8] rebase main Signed-off-by: Cheryl Kong --- .../src/react-flow/diagram/Diagram.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx index 145f54a..6ca6691 100644 --- a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx +++ b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx @@ -195,6 +195,12 @@ export const Diagram = ({ divRef, ref, colorMode = "light" }: DiagramProps) => { > M + setMinimapVisible(!minimapVisible)} + aria-label={minimapVisible ? "Hide minimap" : "Show minimap"} + > + M + From 4f2ff0f2dedcfa5f3db8e3d640d2116ce98c974b Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Mon, 29 Jun 2026 14:30:07 +0100 Subject: [PATCH 2/8] feat: add toast alert system Signed-off-by: Cheryl Kong --- .../src/components/ui/toast.tsx | 140 ++++++++++++++++++ .../src/diagram-editor/DiagramEditor.tsx | 5 + .../src/hooks/useToast.ts | 73 +++++++++ .../src/i18n/locales/en.ts | 3 + .../src/side-panel/MermaidActions.tsx | 16 +- 5 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx create mode 100644 packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts diff --git a/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx b/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx new file mode 100644 index 0000000..c727598 --- /dev/null +++ b/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx @@ -0,0 +1,140 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from "react"; +import { CircleAlert, CircleCheck, Info, TriangleAlert, XIcon } from "lucide-react"; +import { Toast as ToastPrimitive } from "radix-ui"; +import { cn } from "@/lib/utils"; +import { useToast } from "@/hooks/useToast"; + +type ToastProps = { + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + variant?: "default" | "error" | "success" | "warning" | "info"; + open: boolean; +}; + +const TOAST_VARIANT_STYLES_BASE = { + error: { + root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", + close: + "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", + icon: CircleAlert, + iconColor: "dec:text-red-500", + borderColor: "dec:border-l-red-500 dec:dark:border-l-red-500", + }, + success: { + root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", + close: + "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", + icon: CircleCheck, + iconColor: "dec:text-green-500", + borderColor: "dec:border-l-green-500 dec:dark:border-l-green-500", + }, + warning: { + root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", + close: + "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", + icon: TriangleAlert, + iconColor: "dec:text-orange-500", + borderColor: "dec:border-l-orange-500 dec:dark:border-l-orange-500", + }, + info: { + root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", + close: + "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", + icon: Info, + iconColor: "dec:text-blue-500", + borderColor: "dec:border-l-blue-500 dec:dark:border-l-blue-500", + }, +} as const; + +const TOAST_VARIANT_STYLES = { + ...TOAST_VARIANT_STYLES_BASE, + default: TOAST_VARIANT_STYLES_BASE.info, +} as const; + +const ToastItem = React.memo( + ({ toast, onDismiss }: { toast: ToastProps; onDismiss: (id: string) => void }) => { + const { id, open, variant = "default", title, description } = toast; + const styles = TOAST_VARIANT_STYLES[variant]; + const Icon = styles.icon; + + return ( + + {Icon && ( +
+ +
+ )} +
+ {title && ( + + {title} + + )} + {description && ( + + {description} + + )} +
+ onDismiss(id)} + className={cn( + "dec:-mt-0.5 dec:rounded-md dec:p-1 dec:opacity-100 dec:transition-colors", + styles.close, + )} + > + + +
+ ); + }, +); + +ToastItem.displayName = "ToastItem"; + +function Toaster() { + const { toasts, dismiss } = useToast(); + + return ( + <> + {toasts.map((toast) => ( + + ))} + + + ); +} + +export { Toaster }; diff --git a/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx b/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx index 4521b12..eb8c543 100644 --- a/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx +++ b/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx @@ -27,6 +27,8 @@ import { useResolvedColorMode } from "../hooks/useResolvedColorMode"; import { SidebarProvider } from "@/components/ui/sidebar"; import { SidePanel } from "@/side-panel/SidePanel"; import { DiagramEditorErrorBoundary } from "./error-pages/DiagramEditorErrorBoundary"; +import { Toaster } from "@/components/ui/toast"; +import { Toast as ToastPrimitive } from "radix-ui"; /** * DiagramEditor component API @@ -130,6 +132,9 @@ export const DiagramEditor = (props: DiagramEditorProps) => { }} + + + ); }; diff --git a/packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts b/packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts new file mode 100644 index 0000000..f5051ce --- /dev/null +++ b/packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from "react"; + +type ToastProps = { + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + variant?: "default" | "error" | "success" | "warning" | "info"; + open: boolean; +}; + +type Action = + | { type: "ADD"; toast: Omit } + | { type: "DISMISS"; id: string } + | { type: "REMOVE"; id: string }; + +let toasts: ToastProps[] = []; +let toastCount = 0; +const listeners: Array<(toasts: ToastProps[]) => void> = []; + +function dispatch(action: Action) { + if (action.type === "ADD") { + toasts = [...toasts, { ...action.toast, open: true }]; + setTimeout(() => dispatch({ type: "DISMISS", id: action.toast.id }), 5000); + } else if (action.type === "DISMISS") { + toasts = toasts.map((t) => (t.id === action.id ? { ...t, open: false } : t)); + setTimeout(() => dispatch({ type: "REMOVE", id: action.id }), 300); + } else if (action.type === "REMOVE") { + toasts = toasts.filter((t) => t.id !== action.id); + } + listeners.forEach((listener) => listener(toasts)); +} + +function toast(props: Omit) { + const id = String(toastCount++); + dispatch({ type: "ADD", toast: { ...props, id } }); + return id; +} + +function useToast() { + const [state, setState] = React.useState(toasts); + + React.useEffect(() => { + listeners.push(setState); + return () => { + const index = listeners.indexOf(setState); + if (index > -1) listeners.splice(index, 1); + }; + }, []); + + return { + toasts: state, + show: toast, + dismiss: (id: string) => dispatch({ type: "DISMISS", id }), + }; +} + +export { useToast, toast }; diff --git a/packages/serverless-workflow-diagram-editor/src/i18n/locales/en.ts b/packages/serverless-workflow-diagram-editor/src/i18n/locales/en.ts index bdd2d20..401cd0a 100644 --- a/packages/serverless-workflow-diagram-editor/src/i18n/locales/en.ts +++ b/packages/serverless-workflow-diagram-editor/src/i18n/locales/en.ts @@ -50,6 +50,9 @@ export const en = { "aria.panel.workflowInfo": "Workflow information panel", "aria.panel.content": "Panel content", "aria.panel.exportActions": "Export actions", + "toast.clipboard.error": "Failed to copy", + "toast.download.success": "Download started", + "toast.download.error": "Download failed", } as const; export type TranslationKeys = keyof typeof en; diff --git a/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx b/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx index 691fb7f..634dd06 100644 --- a/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx +++ b/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx @@ -22,6 +22,7 @@ import { exportToMermaid } from "@/core"; import { copyToClipboard } from "@/lib/clipboard"; import { downloadFile } from "@/lib/download"; import type { Specification } from "@serverlessworkflow/sdk"; +import { toast } from "@/hooks/useToast"; export function MermaidActions({ model }: { model: Specification.Workflow }): React.JSX.Element { const { t } = useI18n(); @@ -51,8 +52,11 @@ export function MermaidActions({ model }: { model: Specification.Workflow }): Re copyTimeoutRef.current = null; }, 2000); } catch (error) { - console.error("Failed to copy mermaid code:", error); - // TODO: Create component to show errors to users + toast({ + variant: "error", + title: t("toast.clipboard.error"), + description: error instanceof Error ? error.message : undefined, + }); } }; @@ -66,9 +70,13 @@ export function MermaidActions({ model }: { model: Specification.Workflow }): Re .substring(0, 200); const filename = `${sanitizedName}.mmd`; downloadFile(mermaidCode, filename); + toast({ variant: "success", title: t("toast.download.success") }); } catch (error) { - console.error("Failed to download mermaid file:", error); - // TODO: Create component to show errors to users + toast({ + variant: "error", + title: t("toast.download.error"), + description: error instanceof Error ? error.message : undefined, + }); } }; From b5b086aa8ab9db48ce9d6411b78e17bdbd63d69f Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Tue, 30 Jun 2026 09:56:31 +0100 Subject: [PATCH 3/8] test: update Mermaid Actions tests Signed-off-by: Cheryl Kong --- .../tests/side-panel/MermaidActions.test.tsx | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx b/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx index fbd5b0c..36880d9 100644 --- a/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx +++ b/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx @@ -24,8 +24,12 @@ import { WORKFLOW_WITH_METADATA_JSON } from "../fixtures/workflows"; import * as clipboard from "../../src/lib/clipboard"; import * as core from "../../src/core"; import * as download from "../../src/lib/download"; +import * as toastHook from "../../src/hooks/useToast"; describe("MermaidActions", () => { + const toastMock = vi.fn(); + const MERMAID_CODE = "mermaid code"; + afterEach(() => { vi.restoreAllMocks(); }); @@ -34,25 +38,84 @@ describe("MermaidActions", () => { const user = userEvent.setup(); const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON); const copySpy = vi.spyOn(clipboard, "copyToClipboard").mockResolvedValue(undefined); - vi.spyOn(core, "exportToMermaid").mockReturnValue("mermaid code"); + vi.spyOn(core, "exportToMermaid").mockReturnValue(MERMAID_CODE); renderWithProviders(, { model }); - const copyButton = screen.getByText(/Copy Mermaid Code/i); + + const copyButton = screen.getByRole("button", { + name: /Copy Mermaid Code/i, + }); + + await user.click(copyButton); + + expect(copySpy).toHaveBeenCalledWith(MERMAID_CODE); + }); + + it("should show error toast when clipboard copy fails", async () => { + const user = userEvent.setup(); + const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON); + vi.spyOn(clipboard, "copyToClipboard").mockRejectedValue(new Error("Clipboard error")); + vi.spyOn(core, "exportToMermaid").mockReturnValue(MERMAID_CODE); + vi.spyOn(toastHook, "toast").mockImplementation(toastMock); + + renderWithProviders(, { model }); + + const copyButton = screen.getByRole("button", { + name: /Copy Mermaid Code/i, + }); + await user.click(copyButton); - expect(copySpy).toHaveBeenCalledWith("mermaid code"); + expect(toastMock).toHaveBeenCalledWith({ + variant: "error", + title: expect.any(String), + description: "Clipboard error", + }); }); - it("should call downloadMermaidFile when download button is clicked", async () => { + it("should call downloadMermaidFile and show success toast when download button is clicked", async () => { const user = userEvent.setup(); const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON); const downloadSpy = vi.spyOn(download, "downloadFile").mockImplementation(() => {}); - vi.spyOn(core, "exportToMermaid").mockReturnValue("mermaid code"); + vi.spyOn(core, "exportToMermaid").mockReturnValue(MERMAID_CODE); + vi.spyOn(toastHook, "toast").mockImplementation(toastMock); renderWithProviders(, { model }); - const downloadButton = screen.getByText(/Download as Mermaid File/i); + + const downloadButton = screen.getByRole("button", { + name: /Download as Mermaid File/i, + }); + + await user.click(downloadButton); + + expect(downloadSpy).toHaveBeenCalledWith(MERMAID_CODE, "test-wf.mmd"); + expect(toastMock).toHaveBeenCalledWith({ + variant: "success", + title: expect.any(String), + }); + }); + + it("should show error toast when download fails", async () => { + const user = userEvent.setup(); + const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON); + vi.spyOn(download, "downloadFile").mockImplementation(() => { + throw new Error("Download error"); + }); + vi.spyOn(core, "exportToMermaid").mockReturnValue(MERMAID_CODE); + vi.spyOn(toastHook, "toast").mockImplementation(toastMock); + + renderWithProviders(, { model }); + + const downloadButton = screen.getByRole("button", { + name: /Download as Mermaid File/i, + }); + await user.click(downloadButton); - expect(downloadSpy).toHaveBeenCalledWith("mermaid code", "test-wf.mmd"); + expect(toastMock).toHaveBeenCalledWith({ + variant: "error", + title: expect.any(String), + description: "Download error", + }); }); }); From e54643103c54a820cdf524b33912d261a3c0f315 Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Tue, 30 Jun 2026 10:26:39 +0100 Subject: [PATCH 4/8] add changeset Signed-off-by: Cheryl Kong --- .changeset/toast-alerts.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/toast-alerts.md diff --git a/.changeset/toast-alerts.md b/.changeset/toast-alerts.md new file mode 100644 index 0000000..9a15f82 --- /dev/null +++ b/.changeset/toast-alerts.md @@ -0,0 +1,5 @@ +--- +"@serverlessworkflow/diagram-editor": minor +--- + +add toast notification system From 5a46120048217c1c9addd476d6a4fd47125894bf Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Tue, 30 Jun 2026 10:43:58 +0100 Subject: [PATCH 5/8] fix copilot complaints Signed-off-by: Cheryl Kong --- .../src/components/ui/toast.tsx | 3 +++ .../src/react-flow/diagram/Diagram.tsx | 6 ------ .../tests/side-panel/MermaidActions.test.tsx | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx b/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx index c727598..da44922 100644 --- a/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx +++ b/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx @@ -77,6 +77,9 @@ const ToastItem = React.memo( return ( { + if (!nextOpen) onDismiss(id); + }} data-slot="toast" className={cn( "dec:pointer-events-auto dec:relative dec:flex dec:w-full dec:min-w-[320px] dec:max-w-[680px] dec:items-start dec:gap-4 dec:rounded-[14px] dec:border dec:border-l-4 dec:px-7 dec:py-6 dec:shadow-[0_18px_38px_rgba(22,23,24,0.12),0_8px_16px_rgba(22,23,24,0.08)] dec:transition-all dec:data-[state=open]:animate-in dec:data-[state=closed]:animate-out dec:data-[state=closed]:fade-out-80 dec:data-[state=closed]:slide-out-to-left-full dec:data-[state=open]:slide-in-from-top-full", diff --git a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx index 6ca6691..145f54a 100644 --- a/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx +++ b/packages/serverless-workflow-diagram-editor/src/react-flow/diagram/Diagram.tsx @@ -195,12 +195,6 @@ export const Diagram = ({ divRef, ref, colorMode = "light" }: DiagramProps) => { > M - setMinimapVisible(!minimapVisible)} - aria-label={minimapVisible ? "Hide minimap" : "Show minimap"} - > - M - diff --git a/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx b/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx index 36880d9..4e806e0 100644 --- a/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx +++ b/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx @@ -31,6 +31,7 @@ describe("MermaidActions", () => { const MERMAID_CODE = "mermaid code"; afterEach(() => { + toastMock.mockClear(); vi.restoreAllMocks(); }); From 7c82c145833a1570e8799337aa7df7cea55b7876 Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Thu, 2 Jul 2026 10:46:55 +0100 Subject: [PATCH 6/8] feat: add Sonner Signed-off-by: Cheryl Kong --- .../{toast-alerts.md => toast-alert.md} | 2 +- .../package.json | 2 + .../src/components/ui/sonner.css | 39 +++++ .../src/components/ui/sonner.tsx | 39 +++++ .../src/components/ui/toast.tsx | 143 ------------------ .../src/diagram-editor/DiagramEditor.tsx | 7 +- .../src/hooks/useToast.ts | 73 --------- .../src/side-panel/MermaidActions.tsx | 12 +- .../tests/side-panel/MermaidActions.test.tsx | 28 ++-- pnpm-lock.yaml | 28 ++++ pnpm-workspace.yaml | 2 + 11 files changed, 127 insertions(+), 248 deletions(-) rename .changeset/{toast-alerts.md => toast-alert.md} (54%) create mode 100644 packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css create mode 100644 packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx delete mode 100644 packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx delete mode 100644 packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts diff --git a/.changeset/toast-alerts.md b/.changeset/toast-alert.md similarity index 54% rename from .changeset/toast-alerts.md rename to .changeset/toast-alert.md index 9a15f82..5979db0 100644 --- a/.changeset/toast-alerts.md +++ b/.changeset/toast-alert.md @@ -2,4 +2,4 @@ "@serverlessworkflow/diagram-editor": minor --- -add toast notification system +add toast alert system through shadcn Sonner diff --git a/packages/serverless-workflow-diagram-editor/package.json b/packages/serverless-workflow-diagram-editor/package.json index 98e3422..f2f99c8 100644 --- a/packages/serverless-workflow-diagram-editor/package.json +++ b/packages/serverless-workflow-diagram-editor/package.json @@ -48,7 +48,9 @@ "clsx": "catalog:", "elkjs": "catalog:", "js-yaml": "catalog:", + "next-themes": "catalog:", "radix-ui": "catalog:", + "sonner": "catalog:", "use-sync-external-store": "catalog:" }, "devDependencies": { diff --git a/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css new file mode 100644 index 0000000..015ab22 --- /dev/null +++ b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css @@ -0,0 +1,39 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@layer sonner { + [data-sonner-toaster] { --width: 300px !important; } + [data-sonner-toast] { background: #ffffff !important; border-radius: 10px !important; border: 1px solid #e9edf4 !important; } + [data-sonner-toast][data-type="success"] { border-left: 4px solid #22c55e !important; } + [data-sonner-toast][data-type="error"] { border-left: 4px solid #ef4444 !important; } + [data-sonner-toast][data-type="warning"] { border-left: 4px solid #f97316 !important; } + [data-sonner-toast][data-type="info"] { border-left: 4px solid #3b82f6 !important; } + .dark [data-sonner-toast] { background: #2d3748 !important; color: #ffffff !important; border-top-color: #4a5568 !important; border-right-color: #4a5568 !important; border-bottom-color: #4a5568 !important; } + .dark [data-sonner-toast] [data-title], .dark [data-sonner-toast] [data-description] { color: #ffffff !important; } + [data-sonner-toast] [data-close-button] { + position: absolute !important; + top: 50% !important; + right: 12px !important; + left: auto !important; + transform: translateY(-50%) !important; + background: transparent !important; + border: none !important; + box-shadow: none !important; + color: #6b7280; + cursor: pointer; + } + .dark [data-sonner-toast] [data-close-button] { color: #ffffff; } +} diff --git a/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx new file mode 100644 index 0000000..739da8a --- /dev/null +++ b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx @@ -0,0 +1,39 @@ +import { + CircleCheckIcon, + InfoIcon, + Loader2Icon, + OctagonXIcon, + TriangleAlertIcon, + XIcon, +} from "lucide-react"; +import { Toaster as Sonner, type ToasterProps } from "sonner"; +import "./sonner.css"; + +const Toaster = ({ ...props }: ToasterProps) => { + return ( + , + info: , + warning: , + error: , + loading: , + close: , + }} + style={ + { + "--normal-bg": "var(--popover)", + "--normal-text": "var(--popover-foreground)", + "--normal-border": "var(--border)", + "--border-radius": "var(--radius)", + } as React.CSSProperties + } + {...props} + /> + ); +}; + +export { Toaster }; diff --git a/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx b/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx deleted file mode 100644 index da44922..0000000 --- a/packages/serverless-workflow-diagram-editor/src/components/ui/toast.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2021-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as React from "react"; -import { CircleAlert, CircleCheck, Info, TriangleAlert, XIcon } from "lucide-react"; -import { Toast as ToastPrimitive } from "radix-ui"; -import { cn } from "@/lib/utils"; -import { useToast } from "@/hooks/useToast"; - -type ToastProps = { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - variant?: "default" | "error" | "success" | "warning" | "info"; - open: boolean; -}; - -const TOAST_VARIANT_STYLES_BASE = { - error: { - root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", - close: - "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", - icon: CircleAlert, - iconColor: "dec:text-red-500", - borderColor: "dec:border-l-red-500 dec:dark:border-l-red-500", - }, - success: { - root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", - close: - "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", - icon: CircleCheck, - iconColor: "dec:text-green-500", - borderColor: "dec:border-l-green-500 dec:dark:border-l-green-500", - }, - warning: { - root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", - close: - "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", - icon: TriangleAlert, - iconColor: "dec:text-orange-500", - borderColor: "dec:border-l-orange-500 dec:dark:border-l-orange-500", - }, - info: { - root: "dec:border-gray-200 dec:bg-white dec:text-gray-900 dec:dark:border-gray-700 dec:dark:bg-[#2d3748] dec:dark:text-gray-100", - close: - "dec:text-gray-600 hover:dec:text-gray-900 dec:dark:text-gray-400 dec:dark:hover:text-gray-100", - icon: Info, - iconColor: "dec:text-blue-500", - borderColor: "dec:border-l-blue-500 dec:dark:border-l-blue-500", - }, -} as const; - -const TOAST_VARIANT_STYLES = { - ...TOAST_VARIANT_STYLES_BASE, - default: TOAST_VARIANT_STYLES_BASE.info, -} as const; - -const ToastItem = React.memo( - ({ toast, onDismiss }: { toast: ToastProps; onDismiss: (id: string) => void }) => { - const { id, open, variant = "default", title, description } = toast; - const styles = TOAST_VARIANT_STYLES[variant]; - const Icon = styles.icon; - - return ( - { - if (!nextOpen) onDismiss(id); - }} - data-slot="toast" - className={cn( - "dec:pointer-events-auto dec:relative dec:flex dec:w-full dec:min-w-[320px] dec:max-w-[680px] dec:items-start dec:gap-4 dec:rounded-[14px] dec:border dec:border-l-4 dec:px-7 dec:py-6 dec:shadow-[0_18px_38px_rgba(22,23,24,0.12),0_8px_16px_rgba(22,23,24,0.08)] dec:transition-all dec:data-[state=open]:animate-in dec:data-[state=closed]:animate-out dec:data-[state=closed]:fade-out-80 dec:data-[state=closed]:slide-out-to-left-full dec:data-[state=open]:slide-in-from-top-full", - styles.root, - styles.borderColor, - )} - > - {Icon && ( -
- -
- )} -
- {title && ( - - {title} - - )} - {description && ( - - {description} - - )} -
- onDismiss(id)} - className={cn( - "dec:-mt-0.5 dec:rounded-md dec:p-1 dec:opacity-100 dec:transition-colors", - styles.close, - )} - > - - -
- ); - }, -); - -ToastItem.displayName = "ToastItem"; - -function Toaster() { - const { toasts, dismiss } = useToast(); - - return ( - <> - {toasts.map((toast) => ( - - ))} - - - ); -} - -export { Toaster }; diff --git a/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx b/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx index eb8c543..8271c80 100644 --- a/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx +++ b/packages/serverless-workflow-diagram-editor/src/diagram-editor/DiagramEditor.tsx @@ -27,8 +27,7 @@ import { useResolvedColorMode } from "../hooks/useResolvedColorMode"; import { SidebarProvider } from "@/components/ui/sidebar"; import { SidePanel } from "@/side-panel/SidePanel"; import { DiagramEditorErrorBoundary } from "./error-pages/DiagramEditorErrorBoundary"; -import { Toaster } from "@/components/ui/toast"; -import { Toast as ToastPrimitive } from "radix-ui"; +import { Toaster } from "@/components/ui/sonner"; /** * DiagramEditor component API @@ -132,9 +131,7 @@ export const DiagramEditor = (props: DiagramEditorProps) => { }} - - - + ); }; diff --git a/packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts b/packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts deleted file mode 100644 index f5051ce..0000000 --- a/packages/serverless-workflow-diagram-editor/src/hooks/useToast.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2021-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as React from "react"; - -type ToastProps = { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - variant?: "default" | "error" | "success" | "warning" | "info"; - open: boolean; -}; - -type Action = - | { type: "ADD"; toast: Omit } - | { type: "DISMISS"; id: string } - | { type: "REMOVE"; id: string }; - -let toasts: ToastProps[] = []; -let toastCount = 0; -const listeners: Array<(toasts: ToastProps[]) => void> = []; - -function dispatch(action: Action) { - if (action.type === "ADD") { - toasts = [...toasts, { ...action.toast, open: true }]; - setTimeout(() => dispatch({ type: "DISMISS", id: action.toast.id }), 5000); - } else if (action.type === "DISMISS") { - toasts = toasts.map((t) => (t.id === action.id ? { ...t, open: false } : t)); - setTimeout(() => dispatch({ type: "REMOVE", id: action.id }), 300); - } else if (action.type === "REMOVE") { - toasts = toasts.filter((t) => t.id !== action.id); - } - listeners.forEach((listener) => listener(toasts)); -} - -function toast(props: Omit) { - const id = String(toastCount++); - dispatch({ type: "ADD", toast: { ...props, id } }); - return id; -} - -function useToast() { - const [state, setState] = React.useState(toasts); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) listeners.splice(index, 1); - }; - }, []); - - return { - toasts: state, - show: toast, - dismiss: (id: string) => dispatch({ type: "DISMISS", id }), - }; -} - -export { useToast, toast }; diff --git a/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx b/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx index 634dd06..a7dc155 100644 --- a/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx +++ b/packages/serverless-workflow-diagram-editor/src/side-panel/MermaidActions.tsx @@ -22,7 +22,7 @@ import { exportToMermaid } from "@/core"; import { copyToClipboard } from "@/lib/clipboard"; import { downloadFile } from "@/lib/download"; import type { Specification } from "@serverlessworkflow/sdk"; -import { toast } from "@/hooks/useToast"; +import { toast } from "sonner"; export function MermaidActions({ model }: { model: Specification.Workflow }): React.JSX.Element { const { t } = useI18n(); @@ -52,9 +52,7 @@ export function MermaidActions({ model }: { model: Specification.Workflow }): Re copyTimeoutRef.current = null; }, 2000); } catch (error) { - toast({ - variant: "error", - title: t("toast.clipboard.error"), + toast.error(t("toast.clipboard.error"), { description: error instanceof Error ? error.message : undefined, }); } @@ -70,11 +68,9 @@ export function MermaidActions({ model }: { model: Specification.Workflow }): Re .substring(0, 200); const filename = `${sanitizedName}.mmd`; downloadFile(mermaidCode, filename); - toast({ variant: "success", title: t("toast.download.success") }); + toast.success(t("toast.download.success")); } catch (error) { - toast({ - variant: "error", - title: t("toast.download.error"), + toast.error(t("toast.download.error"), { description: error instanceof Error ? error.message : undefined, }); } diff --git a/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx b/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx index 4e806e0..8d3f797 100644 --- a/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx +++ b/packages/serverless-workflow-diagram-editor/tests/side-panel/MermaidActions.test.tsx @@ -24,7 +24,7 @@ import { WORKFLOW_WITH_METADATA_JSON } from "../fixtures/workflows"; import * as clipboard from "../../src/lib/clipboard"; import * as core from "../../src/core"; import * as download from "../../src/lib/download"; -import * as toastHook from "../../src/hooks/useToast"; +import * as sonner from "sonner"; describe("MermaidActions", () => { const toastMock = vi.fn(); @@ -57,7 +57,8 @@ describe("MermaidActions", () => { const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON); vi.spyOn(clipboard, "copyToClipboard").mockRejectedValue(new Error("Clipboard error")); vi.spyOn(core, "exportToMermaid").mockReturnValue(MERMAID_CODE); - vi.spyOn(toastHook, "toast").mockImplementation(toastMock); + vi.spyOn(sonner.toast, "error").mockImplementation(toastMock); + vi.spyOn(sonner.toast, "success").mockImplementation(toastMock); renderWithProviders(, { model }); @@ -67,11 +68,7 @@ describe("MermaidActions", () => { await user.click(copyButton); - expect(toastMock).toHaveBeenCalledWith({ - variant: "error", - title: expect.any(String), - description: "Clipboard error", - }); + expect(toastMock).toHaveBeenCalledWith(expect.any(String), { description: "Clipboard error" }); }); it("should call downloadMermaidFile and show success toast when download button is clicked", async () => { @@ -79,7 +76,8 @@ describe("MermaidActions", () => { const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON); const downloadSpy = vi.spyOn(download, "downloadFile").mockImplementation(() => {}); vi.spyOn(core, "exportToMermaid").mockReturnValue(MERMAID_CODE); - vi.spyOn(toastHook, "toast").mockImplementation(toastMock); + vi.spyOn(sonner.toast, "error").mockImplementation(toastMock); + vi.spyOn(sonner.toast, "success").mockImplementation(toastMock); renderWithProviders(, { model }); @@ -90,10 +88,7 @@ describe("MermaidActions", () => { await user.click(downloadButton); expect(downloadSpy).toHaveBeenCalledWith(MERMAID_CODE, "test-wf.mmd"); - expect(toastMock).toHaveBeenCalledWith({ - variant: "success", - title: expect.any(String), - }); + expect(toastMock).toHaveBeenCalledWith(expect.any(String)); }); it("should show error toast when download fails", async () => { @@ -103,7 +98,8 @@ describe("MermaidActions", () => { throw new Error("Download error"); }); vi.spyOn(core, "exportToMermaid").mockReturnValue(MERMAID_CODE); - vi.spyOn(toastHook, "toast").mockImplementation(toastMock); + vi.spyOn(sonner.toast, "error").mockImplementation(toastMock); + vi.spyOn(sonner.toast, "success").mockImplementation(toastMock); renderWithProviders(, { model }); @@ -113,10 +109,6 @@ describe("MermaidActions", () => { await user.click(downloadButton); - expect(toastMock).toHaveBeenCalledWith({ - variant: "error", - title: expect.any(String), - description: "Download error", - }); + expect(toastMock).toHaveBeenCalledWith(expect.any(String), { description: "Download error" }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f9083f..62df7c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,9 +227,15 @@ importers: js-yaml: specifier: 'catalog:' version: 4.2.0 + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) radix-ui: specifier: 'catalog:' version: 1.6.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) use-sync-external-store: specifier: 'catalog:' version: 1.6.0(react@19.2.7) @@ -3220,6 +3226,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -3562,6 +3574,12 @@ packages: resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} engines: {node: '>=20'} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -6684,6 +6702,11 @@ snapshots: nanoid@3.3.12: {} + next-themes@0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -7117,6 +7140,11 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + sonner@2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + source-map-js@1.2.1: {} source-map@0.6.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 095aab4..c152fec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -31,6 +31,7 @@ catalog: "js-yaml": ^4.2.0 jsdom: ^29.1.0 lucide-react: ^1.21.0 + next-themes: ^0.4.6 lint-staged: ^17.0.8 oxfmt: ^0.55.0 oxlint: ^1.70.0 @@ -38,6 +39,7 @@ catalog: react: ^19.2.7 react-dom: ^19.2.7 rimraf: ^6.1.3 + sonner: ^2.0.7 storybook: ^10.4.6 syncpack: ^15.3.2 tailwindcss: ^4.3.1 From 383dc9d5e215c197b4b0d726407c850accec6ea5 Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Thu, 2 Jul 2026 11:03:50 +0100 Subject: [PATCH 7/8] fix copilot complaints Signed-off-by: Cheryl Kong --- .../package.json | 1 - .../src/components/ui/sonner.css | 20 +++++++++---------- pnpm-lock.yaml | 19 ++++-------------- pnpm-workspace.yaml | 1 - 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/packages/serverless-workflow-diagram-editor/package.json b/packages/serverless-workflow-diagram-editor/package.json index f2f99c8..8b16b01 100644 --- a/packages/serverless-workflow-diagram-editor/package.json +++ b/packages/serverless-workflow-diagram-editor/package.json @@ -48,7 +48,6 @@ "clsx": "catalog:", "elkjs": "catalog:", "js-yaml": "catalog:", - "next-themes": "catalog:", "radix-ui": "catalog:", "sonner": "catalog:", "use-sync-external-store": "catalog:" diff --git a/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css index 015ab22..bb1bca6 100644 --- a/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css +++ b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.css @@ -15,15 +15,15 @@ */ @layer sonner { - [data-sonner-toaster] { --width: 300px !important; } - [data-sonner-toast] { background: #ffffff !important; border-radius: 10px !important; border: 1px solid #e9edf4 !important; } - [data-sonner-toast][data-type="success"] { border-left: 4px solid #22c55e !important; } - [data-sonner-toast][data-type="error"] { border-left: 4px solid #ef4444 !important; } - [data-sonner-toast][data-type="warning"] { border-left: 4px solid #f97316 !important; } - [data-sonner-toast][data-type="info"] { border-left: 4px solid #3b82f6 !important; } - .dark [data-sonner-toast] { background: #2d3748 !important; color: #ffffff !important; border-top-color: #4a5568 !important; border-right-color: #4a5568 !important; border-bottom-color: #4a5568 !important; } - .dark [data-sonner-toast] [data-title], .dark [data-sonner-toast] [data-description] { color: #ffffff !important; } - [data-sonner-toast] [data-close-button] { + .dec-root [data-sonner-toaster] { --width: 300px !important; } + .dec-root [data-sonner-toast] { background: #ffffff !important; border-radius: 10px !important; border: 1px solid #e9edf4 !important; } + .dec-root [data-sonner-toast][data-type="success"] { border-left: 4px solid #22c55e !important; } + .dec-root [data-sonner-toast][data-type="error"] { border-left: 4px solid #ef4444 !important; } + .dec-root [data-sonner-toast][data-type="warning"] { border-left: 4px solid #f97316 !important; } + .dec-root [data-sonner-toast][data-type="info"] { border-left: 4px solid #3b82f6 !important; } + .dec-root.dark [data-sonner-toast] { background: #2d3748 !important; color: #ffffff !important; border-top-color: #4a5568 !important; border-right-color: #4a5568 !important; border-bottom-color: #4a5568 !important; } + .dec-root.dark [data-sonner-toast] [data-title], .dec-root.dark [data-sonner-toast] [data-description] { color: #ffffff !important; } + .dec-root [data-sonner-toast] [data-close-button] { position: absolute !important; top: 50% !important; right: 12px !important; @@ -35,5 +35,5 @@ color: #6b7280; cursor: pointer; } - .dark [data-sonner-toast] [data-close-button] { color: #ffffff; } + .dec-root.dark [data-sonner-toast] [data-close-button] { color: #ffffff; } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62df7c5..e1cd390 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,9 @@ catalogs: rimraf: specifier: ^6.1.3 version: 6.1.3 + sonner: + specifier: ^2.0.7 + version: 2.0.7 storybook: specifier: ^10.4.6 version: 10.4.6 @@ -227,14 +230,11 @@ importers: js-yaml: specifier: 'catalog:' version: 4.2.0 - next-themes: - specifier: ^0.4.6 - version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) radix-ui: specifier: 'catalog:' version: 1.6.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) sonner: - specifier: ^2.0.7 + specifier: 'catalog:' version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) use-sync-external-store: specifier: 'catalog:' @@ -3226,12 +3226,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - next-themes@0.4.6: - resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} - peerDependencies: - react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -6702,11 +6696,6 @@ snapshots: nanoid@3.3.12: {} - next-themes@0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c152fec..b38b2b2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -31,7 +31,6 @@ catalog: "js-yaml": ^4.2.0 jsdom: ^29.1.0 lucide-react: ^1.21.0 - next-themes: ^0.4.6 lint-staged: ^17.0.8 oxfmt: ^0.55.0 oxlint: ^1.70.0 From e7f87ebd6ef04b9265b281fb693b1a54a3c21279 Mon Sep 17 00:00:00 2001 From: Cheryl Kong Date: Thu, 2 Jul 2026 11:06:23 +0100 Subject: [PATCH 8/8] fix: add license header Signed-off-by: Cheryl Kong --- .../src/components/ui/sonner.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx index 739da8a..de31add 100644 --- a/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx +++ b/packages/serverless-workflow-diagram-editor/src/components/ui/sonner.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { CircleCheckIcon, InfoIcon,