Skip to content

Commit d7af396

Browse files
authored
fix: DNS copy buttons don't trigger/show tooltips like "Copied" (#4160)
## Description closes #4152 ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent 329ddea commit d7af396

File tree

4 files changed

+87
-58
lines changed

4 files changed

+87
-58
lines changed

apps/builder/app/builder/features/topbar/domains.tsx

+29-19
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { $publisherHost } from "~/shared/nano-states";
3636
import { extractCname } from "./cname";
3737
import { useEffectEvent } from "~/shared/hook-utils/effect-event";
3838
import DomainCheckbox from "./domain-checkbox";
39+
import { CopyToClipboard } from "~/builder/shared/copy-to-clipboard";
3940

4041
export type Domain = Project["domainsVirtual"][number];
4142

@@ -45,21 +46,6 @@ const InputEllipsis = styled(InputField, {
4546
},
4647
});
4748

48-
const CopyToClipboard = (props: { text: string }) => {
49-
return (
50-
<Tooltip content={"Copy to clipboard"}>
51-
<NestedInputButton
52-
type="button"
53-
onClick={() => {
54-
navigator.clipboard.writeText(props.text);
55-
}}
56-
>
57-
<CopyIcon />
58-
</NestedInputButton>
59-
</Tooltip>
60-
);
61-
};
62-
6349
export const getStatus = (projectDomain: Domain) =>
6450
projectDomain.verified
6551
? (`VERIFIED_${projectDomain.status}` as const)
@@ -456,24 +442,48 @@ const DomainItem = (props: {
456442
<InputEllipsis
457443
readOnly
458444
value={cnameRecord.host}
459-
suffix={<CopyToClipboard text={cnameRecord.host} />}
445+
suffix={
446+
<CopyToClipboard text={cnameRecord.host}>
447+
<NestedInputButton type="button">
448+
<CopyIcon />
449+
</NestedInputButton>
450+
</CopyToClipboard>
451+
}
460452
/>
461453
<InputEllipsis
462454
readOnly
463455
value={cnameRecord.value}
464-
suffix={<CopyToClipboard text={cnameRecord.value} />}
456+
suffix={
457+
<CopyToClipboard text={cnameRecord.value}>
458+
<NestedInputButton type="button">
459+
<CopyIcon />
460+
</NestedInputButton>
461+
</CopyToClipboard>
462+
}
465463
/>
466464

467465
<InputEllipsis readOnly value="TXT" />
468466
<InputEllipsis
469467
readOnly
470468
value={txtRecord.host}
471-
suffix={<CopyToClipboard text={txtRecord.host} />}
469+
suffix={
470+
<CopyToClipboard text={txtRecord.host}>
471+
<NestedInputButton type="button">
472+
<CopyIcon />
473+
</NestedInputButton>
474+
</CopyToClipboard>
475+
}
472476
/>
473477
<InputEllipsis
474478
readOnly
475479
value={txtRecord.value}
476-
suffix={<CopyToClipboard text={txtRecord.value} />}
480+
suffix={
481+
<CopyToClipboard text={txtRecord.value}>
482+
<NestedInputButton type="button">
483+
<CopyIcon />
484+
</NestedInputButton>
485+
</CopyToClipboard>
486+
}
477487
/>
478488
</Grid>
479489

apps/builder/app/builder/features/topbar/publish.tsx

+7-17
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { isFeatureEnabled } from "@webstudio-is/feature-flags";
5656
import type { Templates } from "@webstudio-is/sdk";
5757
import { formatDistance } from "date-fns/formatDistance";
5858
import DomainCheckbox, { domainToPublishName } from "./domain-checkbox";
59+
import { CopyToClipboard } from "~/builder/shared/copy-to-clipboard";
5960

6061
type ChangeProjectDomainProps = {
6162
project: Project;
@@ -778,18 +779,11 @@ const ExportContent = (props: { projectId: Project["id"] }) => {
778779
value={npxCommand}
779780
/>
780781

781-
<Tooltip content={"Copy to clipboard"}>
782-
<Button
783-
type="button"
784-
color="neutral"
785-
onClick={() => {
786-
navigator.clipboard.writeText(npxCommand);
787-
}}
788-
prefix={<CopyIcon />}
789-
>
782+
<CopyToClipboard text={npxCommand}>
783+
<Button type="button" color="neutral" prefix={<CopyIcon />}>
790784
Copy
791785
</Button>
792-
</Tooltip>
786+
</CopyToClipboard>
793787
</Flex>
794788
</Grid>
795789
<Grid columns={1} gap={2}>
@@ -820,21 +814,17 @@ const ExportContent = (props: { projectId: Project["id"] }) => {
820814
.trimStart()
821815
.replace(/ +$/, "")}
822816
/>
823-
<Tooltip content={"Copy to clipboard"}>
817+
818+
<CopyToClipboard text={deployTargets[deployTarget].command}>
824819
<Button
825820
type="button"
826821
css={{ flexShrink: 0 }}
827822
color="neutral"
828-
onClick={() => {
829-
navigator.clipboard.writeText(
830-
deployTargets[deployTarget].command
831-
);
832-
}}
833823
prefix={<CopyIcon />}
834824
>
835825
Copy
836826
</Button>
837-
</Tooltip>
827+
</CopyToClipboard>
838828
</Flex>
839829
</Grid>
840830
<Grid columns={1} gap={1}>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Tooltip } from "@webstudio-is/design-system";
2+
import { useState } from "react";
3+
4+
export const CopyToClipboard = ({
5+
text,
6+
copyText = "Copy to clipboard",
7+
copiedText = "Copied",
8+
children,
9+
}: {
10+
text: string;
11+
copyText?: string;
12+
copiedText?: string;
13+
children: React.ReactNode;
14+
}) => {
15+
const [isCopied, setIsCopied] = useState(false);
16+
17+
return (
18+
<div
19+
style={{ display: "contents" }}
20+
onClick={() => {
21+
navigator.clipboard.writeText(text);
22+
setIsCopied(true);
23+
}}
24+
>
25+
<Tooltip
26+
// Tooltip sometimes receives onOpenChange with isOpen=false immediately after a click.
27+
// Changing the key seems like a workaround to address this issue.
28+
key={isCopied ? "copied" : "copy"}
29+
disableHoverableContent
30+
content={isCopied ? copiedText : copyText}
31+
open={isCopied === true ? true : undefined}
32+
onOpenChange={(isOpen) => {
33+
if (isOpen === false) {
34+
setIsCopied(false);
35+
}
36+
}}
37+
>
38+
{children}
39+
</Tooltip>
40+
</div>
41+
);
42+
};

apps/builder/app/shared/share-project/share-project.tsx

+9-22
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from "@webstudio-is/icons";
3232
import { Fragment, useState, type ComponentProps, type ReactNode } from "react";
3333
import { useIds } from "../form-utils";
34+
import { CopyToClipboard } from "~/builder/shared/copy-to-clipboard";
3435

3536
const Item = (props: ComponentProps<typeof Flex>) => (
3637
<Flex
@@ -316,35 +317,21 @@ const SharedLinkItem = ({
316317
hasProPlan,
317318
}: SharedLinkItemType) => {
318319
const [currentName, setCurrentName] = useState(value.name);
319-
const [isCopied, setIsCopied] = useState(false);
320320

321321
return (
322322
<Box className={itemStyle()}>
323323
<Label css={{ flexGrow: 1 }}>{currentName}</Label>
324-
<Tooltip
325-
content={isCopied ? "Copied" : "Copy link"}
326-
open={isCopied === true ? true : undefined}
327-
onOpenChange={(isOpen) => {
328-
if (isOpen === false) {
329-
setIsCopied(false);
330-
}
331-
}}
324+
<CopyToClipboard
325+
text={builderUrl({
326+
authToken: value.token,
327+
mode: value.relation === "viewers" ? "preview" : "edit",
328+
})}
329+
copyText="Copy link"
332330
>
333-
<IconButton
334-
aria-label="Copy link"
335-
onClick={() => {
336-
navigator.clipboard.writeText(
337-
builderUrl({
338-
authToken: value.token,
339-
mode: value.relation === "viewers" ? "preview" : "edit",
340-
})
341-
);
342-
setIsCopied(true);
343-
}}
344-
>
331+
<IconButton aria-label="Copy link">
345332
<CopyIcon aria-hidden />
346333
</IconButton>
347-
</Tooltip>
334+
</CopyToClipboard>
348335
<Menu
349336
name={currentName}
350337
value={value}

0 commit comments

Comments
 (0)