# Copy Button

Copies one value to the clipboard, alone or with a pivot action next to a value in a table.

```tsx
import { CopyButton } from "@zenoxai/ui/patterns/copy-button"

function CopyButtonDemo() {
  return (
    <div className="flex items-center gap-1.5 text-sm">
      <span className="text-text-secondary">Tenant ID</span>
      <span className="font-mono">tnt_8f3a2c91d4</span>
      <CopyButton value="tnt_8f3a2c91d4" label="Copy tenant ID" />
    </div>
  )
}
```

## Usage

```tsx
import { CopyButton } from "@zenoxai/ui/patterns/copy-button"

export function TenantId({ id }: { id: string }) {
  return (
    <span className="gap-1.5 flex items-center">
      <span className="font-mono">{id}</span>
      <CopyButton value={id} label="Copy tenant ID" />
    </span>
  )
}
```

Put a `CopyButton` after values people paste somewhere else: identifiers, hashes, IP addresses,
domains, commands. After a successful copy the icon turns into a check for a moment.

- `value` is the exact text written to the clipboard. Pass the raw value, never a masked or
  truncated rendering of it.
- `label` is the accessible name and the tooltip. It defaults to "Copy"; name what is copied
  ("Copy tenant ID") whenever more than one value on screen can be copied.

In tables, wrap the value in `ValueActions` instead of placing buttons by hand. It adds copy and
pivot actions that appear on hover.

## Examples

### With a toast

The check icon is enough next to a single value. When the surface needs more feedback, or the
copy can fail (the browser denies clipboard access), raise a toast from `onCopy` and
`onCopyError`. The app mounts the `Toaster` once at its root; this example renders its own.

```tsx
import { toast } from "sonner"
import { Toaster } from "@zenoxai/ui/components/sonner"
import { CopyButton } from "@zenoxai/ui/patterns/copy-button"

function CopyButtonWithToast() {
  const hash =
    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

  return (
    <>
      <div className="flex w-full max-w-md items-center gap-1.5 text-sm">
        {/* Truncated at the end; the full value is in the title and the
            copy action. */}
        <span className="min-w-0 truncate font-mono" title={hash}>
          {hash}
        </span>
        <CopyButton
          value={hash}
          label="Copy SHA-256"
          onCopy={() => toast.success("SHA-256 copied to the clipboard")}
          onCopyError={() =>
            toast.error("Could not copy. The browser blocked clipboard access.")
          }
        />
      </div>
      <Toaster />
    </>
  )
}
```

### Value actions in a table

`ValueActions` renders the value followed by its actions, hidden until the pointer is over the
value or an action has keyboard focus.

- `copyValue` adds the copy action. Omit it to drop the action.
- `onPivot` adds a search action: "show every row with this value". Omit it to drop the action.

When the cell shows a derived label, such as a translated enum or a country name resolved from
a code, offer the pivot without the copy. Copying text the user cannot see, like the underlying
code, is a surprise.

Clicks on the actions do not propagate, so an action inside a clickable row does not also open
the row.

```tsx
import * as React from "react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@zenoxai/ui/components/table"
import { ValueActions } from "@zenoxai/ui/patterns/copy-button"

function CopyButtonValueActions() {
  const [pivot, setPivot] = React.useState<string | null>(null)

  const events = [
    {
      id: "evt-1",
      ip: "203.0.113.42",
      country: "Netherlands",
      rule: "SSH brute force",
    },
    {
      id: "evt-2",
      ip: "198.51.100.7",
      country: "Brazil",
      rule: "Impossible travel",
    },
    {
      id: "evt-3",
      ip: "203.0.113.42",
      country: "Netherlands",
      rule: "Port scan",
    },
  ]

  return (
    <div className="flex w-full flex-col gap-3">
      <Table containerLabel="Events">
        <TableHeader>
          <TableRow>
            <TableHead>Source IP</TableHead>
            <TableHead>Country</TableHead>
            <TableHead>Rule</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {events.map((event) => (
            <TableRow key={event.id}>
              <TableCell>
                <ValueActions
                  copyValue={event.ip}
                  copyLabel="Copy IP address"
                  onPivot={() => setPivot(`source.ip = ${event.ip}`)}
                  pivotLabel="Show events from this IP"
                >
                  <span className="font-mono">{event.ip}</span>
                </ValueActions>
              </TableCell>
              <TableCell>
                {/* A derived label: pivot on it, but do not copy it. */}
                <ValueActions
                  onPivot={() => setPivot(`country = ${event.country}`)}
                  pivotLabel="Show events from this country"
                >
                  {event.country}
                </ValueActions>
              </TableCell>
              <TableCell>{event.rule}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
      <p className="text-sm text-text-secondary">
        {pivot
          ? `Filter: ${pivot}`
          : "Hover a value to copy it or filter by it."}
      </p>
    </div>
  )
}
```

## Accessibility

- Each action is a real icon button named by its label, with the same text as a tooltip.
- Hidden actions stay in the tab order and appear when they receive keyboard focus.
- The swap to a check icon is not announced to screen readers. When the copy matters to the
  task, confirm it with a toast from `onCopy`, which is announced.
- Touch screens have no hover, so hover-revealed actions stay hidden there. Where touch
  matters, render `CopyButton` without `revealOnHover`.

## API reference

`import { ... } from "@zenoxai/ui/patterns/copy-button"`

### CopyButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| copiedLabel | `string` | `Copied` | Already-translated text announced to screen readers after a copy. |
| label | `string` | `Copy` | Already-translated accessible name and tooltip. |
| onCopy | `((value: string) => void)` |  | Called after the value reached the clipboard. Show a toast here when the surface needs more feedback than the check icon. |
| onCopyError | `((error: unknown) => void)` |  | Called when the value could not be written: the page is not a secure context (no Clipboard API) or the browser denied the write. |
| revealOnHover | `boolean` | `false` | Hide until the enclosing `group/value` is hovered. |
| value (required) | `string` |  | The exact text written to the clipboard, never a masked rendering. |

### ValueActions

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| copyLabel | `string` |  | Already-translated label of the copy action. |
| copyValue | `string \| null` |  | Text written to the clipboard. Omit to drop the copy action. |
| onCopy | `((value: string) => void)` |  | Forwarded to the copy action. |
| onPivot | `(() => void) \| null` |  | "Show every row with this value". Omit to drop the pivot action. |
| pivotLabel | `string` | `Search for this value` | Already-translated label of the pivot action. |

