Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/builder/app/shared/notifications/subscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ export const startSubscription = () => {
const NEW_VERSION_TOAST_ID = "new-builder-version";
manager.subscribe("builderVersion", (serverVersion) => {
if (serverVersion !== publicStaticEnv.VERSION) {
const message =
"A new version of Webstudio is available. Reload to get the latest - see what's new at https://wstd.us/changelog";
toast.info(
<>
A new version of Webstudio is available. Reload to get the latest —
Expand All @@ -134,7 +136,11 @@ export const startSubscription = () => {
wstd.us/changelog
</Link>
</>,
{ id: NEW_VERSION_TOAST_ID, duration: Number.POSITIVE_INFINITY }
{
id: NEW_VERSION_TOAST_ID,
duration: Number.POSITIVE_INFINITY,
copyText: message,
}
);
}
});
Expand Down
26 changes: 26 additions & 0 deletions packages/design-system/src/components/toast.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, test } from "vitest";
import { __testingToast__ } from "./toast";

const { getTextContent } = __testingToast__;

describe("getTextContent", () => {
test("returns string toast content as-is", () => {
expect(getTextContent("Project saved successfully")).toBe(
"Project saved successfully"
);
});

test("extracts text from jsx toast content", () => {
expect(
getTextContent(
<>
A new version of Webstudio is available. Reload to get the latest -
see what&apos;s new at{" "}
<a href="https://wstd.us/changelog">wstd.us/changelog</a>
</>
)
).toBe(
"A new version of Webstudio is available. Reload to get the latest - see what's new at wstd.us/changelog"
);
});
});
36 changes: 33 additions & 3 deletions packages/design-system/src/components/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JSX } from "react";
import { Children, isValidElement, type JSX } from "react";
import * as ToastPrimitive from "@radix-ui/react-toast";
import hotToast, {
resolveValue,
Expand Down Expand Up @@ -343,6 +343,26 @@ const mapToVariant: Record<HotToast["type"], ToastVariant> = {
custom: "warning",
};

const getTextContent = (node: React.ReactNode): string => {
if (node === undefined || node === null || typeof node === "boolean") {
return "";
}

if (typeof node === "string" || typeof node === "number") {
return String(node);
}

if (Array.isArray(node)) {
return node.map(getTextContent).join("");
}

if (isValidElement<{ children?: React.ReactNode }>(node)) {
return getTextContent(node.props.children);
}

return Children.toArray(node).map(getTextContent).join("");
};

export const Toaster = () => {
const { toasts, handlers } = useToaster();
const { startPause, endPause } = handlers;
Expand All @@ -352,6 +372,10 @@ export const Toaster = () => {
{toasts.map((toastData) => {
const toastVariant = mapToVariant[toastData.type];
const children = resolveValue(toastData.message, toastData);
const copyText =
"copyText" in toastData && typeof toastData.copyText === "string"
? toastData.copyText
: getTextContent(children);

return (
<AnimatedToast
Expand All @@ -366,7 +390,7 @@ export const Toaster = () => {
hotToast.remove(toastData.id);
}}
onCopy={() => {
navigator.clipboard.writeText(children?.toString() ?? "");
navigator.clipboard.writeText(copyText);
}}
icon={toastData.icon}
>
Expand All @@ -380,7 +404,9 @@ export const Toaster = () => {
);
};

type Options = Pick<ToastOptions, "duration" | "id" | "icon">;
type Options = Pick<ToastOptions, "duration" | "id" | "icon"> & {
copyText?: string;
};

export const toast = {
info: (value: JSX.Element | string, options?: Options) =>
Expand All @@ -393,3 +419,7 @@ export const toast = {
hotToast.success(value, options),
dismiss: hotToast.dismiss,
};

export const __testingToast__ = {
getTextContent,
};
Loading