{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "signature-pad",
  "title": "Signature Pad",
  "description": "Sign with a pointer on a canvas, or type a name instead.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/signature-pad/signature-pad.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Eraser, PenLine, Type } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nexport type SignatureMode = \"draw\" | \"type\"\n\nexport type SignatureValue = {\n  mode: SignatureMode\n  dataUrl?: string\n  text?: string\n}\n\nexport type SignaturePadProps = Omit<\n  React.HTMLAttributes<HTMLElement>,\n  \"children\" | \"onChange\"\n> & {\n  mode?: SignatureMode\n  defaultMode?: SignatureMode\n  onModeChange?: (mode: SignatureMode) => void\n  onChange?: (value: SignatureValue | null) => void\n  penColor?: string\n  lineWidth?: number\n  height?: number\n  label?: string\n  hint?: React.ReactNode\n  clearLabel?: string\n  typedPlaceholder?: string\n  typedFontFamily?: string\n}\n\nexport function SignaturePad({\n  mode,\n  defaultMode = \"draw\",\n  onModeChange,\n  onChange,\n  penColor = \"currentColor\",\n  lineWidth = 2.25,\n  height = 180,\n  label = \"Signature\",\n  hint = \"Draw with a pointer, or switch to typing.\",\n  clearLabel = \"Clear\",\n  typedPlaceholder = \"Type your name\",\n  typedFontFamily = \"var(--font-display), cursive\",\n  className,\n  ...rootProps\n}: SignaturePadProps) {\n  const reactId = React.useId()\n  const canvasRef = React.useRef<HTMLCanvasElement>(null)\n  const drawing = React.useRef(false)\n  const [hasInk, setHasInk] = React.useState(false)\n  const [typed, setTyped] = React.useState(\"\")\n\n  const [uncontrolledMode, setUncontrolledMode] =\n    React.useState<SignatureMode>(defaultMode)\n  const activeMode = mode ?? uncontrolledMode\n\n  const emit = React.useRef(onChange)\n\n  React.useEffect(() => {\n    emit.current = onChange\n  })\n\n  const setMode = (next: SignatureMode) => {\n    if (mode === undefined) setUncontrolledMode(next)\n    onModeChange?.(next)\n  }\n\n  const context = () => {\n    const canvas = canvasRef.current\n    if (!canvas) return null\n\n    const ctx = canvas.getContext(\"2d\")\n    if (!ctx) return null\n\n    ctx.lineCap = \"round\"\n    ctx.lineJoin = \"round\"\n    ctx.lineWidth = lineWidth\n    ctx.strokeStyle =\n      penColor === \"currentColor\" ? getComputedStyle(canvas).color : penColor\n\n    return ctx\n  }\n\n  React.useEffect(() => {\n    const canvas = canvasRef.current\n    if (!canvas) return\n\n    const resize = () => {\n      const ratio = window.devicePixelRatio || 1\n      const rect = canvas.getBoundingClientRect()\n      if (rect.width === 0) return\n\n      canvas.width = rect.width * ratio\n      canvas.height = rect.height * ratio\n\n      const ctx = canvas.getContext(\"2d\")\n      ctx?.scale(ratio, ratio)\n      setHasInk(false)\n    }\n\n    resize()\n\n    const observer = new ResizeObserver(resize)\n    observer.observe(canvas)\n\n    return () => observer.disconnect()\n  }, [])\n\n  const pointFrom = (event: React.PointerEvent<HTMLCanvasElement>) => {\n    const rect = event.currentTarget.getBoundingClientRect()\n    return { x: event.clientX - rect.left, y: event.clientY - rect.top }\n  }\n\n  const clear = () => {\n    const canvas = canvasRef.current\n    const ctx = canvas?.getContext(\"2d\")\n\n    if (canvas && ctx) {\n      ctx.clearRect(0, 0, canvas.width, canvas.height)\n    }\n\n    setHasInk(false)\n    setTyped(\"\")\n    emit.current?.(null)\n  }\n\n  const commitDrawing = () => {\n    const canvas = canvasRef.current\n    if (!canvas) return\n\n    emit.current?.({ mode: \"draw\", dataUrl: canvas.toDataURL(\"image/png\") })\n  }\n\n  const isEmpty = activeMode === \"draw\" ? !hasInk : typed.trim().length === 0\n\n  return (\n    <section\n      data-slot=\"signature-pad\"\n      data-mode={activeMode}\n      aria-labelledby={`${reactId}-label`}\n      className={cn(\n        \"border-border bg-card text-card-foreground rounded-[var(--radius)] border\",\n        className\n      )}\n      {...rootProps}\n    >\n      <div className=\"border-border flex flex-wrap items-center gap-2 border-b px-3 py-2\">\n        <p id={`${reactId}-label`} className=\"text-sm font-semibold\">\n          {label}\n        </p>\n\n        <div\n          role=\"group\"\n          aria-label=\"Signature method\"\n          className=\"border-border ml-auto inline-flex rounded-full border p-0.5\"\n        >\n          {(\n            [\n              { id: \"draw\" as const, icon: PenLine, text: \"Draw\" },\n              { id: \"type\" as const, icon: Type, text: \"Type\" },\n            ] satisfies {\n              id: SignatureMode\n              icon: typeof PenLine\n              text: string\n            }[]\n          ).map((option) => {\n            const Icon = option.icon\n            const isActive = activeMode === option.id\n\n            return (\n              <button\n                key={option.id}\n                type=\"button\"\n                aria-pressed={isActive}\n                className={cn(\n                  \"inline-flex min-h-9 items-center gap-1.5 rounded-full px-2.5 text-xs font-semibold\",\n                  isActive\n                    ? \"bg-foreground text-background\"\n                    : \"text-muted-foreground hover:text-foreground transition-colors duration-150 motion-reduce:transition-none\"\n                )}\n                onClick={() => setMode(option.id)}\n              >\n                <Icon aria-hidden=\"true\" size={13} />\n                {option.text}\n              </button>\n            )\n          })}\n        </div>\n      </div>\n\n      <div className=\"p-3\">\n        {activeMode === \"draw\" ? (\n          <canvas\n            ref={canvasRef}\n            data-slot=\"signature-pad-canvas\"\n            aria-label={`${label}. Drawing area. Switch to typing for a keyboard alternative.`}\n            role=\"img\"\n            className=\"border-border w-full touch-none rounded-[calc(var(--radius)-0.25rem)] border border-dashed\"\n            style={{ height }}\n            onPointerDown={(event) => {\n              if (event.button !== 0) return\n              const ctx = context()\n              if (!ctx) return\n\n              event.currentTarget.setPointerCapture(event.pointerId)\n              drawing.current = true\n\n              const { x, y } = pointFrom(event)\n              ctx.beginPath()\n              ctx.moveTo(x, y)\n            }}\n            onPointerMove={(event) => {\n              if (!drawing.current) return\n              const ctx = context()\n              if (!ctx) return\n\n              const { x, y } = pointFrom(event)\n              ctx.lineTo(x, y)\n              ctx.stroke()\n              if (!hasInk) setHasInk(true)\n            }}\n            onPointerUp={() => {\n              if (!drawing.current) return\n              drawing.current = false\n              commitDrawing()\n            }}\n            onPointerCancel={() => {\n              drawing.current = false\n            }}\n          />\n        ) : (\n          <div>\n            <label className=\"sr-only\" htmlFor={`${reactId}-typed`}>\n              {label}\n            </label>\n            <input\n              id={`${reactId}-typed`}\n              className=\"border-border placeholder:text-muted-foreground flex w-full items-center rounded-[calc(var(--radius)-0.25rem)] border border-dashed bg-transparent px-4 text-3xl outline-none\"\n              placeholder={typedPlaceholder}\n              style={{ height, fontFamily: typedFontFamily }}\n              value={typed}\n              onChange={(event) => {\n                const next = event.target.value\n                setTyped(next)\n                emit.current?.(\n                  next.trim() ? { mode: \"type\", text: next } : null\n                )\n              }}\n            />\n          </div>\n        )}\n\n        <div className=\"mt-2 flex items-center gap-3\">\n          <p className=\"text-muted-foreground text-xs\">{hint}</p>\n\n          <button\n            type=\"button\"\n            data-slot=\"signature-pad-clear\"\n            disabled={isEmpty}\n            className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring ml-auto inline-flex min-h-11 items-center gap-1.5 rounded-full px-2 text-xs font-semibold transition-colors duration-150 focus-visible:ring-2 focus-visible:outline-none disabled:opacity-40 motion-reduce:transition-none\"\n            onClick={clear}\n          >\n            <Eraser aria-hidden=\"true\" size={13} />\n            {clearLabel}\n          </button>\n        </div>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "document",
    "signature",
    "canvas",
    "form"
  ],
  "type": "registry:ui"
}