Files
zeekay 05fc512820 feat: sync upstream shadcn/ui — [email protected], v4 app, font system, chart improvements
Upstream sync from shadcn-ui/ui (1686 commits since last merge base).

Key changes:
- packages/shadcn upgraded to 4.1.0 (CLI font transformers, scaffold from github, chart colors)
- apps/v4 replaces apps/www as the primary docs/registry app
- Font system: 25+ heading font registries, font markers utility, @supports override
- Chart improvements: recharts v3 compat, radial chart fixes
- templates/ updated with monorepo variants (astro, vite, react-router, start)
- skills/shadcn AI skill definitions added
- deprecated/ removed (auth, auth-firebase, cli, www)

Preserved Hanzo-only paths: app/, pkg/, demo/, docs/, template/next/
2026-03-24 19:04:31 -07:00

122 lines
2.5 KiB
TypeScript

"use client"
import * as React from "react"
import { IconCheck, IconCopy } from "@tabler/icons-react"
import { trackEvent, type Event } from "@/lib/events"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york-v4/ui/button"
function legacyCopyToClipboard(value: string) {
const textArea = document.createElement("textarea")
textArea.value = value
textArea.setAttribute("readonly", "")
textArea.style.position = "fixed"
textArea.style.opacity = "0"
textArea.style.pointerEvents = "none"
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
textArea.setSelectionRange(0, value.length)
let hasCopied = false
try {
hasCopied = document.execCommand("copy")
} catch {
hasCopied = false
}
document.body.removeChild(textArea)
return hasCopied
}
export async function copyToClipboardWithMeta(value: string, event?: Event) {
if (typeof window === "undefined") {
return false
}
if (!value) {
return false
}
let hasCopied = false
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(value)
hasCopied = true
} catch {
hasCopied = legacyCopyToClipboard(value)
}
} else {
hasCopied = legacyCopyToClipboard(value)
}
if (!hasCopied) {
return false
}
if (event) {
trackEvent(event)
}
return true
}
export function CopyButton({
value,
className,
variant = "ghost",
event,
...props
}: React.ComponentProps<typeof Button> & {
value: string
src?: string
event?: Event["name"]
tooltip?: string
}) {
const [hasCopied, setHasCopied] = React.useState(false)
React.useEffect(() => {
if (hasCopied) {
const timer = setTimeout(() => setHasCopied(false), 2000)
return () => clearTimeout(timer)
}
}, [hasCopied])
return (
<Button
data-slot="copy-button"
data-copied={hasCopied}
size="icon"
variant={variant}
className={cn(
"absolute top-3 right-2 z-10 size-7 bg-code hover:opacity-100 focus-visible:opacity-100",
className
)}
onClick={async () => {
const hasCopied = await copyToClipboardWithMeta(
value,
event
? {
name: event,
properties: {
code: value,
},
}
: undefined
)
if (hasCopied) {
setHasCopied(true)
}
}}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
)
}