Particular.Tech
Actionsui

Copy button

Copies a value to the clipboard and confirms success or failure.

https://acme-web-8f2ac91d.particular.app

Installation

npx shadcn@latest add @particular/copy-button
npm dependencies
  • cn@^0.4.0
  • lucide-react@^1.51.0
Installs these registry items

Source

ui/copy-button.tsx
"use client"

import * as React from "react"
import { CheckIcon, CopyIcon, XIcon } from "lucide-react"
import { cn } from "cn"

import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"

const statusText = {
  idle: "",
  copied: "Copied to clipboard",
  error: "Copy failed",
} as const

function CopyButton({
  value,
  label = "Copy",
  onCopied,
  className,
  variant = "ghost",
  size = "icon-sm",
  children,
  ...props
}: Omit<React.ComponentProps<typeof Button>, "value" | "onClick" | "onCopy"> & {
  value: string
  /** Accessible name of the button. */
  label?: string
  onCopied?: (value: string) => void
}) {
  const { status, copy } = useCopyToClipboard()
  const Icon = status === "copied" ? CheckIcon : status === "error" ? XIcon : CopyIcon

  return (
    <Button
      data-slot="copy-button"
      data-status={status}
      variant={variant}
      size={size}
      aria-label={children ? undefined : label}
      className={cn(
        "text-muted-foreground hover:text-foreground data-[status=copied]:text-success data-[status=error]:text-destructive",
        className
      )}
      onClick={async () => {
        if (await copy(value)) onCopied?.(value)
      }}
      {...props}
    >
      <Icon data-icon={children ? "inline-start" : undefined} />
      {children}
      <span aria-live="polite" className="sr-only">
        {statusText[status]}
      </span>
    </Button>
  )
}

export { CopyButton }