{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "approval",
  "title": "Approval",
  "description": "An action an agent wants to take, waiting for a person. Say what it does and what can't be undone; press and hold the brass seal to approve.",
  "dependencies": [
    "@base-ui/react",
    "cn"
  ],
  "registryDependencies": [
    "https://bevel-ui.vercel.app/r/bevel.json",
    "https://bevel-ui.vercel.app/r/stamp.json"
  ],
  "files": [
    {
      "path": "registry/bevel/ui/approval.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\"\nimport { cn } from \"cn\"\nimport { Seal, Stamp } from \"@/components/ui/stamp\"\n\ntype ApprovalState = \"pending\" | \"approved\" | \"rejected\"\n\nconst ApprovalContext = React.createContext<{\n  state: ApprovalState\n  approve: () => void\n  reject: () => void\n  titleId: string\n}>({ state: \"pending\", approve: () => {}, reject: () => {}, titleId: \"\" })\n\ntype ApprovalProps = Omit<React.ComponentProps<\"div\">, \"onReset\"> & {\n  state?: ApprovalState\n  defaultState?: ApprovalState\n  onApprove?: () => void\n  onReject?: () => void\n}\n\n/**\n * An action waiting for a person: what it does, what it can't undo, who has\n * signed, and the seal to approve it.\n */\nfunction Approval({\n  className,\n  state: stateProp,\n  defaultState = \"pending\",\n  onApprove,\n  onReject,\n  children,\n  ...props\n}: ApprovalProps) {\n  const [uncontrolled, setUncontrolled] = React.useState(defaultState)\n  const state = stateProp ?? uncontrolled\n  const titleId = React.useId()\n  const context = React.useMemo(\n    () => ({\n      state,\n      titleId,\n      approve: () => {\n        if (stateProp === undefined) setUncontrolled(\"approved\")\n        onApprove?.()\n      },\n      reject: () => {\n        if (stateProp === undefined) setUncontrolled(\"rejected\")\n        onReject?.()\n      },\n    }),\n    [state, stateProp, titleId, onApprove, onReject]\n  )\n  return (\n    <ApprovalContext value={context}>\n      <div\n        role=\"group\"\n        aria-labelledby={titleId}\n        data-slot=\"approval\"\n        data-state={state}\n        className={cn(\n          \"bevel-paper relative flex w-full max-w-md flex-col gap-4 rounded-(--bevel-radius-xs) p-5 text-sm\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <ApprovalOutcome />\n      </div>\n    </ApprovalContext>\n  )\n}\n\n/** The outcome pressed into the card, and announced. */\nfunction ApprovalOutcome() {\n  const { state } = React.useContext(ApprovalContext)\n  return (\n    <div role=\"status\" className=\"pointer-events-none absolute top-4 right-4\">\n      {state === \"approved\" && (\n        <>\n          {/* Announce one clear word; the seal's ring text is decoration. */}\n          <span className=\"sr-only\">Approved</span>\n          <Seal aria-hidden text=\"Approved · Approved ·\" animate className=\"w-(--approval-seal-size)\">\n            OK\n          </Seal>\n        </>\n      )}\n      {state === \"rejected\" && (\n        <Stamp tone=\"red\" animate>\n          Sent back\n        </Stamp>\n      )}\n    </div>\n  )\n}\n\nfunction ApprovalTitle({ className, ...props }: React.ComponentProps<\"h3\">) {\n  const { titleId } = React.useContext(ApprovalContext)\n  return (\n    <h3\n      id={titleId}\n      data-slot=\"approval-title\"\n      className={cn(\"pr-24 text-base font-semibold text-(--bevel-paper-foreground)\", className)}\n      {...props}\n    />\n  )\n}\n\n/** What happens, and what can't be undone. Say it plainly. */\nfunction ApprovalDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"approval-description\"\n      className={cn(\"pr-20 leading-relaxed text-(--bevel-paper-muted)\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ApprovalSignatures({ className, ...props }: React.ComponentProps<\"ul\">) {\n  return (\n    <ul\n      aria-label=\"Signatures\"\n      data-slot=\"approval-signatures\"\n      className={cn(\"grid grid-cols-2 gap-4\", className)}\n      {...props}\n    />\n  )\n}\n\n/** A signature line. Signed names are written over it in marker. */\nfunction ApprovalSignature({\n  name,\n  signed = false,\n  className,\n  ...props\n}: { name: string; signed?: boolean } & Omit<React.ComponentProps<\"li\">, \"children\">) {\n  return (\n    <li data-slot=\"approval-signature\" data-signed={signed ? \"\" : undefined} className={cn(\"flex flex-col\", className)} {...props}>\n      <span\n        aria-hidden\n        className=\"flex h-8 items-end border-b border-(--bevel-paper-foreground)/50 pb-0.5 font-(family-name:--bevel-font-marker) text-lg leading-none text-(--bevel-pen-blue)\"\n      >\n        {signed ? name : \"\"}\n      </span>\n      <span className=\"mt-1 text-(length:--bevel-fine) text-(--bevel-paper-muted)\">\n        {name}\n        <span className=\"sr-only\">: {signed ? \"signed\" : \"not signed yet\"}</span>\n        <span aria-hidden>{signed ? \" · signed\" : \" · awaiting\"}</span>\n      </span>\n    </li>\n  )\n}\n\nfunction ApprovalActions({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { state } = React.useContext(ApprovalContext)\n  if (state !== \"pending\") return null\n  return (\n    <div\n      data-slot=\"approval-actions\"\n      className={cn(\"flex items-center justify-between gap-4 pt-1\", className)}\n      {...props}\n    />\n  )\n}\n\n/** Send it back without approving. A plain, single press. */\nfunction ApprovalReject({ className, onClick, ...props }: ButtonPrimitive.Props) {\n  const { reject } = React.useContext(ApprovalContext)\n  return (\n    <ButtonPrimitive\n      data-slot=\"approval-reject\"\n      onClick={(event) => {\n        onClick?.(event)\n        if (!event.defaultPrevented) reject()\n      }}\n      className={cn(\n        \"h-9 rounded-(--bevel-radius-sm) border border-(--bevel-paper-foreground)/30 px-3 text-sm font-medium text-(--bevel-paper-foreground) outline-none\",\n        \"hover:bg-(--bevel-paper-foreground)/5 focus-visible:ring-2 focus-visible:ring-(--bevel-pen-blue)/50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/** Browsers may report a custom-property duration as \"1200ms\" or \"1.2s\". */\nfunction toMilliseconds(value: string) {\n  const n = parseFloat(value)\n  if (Number.isNaN(n)) return 1200\n  return /ms\\s*$/.test(value) ? n : /s\\s*$/.test(value) ? n * 1000 : n\n}\n\ntype ApprovalSealProps = Omit<ButtonPrimitive.Props, \"onClick\"> & {\n  /** Press and hold to approve (the default), or a single press. */\n  requireHold?: boolean\n}\n\n/**\n * The brass embosser. Press and hold (pointer, Enter or Space) until it\n * seals; letting go early springs it back. The hold is deliberate on\n * purpose: approving is hard to undo.\n */\nfunction ApprovalSeal({ className, requireHold = true, children, ...props }: ApprovalSealProps) {\n  const { approve } = React.useContext(ApprovalContext)\n  const [holding, setHolding] = React.useState(false)\n  const timer = React.useRef<number | undefined>(undefined)\n  const ref = React.useRef<HTMLButtonElement>(null)\n  const hintId = React.useId()\n\n  const start = () => {\n    if (!requireHold) return approve()\n    if (timer.current !== undefined) return\n    const ms = toMilliseconds(getComputedStyle(ref.current!).getPropertyValue(\"--approval-hold\"))\n    setHolding(true)\n    timer.current = window.setTimeout(() => {\n      timer.current = undefined\n      setHolding(false)\n      approve()\n    }, ms)\n  }\n  const cancel = () => {\n    if (timer.current !== undefined) window.clearTimeout(timer.current)\n    timer.current = undefined\n    setHolding(false)\n  }\n  React.useEffect(() => cancel, [])\n\n  return (\n    <div className=\"flex items-center gap-3\">\n      <span id={hintId} className=\"text-right text-(length:--bevel-fine) text-(--bevel-paper-muted)\">\n        {requireHold ? \"Press and hold to approve\" : \"Press to approve\"}\n      </span>\n      <ButtonPrimitive\n        ref={ref}\n        data-slot=\"approval-seal\"\n        data-holding={holding ? \"\" : undefined}\n        aria-describedby={hintId}\n        onPointerDown={(event) => {\n          if (event.button !== 0) return\n          event.currentTarget.setPointerCapture(event.pointerId)\n          start()\n        }}\n        onPointerUp={cancel}\n        onPointerCancel={cancel}\n        onLostPointerCapture={cancel}\n        onKeyDown={(event) => {\n          if ((event.key === \"Enter\" || event.key === \" \") && !event.repeat) {\n            event.preventDefault()\n            start()\n          }\n        }}\n        onKeyUp={(event) => {\n          if (event.key === \"Enter\" || event.key === \" \") cancel()\n        }}\n        className={cn(\n          \"bevel-metal bevel-object relative grid size-16 shrink-0 cursor-pointer place-items-center overflow-hidden rounded-full text-(length:--bevel-fine) font-bold tracking-(--bevel-tracking-print) text-(--bevel-ivory-foreground) uppercase outline-none select-none\",\n          \"transition-[translate] duration-(--bevel-duration-press) ease-(--bevel-ease-out) data-holding:translate-y-0.5 motion-reduce:transition-none\",\n          \"focus-visible:ring-3 focus-visible:ring-(--bevel-pen-blue)/50 focus-visible:ring-offset-2\",\n          className\n        )}\n        {...props}\n      >\n        {/* Fills slowly while held; drains fast on release. */}\n        <span\n          aria-hidden\n          className={cn(\n            \"absolute inset-0 bg-(--approval-press-fill)\",\n            holding\n              ? \"[clip-path:inset(0_0_0_0)] transition-[clip-path] duration-(--approval-hold) ease-linear\"\n              : \"[clip-path:inset(100%_0_0_0)] transition-[clip-path] duration-(--approval-release) ease-(--bevel-ease-out)\",\n            \"motion-reduce:transition-none\"\n          )}\n        />\n        <span className=\"relative\">{children ?? \"Seal\"}</span>\n      </ButtonPrimitive>\n    </div>\n  )\n}\n\nexport {\n  Approval,\n  ApprovalTitle,\n  ApprovalDescription,\n  ApprovalSignatures,\n  ApprovalSignature,\n  ApprovalActions,\n  ApprovalReject,\n  ApprovalSeal,\n}\nexport type { ApprovalProps, ApprovalState, ApprovalSealProps }\n",
      "type": "registry:ui"
    }
  ],
  "css": {
    ":root": {
      "--approval-hold": "1200ms",
      "--approval-release": "200ms",
      "--approval-press-fill": "oklch(0.42 0.08 70 / 0.55)",
      "--approval-seal-size": "5rem"
    },
    "[data-slot=\"approval-seal\"]": {
      "--bevel-material": "var(--bevel-brass)",
      "--bevel-material-edge": "var(--bevel-brass-edge)"
    }
  },
  "categories": [
    "agents"
  ],
  "type": "registry:ui"
}