Skip to content

Commit 43f877d

Browse files
Show confirmation tooltip on copying URL (#3184)
* Show confirmation tooltip on copying URL. * Update src/frontend/src/lib/components/ui/Tooltip.svelte Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --------- Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
1 parent 0c7ffc8 commit 43f877d

File tree

2 files changed

+77
-49
lines changed

2 files changed

+77
-49
lines changed

src/frontend/src/lib/components/ui/Tooltip.svelte

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
align?: Align;
1313
distance?: string;
1414
hidden?: boolean;
15+
arrow?: boolean;
16+
manual?: boolean; // Always render tooltip, even when not hovered
1517
anchor?: HTMLElement;
1618
};
1719
@@ -23,6 +25,8 @@
2325
align = "center",
2426
distance = "0.5rem",
2527
hidden = false,
28+
manual = false,
29+
arrow = true,
2630
anchor,
2731
children,
2832
class: className,
@@ -31,7 +35,7 @@
3135
3236
let wrapperRef = $state<HTMLElement>();
3337
let tooltipRef = $state<HTMLElement>();
34-
let isTooltipVisible = $state(false);
38+
let isTooltipVisible = $state(manual ? !hidden : false);
3539
3640
const anchorRef = $derived(
3741
anchor ??
@@ -94,20 +98,26 @@
9498
tooltipRef?.hidePopover();
9599
}
96100
});
101+
102+
$effect(() => {
103+
if (manual) {
104+
isTooltipVisible = !hidden;
105+
}
106+
});
97107
</script>
98108

99-
{#if hidden}
109+
{#if !manual && hidden}
100110
{@render children?.()}
101111
{:else}
102112
<!-- Wrapper used for event handlers and (optionally) anchoring -->
103113
<!-- svelte-ignore a11y_no_static_element_interactions -->
104114
<div
105115
bind:this={wrapperRef}
106-
onmouseenter={() => (isTooltipVisible = true)}
107-
onmouseleave={() => (isTooltipVisible = false)}
108-
onfocusin={() => (isTooltipVisible = true)}
109-
onfocusout={() => (isTooltipVisible = false)}
110-
ontouchend={() => (isTooltipVisible = !isTooltipVisible)}
116+
onmouseenter={() => !manual && (isTooltipVisible = true)}
117+
onmouseleave={() => !manual && (isTooltipVisible = false)}
118+
onfocusin={() => !manual && (isTooltipVisible = true)}
119+
onfocusout={() => !manual && (isTooltipVisible = false)}
120+
ontouchend={() => !manual && (isTooltipVisible = !isTooltipVisible)}
111121
class="contents"
112122
aria-describedby={id}
113123
>
@@ -161,45 +171,47 @@
161171
</span>
162172
{/if}
163173
<!-- Tooltip arrow -->
164-
<div
165-
class={[
166-
"absolute size-0",
167-
{
168-
up: {
169-
start: "bottom-0 left-7",
170-
center: "bottom-0 left-[50%]",
171-
end: "right-7 bottom-0",
172-
}[align],
173-
right: {
174-
start: "top-7 left-0",
175-
center: "top-[50%] left-0",
176-
end: "bottom-7 left-0",
177-
}[align],
178-
down: {
179-
start: "top-0 left-7",
180-
center: "top-0 left-[50%]",
181-
end: "top-0 right-7",
182-
}[align],
183-
left: {
184-
start: "top-7 right-0",
185-
center: "top-[50%] right-0",
186-
end: "right-0 bottom-7",
187-
}[align],
188-
}[direction],
189-
]}
190-
>
174+
{#if arrow}
191175
<div
192176
class={[
193-
"bg-fg-primary size-2 origin-center -translate-1 rotate-45",
177+
"absolute size-0",
194178
{
195-
up: "rounded-br-xs",
196-
right: "rounded-bl-xs",
197-
down: "rounded-tl-xs",
198-
left: "rounded-tr-xs",
179+
up: {
180+
start: "bottom-0 left-7",
181+
center: "bottom-0 left-[50%]",
182+
end: "right-7 bottom-0",
183+
}[align],
184+
right: {
185+
start: "top-7 left-0",
186+
center: "top-[50%] left-0",
187+
end: "bottom-7 left-0",
188+
}[align],
189+
down: {
190+
start: "top-0 left-7",
191+
center: "top-0 left-[50%]",
192+
end: "top-0 right-7",
193+
}[align],
194+
left: {
195+
start: "top-7 right-0",
196+
center: "top-[50%] right-0",
197+
end: "right-0 bottom-7",
198+
}[align],
199199
}[direction],
200200
]}
201-
></div>
202-
</div>
201+
>
202+
<div
203+
class={[
204+
"bg-fg-primary size-2 origin-center -translate-1 rotate-45",
205+
{
206+
up: "rounded-br-xs",
207+
right: "rounded-bl-xs",
208+
down: "rounded-tl-xs",
209+
left: "rounded-tr-xs",
210+
}[direction],
211+
]}
212+
></div>
213+
</div>
214+
{/if}
203215
</div>
204216
</div>
205217
{/if}
Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
<script lang="ts">
22
import Button from "$lib/components/ui/Button.svelte";
3-
import { CopyIcon } from "@lucide/svelte";
3+
import { CopyIcon, CheckIcon } from "@lucide/svelte";
44
import QrCode from "$lib/components/ui/QrCode.svelte";
5+
import Tooltip from "$lib/components/ui/Tooltip.svelte";
6+
import { waitFor } from "$lib/utils/utils";
57
68
interface Props {
79
url: URL;
810
}
911
1012
const { url }: Props = $props();
1113
12-
const handleCopyLink = () => navigator.clipboard.writeText(url.href);
14+
let copied = $state(false);
15+
16+
const handleCopyLink = async () => {
17+
await navigator.clipboard.writeText(url.href);
18+
copied = true;
19+
await waitFor(700);
20+
copied = false;
21+
};
1322
</script>
1423

1524
<div class="flex flex-col">
@@ -21,10 +30,17 @@
2130
Scan the above QR code with your
2231
<b class="text-text-primary">new device</b> or enter the URL manually.
2332
</p>
24-
<Button onclick={handleCopyLink} variant="secondary" size="lg" class="mt-6">
25-
<span>
26-
{url.host + url.pathname}
27-
</span>
28-
<CopyIcon size="1.25rem" />
29-
</Button>
33+
<Tooltip
34+
label="Link copied to clipboard"
35+
hidden={!copied}
36+
arrow={false}
37+
manual
38+
>
39+
<Button onclick={handleCopyLink} variant="secondary" size="lg" class="mt-6">
40+
<span>
41+
{url.host + url.pathname + url.search}
42+
</span>
43+
<CopyIcon size="1.25rem" />
44+
</Button>
45+
</Tooltip>
3046
</div>

0 commit comments

Comments
 (0)