{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "signature-pad",
  "type": "registry:ui",
  "title": "Signature pad",
  "description": "Draw a signature with mouse, pen or finger on a canvas with pressure aware strokes, undo and clear, a typed name fallback and PNG export for forms.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/button.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/signature-pad.tsx",
      "type": "registry:ui",
      "target": "components/ui/signature-pad.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { EraserIcon, UndoIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\n/**\n * A signature field drawn with pointer events on a canvas: smooth\n * quadratic strokes with pressure-aware width, undo and clear, a typed\n * name fallback for keyboard users, and export to a PNG data URL.\n */\ntype SignaturePadProps = {\n  /** Called with a PNG data URL after each stroke, or null when cleared. */\n  onChange?: (dataUrl: string | null) => void\n  label?: string\n  height?: number\n  className?: string\n}\n\ntype Pt = { x: number; y: number; p: number }\n\nfunction SignaturePad({ onChange, label = \"Signature\", height = 180, className }: SignaturePadProps) {\n  const canvas = React.useRef<HTMLCanvasElement>(null)\n  const strokes = React.useRef<Pt[][]>([])\n  const drawing = React.useRef(false)\n  const [count, setCount] = React.useState(0)\n  const [typed, setTyped] = React.useState(\"\")\n  const [mode, setMode] = React.useState<\"draw\" | \"type\">(\"draw\")\n\n  const redraw = React.useCallback(() => {\n    const c = canvas.current\n    if (!c) return\n    const ctx = c.getContext(\"2d\")!\n    const dpr = window.devicePixelRatio || 1\n    ctx.setTransform(dpr, 0, 0, dpr, 0, 0)\n    ctx.clearRect(0, 0, c.width, c.height)\n    ctx.strokeStyle = getComputedStyle(c).color\n    ctx.lineCap = \"round\"\n    ctx.lineJoin = \"round\"\n    for (const s of strokes.current) {\n      for (let i = 1; i < s.length; i++) {\n        const a = s[i - 1]\n        const b = s[i]\n        const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n        ctx.lineWidth = 1.2 + b.p * 2.4\n        ctx.beginPath()\n        ctx.moveTo(a.x, a.y)\n        ctx.quadraticCurveTo(a.x, a.y, mid.x, mid.y)\n        ctx.lineTo(b.x, b.y)\n        ctx.stroke()\n      }\n    }\n  }, [])\n\n  React.useEffect(() => {\n    const c = canvas.current\n    if (!c) return\n    const fit = () => {\n      const dpr = window.devicePixelRatio || 1\n      c.width = c.clientWidth * dpr\n      c.height = c.clientHeight * dpr\n      redraw()\n    }\n    fit()\n    const ro = new ResizeObserver(fit)\n    ro.observe(c)\n    return () => ro.disconnect()\n  }, [redraw])\n\n  const emit = () => onChange?.(strokes.current.length ? canvas.current!.toDataURL(\"image/png\") : null)\n  const point = (e: React.PointerEvent): Pt => {\n    const r = canvas.current!.getBoundingClientRect()\n    return { x: e.clientX - r.left, y: e.clientY - r.top, p: e.pressure && e.pointerType !== \"mouse\" ? e.pressure : 0.5 }\n  }\n\n  return (\n    <div data-slot=\"signature-pad\" className={cn(\"w-full max-w-md\", className)}>\n      <div className=\"mb-2 flex items-center justify-between\">\n        <span id=\"sig-label\" className=\"text-sm font-medium text-fg\">{label}</span>\n        <button type=\"button\" onClick={() => setMode(mode === \"draw\" ? \"type\" : \"draw\")} className=\"text-xs text-fg-muted underline underline-offset-4 outline-none hover:text-fg focus-visible:ring-2 focus-visible:ring-accent\">\n          {mode === \"draw\" ? \"Type instead\" : \"Draw instead\"}\n        </button>\n      </div>\n      {mode === \"draw\" ? (\n        <div className=\"relative rounded-xl border border-border bg-surface shadow-raised\">\n          <canvas\n            ref={canvas}\n            role=\"img\"\n            aria-labelledby=\"sig-label\"\n            aria-describedby=\"sig-hint\"\n            className=\"block w-full touch-none text-fg\"\n            style={{ height }}\n            onPointerDown={(e) => {\n              ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n              drawing.current = true\n              strokes.current.push([point(e)])\n              redraw()\n            }}\n            onPointerMove={(e) => {\n              if (!drawing.current) return\n              strokes.current[strokes.current.length - 1].push(point(e))\n              redraw()\n            }}\n            onPointerUp={() => {\n              if (!drawing.current) return\n              drawing.current = false\n              setCount(strokes.current.length)\n              emit()\n            }}\n          />\n          <span id=\"sig-hint\" className=\"sr-only\">Draw your signature with a mouse, pen or finger, or choose Type instead.</span>\n          <span aria-hidden className=\"pointer-events-none absolute inset-x-6 bottom-9 border-b border-dashed border-border-strong\" />\n          {!count ? <span aria-hidden className=\"pointer-events-none absolute bottom-3 left-6 text-xs text-fg-subtle\">Sign above the line</span> : null}\n        </div>\n      ) : (\n        <input\n          aria-labelledby=\"sig-label\"\n          value={typed}\n          onChange={(e) => setTyped(e.target.value)}\n          placeholder=\"Type your full name\"\n          className=\"h-[120px] w-full rounded-xl border border-border bg-surface px-5 text-center text-4xl text-fg italic shadow-raised outline-none focus-visible:ring-2 focus-visible:ring-accent\"\n          style={{ fontFamily: '\"Snell Roundhand\", \"Segoe Script\", \"Brush Script MT\", cursive' }}\n        />\n      )}\n      {mode === \"draw\" ? (\n        <div className=\"mt-2 flex gap-2\">\n          <Button size=\"sm\" variant=\"outline\" disabled={!count} onClick={() => { strokes.current.pop(); setCount(strokes.current.length); redraw(); emit() }}>\n            <UndoIcon /> Undo\n          </Button>\n          <Button size=\"sm\" variant=\"ghost\" disabled={!count} onClick={() => { strokes.current = []; setCount(0); redraw(); emit() }}>\n            <EraserIcon /> Clear\n          </Button>\n        </div>\n      ) : null}\n    </div>\n  )\n}\n\nexport { SignaturePad }\nexport type { SignaturePadProps }\n"
    }
  ]
}
