Skip to content

Commit a559239

Browse files
authored
fix: Toast copy text (#5746)
1 parent 6b09e91 commit a559239

3 files changed

Lines changed: 66 additions & 4 deletions

File tree

apps/builder/app/shared/notifications/subscription.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export const startSubscription = () => {
122122
const NEW_VERSION_TOAST_ID = "new-builder-version";
123123
manager.subscribe("builderVersion", (serverVersion) => {
124124
if (serverVersion !== publicStaticEnv.VERSION) {
125+
const message =
126+
"A new version of Webstudio is available. Reload to get the latest - see what's new at https://wstd.us/changelog";
125127
toast.info(
126128
<>
127129
A new version of Webstudio is available. Reload to get the latest —
@@ -134,7 +136,11 @@ export const startSubscription = () => {
134136
wstd.us/changelog
135137
</Link>
136138
</>,
137-
{ id: NEW_VERSION_TOAST_ID, duration: Number.POSITIVE_INFINITY }
139+
{
140+
id: NEW_VERSION_TOAST_ID,
141+
duration: Number.POSITIVE_INFINITY,
142+
copyText: message,
143+
}
138144
);
139145
}
140146
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, test } from "vitest";
2+
import { __testingToast__ } from "./toast";
3+
4+
const { getTextContent } = __testingToast__;
5+
6+
describe("getTextContent", () => {
7+
test("returns string toast content as-is", () => {
8+
expect(getTextContent("Project saved successfully")).toBe(
9+
"Project saved successfully"
10+
);
11+
});
12+
13+
test("extracts text from jsx toast content", () => {
14+
expect(
15+
getTextContent(
16+
<>
17+
A new version of Webstudio is available. Reload to get the latest -
18+
see what&apos;s new at{" "}
19+
<a href="https://wstd.us/changelog">wstd.us/changelog</a>
20+
</>
21+
)
22+
).toBe(
23+
"A new version of Webstudio is available. Reload to get the latest - see what's new at wstd.us/changelog"
24+
);
25+
});
26+
});

packages/design-system/src/components/toast.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { JSX } from "react";
1+
import { Children, isValidElement, type JSX } from "react";
22
import * as ToastPrimitive from "@radix-ui/react-toast";
33
import hotToast, {
44
resolveValue,
@@ -343,6 +343,26 @@ const mapToVariant: Record<HotToast["type"], ToastVariant> = {
343343
custom: "warning",
344344
};
345345

346+
const getTextContent = (node: React.ReactNode): string => {
347+
if (node === undefined || node === null || typeof node === "boolean") {
348+
return "";
349+
}
350+
351+
if (typeof node === "string" || typeof node === "number") {
352+
return String(node);
353+
}
354+
355+
if (Array.isArray(node)) {
356+
return node.map(getTextContent).join("");
357+
}
358+
359+
if (isValidElement<{ children?: React.ReactNode }>(node)) {
360+
return getTextContent(node.props.children);
361+
}
362+
363+
return Children.toArray(node).map(getTextContent).join("");
364+
};
365+
346366
export const Toaster = () => {
347367
const { toasts, handlers } = useToaster();
348368
const { startPause, endPause } = handlers;
@@ -352,6 +372,10 @@ export const Toaster = () => {
352372
{toasts.map((toastData) => {
353373
const toastVariant = mapToVariant[toastData.type];
354374
const children = resolveValue(toastData.message, toastData);
375+
const copyText =
376+
"copyText" in toastData && typeof toastData.copyText === "string"
377+
? toastData.copyText
378+
: getTextContent(children);
355379

356380
return (
357381
<AnimatedToast
@@ -366,7 +390,7 @@ export const Toaster = () => {
366390
hotToast.remove(toastData.id);
367391
}}
368392
onCopy={() => {
369-
navigator.clipboard.writeText(children?.toString() ?? "");
393+
navigator.clipboard.writeText(copyText);
370394
}}
371395
icon={toastData.icon}
372396
>
@@ -380,7 +404,9 @@ export const Toaster = () => {
380404
);
381405
};
382406

383-
type Options = Pick<ToastOptions, "duration" | "id" | "icon">;
407+
type Options = Pick<ToastOptions, "duration" | "id" | "icon"> & {
408+
copyText?: string;
409+
};
384410

385411
export const toast = {
386412
info: (value: JSX.Element | string, options?: Options) =>
@@ -393,3 +419,7 @@ export const toast = {
393419
hotToast.success(value, options),
394420
dismiss: hotToast.dismiss,
395421
};
422+
423+
export const __testingToast__ = {
424+
getTextContent,
425+
};

0 commit comments

Comments
 (0)