forked from jordanlambrecht/tracker-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathActionButtons.tsx
More file actions
116 lines (100 loc) · 3.09 KB
/
Copy pathActionButtons.tsx
File metadata and controls
116 lines (100 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// src/components/ui/ActionButtons.tsx
"use client"
import { useCallback, useState } from "react"
import { Button } from "@/components/ui/Button"
import { CheckLargeIcon, CopyIcon, DownloadArrowIcon } from "@/components/ui/Icons"
import { downloadResponseBlob } from "@/lib/download"
// ── CopyButton ───────────────────────────────────────────────────────────────
interface CopyButtonProps {
value: string
label?: string
className?: string
}
function CopyButton({ value, label, className }: CopyButtonProps) {
const [copied, setCopied] = useState(false)
const handleCopy = useCallback(() => {
navigator.clipboard
.writeText(value)
.then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
.catch(() => {
// clipboard unavailable. silently skip
})
}, [value])
const icon = copied ? (
<CheckLargeIcon width="12" height="12" className="text-success" />
) : (
<CopyIcon width="12" height="12" />
)
const text = label !== undefined ? (copied ? "Copied" : label) : undefined
return (
<Button
variant="secondary"
size={text ? "sm" : "icon"}
onClick={handleCopy}
aria-label="Copy to clipboard"
leftIcon={icon}
text={text}
className={className}
/>
)
}
// ── DownloadButton ───────────────────────────────────────────────────────────
interface DownloadButtonProps {
url: string
fallbackFilename: string
label?: string
notFoundMessage?: string
onError?: (message: string) => void
className?: string
}
function DownloadButton({
url,
fallbackFilename,
label,
notFoundMessage = "File not found",
onError,
className,
}: DownloadButtonProps) {
const [loading, setLoading] = useState(false)
const [done, setDone] = useState(false)
const handleDownload = useCallback(async () => {
setLoading(true)
try {
const res = await fetch(url)
if (!res.ok) {
onError?.(res.status === 404 ? notFoundMessage : "Download failed")
return
}
await downloadResponseBlob(res, fallbackFilename)
setDone(true)
setTimeout(() => setDone(false), 2000)
} catch {
onError?.("Download failed")
} finally {
setLoading(false)
}
}, [url, fallbackFilename, notFoundMessage, onError])
const icon = done ? (
<CheckLargeIcon width="12" height="12" className="text-success" />
) : (
<DownloadArrowIcon width="12" height="12" />
)
const text = label !== undefined ? (done ? "Done" : loading ? "…" : label) : undefined
return (
<Button
variant="secondary"
size={text ? "sm" : "icon"}
onClick={handleDownload}
disabled={loading}
aria-label="Download file"
leftIcon={icon}
text={text}
className={className}
/>
)
}
export type { CopyButtonProps, DownloadButtonProps }
export { CopyButton, DownloadButton }