{
  "name": "customer-input-fields",
  "title": "CustomerInputFields",
  "description": "Per-product custom input fields — text, number, date, time, file upload, image upload, single/multi-select.",
  "type": "component",
  "registryDependencies": [
    "cn"
  ],
  "files": [
    {
      "path": "customer-input-fields.tsx",
      "content": "\"use client\";\n\nimport React, { useCallback } from \"react\";\nimport { INPUT_FIELD_TYPE, type AddressValue, type DateRangeValue, type LocationValue, type PhoneValue, type ProductInputField, type SignatureValue } from \"@cimplify/sdk\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport type { CurrencyCode } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { useOptionalCimplifyClient } from \"@cimplify/sdk/react\";\nimport { AddressAutocomplete } from \"./address-autocomplete\";\nimport { DatePicker } from \"./date-picker\";\nimport { TimePicker } from \"./time-picker\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CustomerInputFieldsClassNames {\n  root?: string;\n  field?: string;\n  label?: string;\n  required?: string;\n  helpText?: string;\n  input?: string;\n  select?: string;\n  textarea?: string;\n  fileInput?: string;\n  colorInput?: string;\n  radioGroup?: string;\n  radioOption?: string;\n  checkboxLabel?: string;\n  priceAdjustment?: string;\n  addressInput?: string;\n  phoneInput?: string;\n  signatureCanvas?: string;\n  multiSelectGroup?: string;\n  multiSelectOption?: string;\n  dateRangeInput?: string;\n  locationInput?: string;\n}\n\nexport interface CustomerInputFieldsProps {\n  fields: ProductInputField[];\n  values: Record<string, unknown>;\n  onChange: (values: Record<string, unknown>) => void;\n  onFileUpload?: (file: File, field: ProductInputField) => Promise<string>;\n  currency?: CurrencyCode;\n  className?: string;\n  classNames?: CustomerInputFieldsClassNames;\n}\n\nexport function CustomerInputFields({\n  fields,\n  values,\n  onChange,\n  onFileUpload,\n  currency,\n  className,\n  classNames,\n}: CustomerInputFieldsProps): React.ReactElement | null {\n  const clientContext = useOptionalCimplifyClient();\n\n  if (fields.length === 0) return null;\n\n  const sorted = [...fields].sort((a, b) => a.display_order - b.display_order);\n\n  const setValue = useCallback(\n    (fieldId: string, value: unknown) => {\n      onChange({ ...values, [fieldId]: value });\n    },\n    [values, onChange],\n  );\n\n  const defaultFileUpload = useCallback(\n    async (file: File): Promise<string> => {\n      if (onFileUpload) return onFileUpload(file, {} as ProductInputField);\n      if (!clientContext?.client) throw new Error(\"No upload provider available\");\n      const result = await clientContext.client.uploads.upload(file);\n      if (!result.ok) throw new Error(result.error.message);\n      return result.value.url;\n    },\n    [onFileUpload, clientContext],\n  );\n\n  return (\n    <div data-cimplify-customer-inputs className={cn(\"space-y-4\", className, classNames?.root)}>\n      {sorted.map((field) => (\n        <InputField\n          key={field.id}\n          field={field}\n          value={values[field.id]}\n          onValueChange={(value) => setValue(field.id, value)}\n          onFileUpload={onFileUpload ?? (clientContext?.client ? (_file, _field) => defaultFileUpload(_file) : undefined)}\n          currency={currency}\n          classNames={classNames}\n        />\n      ))}\n    </div>\n  );\n}\n\nfunction InputField({\n  field,\n  value,\n  onValueChange,\n  onFileUpload,\n  currency,\n  classNames,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  onFileUpload?: (file: File, field: ProductInputField) => Promise<string>;\n  currency?: CurrencyCode;\n  classNames?: CustomerInputFieldsClassNames;\n}): React.ReactElement {\n  const hasPriceAdjustment = field.price_adjustment != null && parsePrice(field.price_adjustment) > 0;\n\n  return (\n    <div data-cimplify-customer-input data-field-type={field.field_type} className={classNames?.field}>\n      <div className=\"flex items-center gap-2 mb-1.5\">\n        <label className={cn(\"text-sm font-semibold\", classNames?.label)}>\n          {field.name}\n        </label>\n        {field.is_required && (\n          <span className={cn(\"text-xs text-destructive\", classNames?.required)}>Required</span>\n        )}\n        {hasPriceAdjustment && (\n          <span className={cn(\"text-xs text-muted-foreground\", classNames?.priceAdjustment)}>\n            +<Price amount={field.price_adjustment!} currency={currency} />\n          </span>\n        )}\n      </div>\n\n      {field.help_text && (\n        <p className={cn(\"text-xs text-muted-foreground mb-1.5\", classNames?.helpText)}>\n          {field.help_text}\n        </p>\n      )}\n\n      <FieldInput\n        field={field}\n        value={value}\n        onValueChange={onValueChange}\n        onFileUpload={onFileUpload}\n        classNames={classNames}\n      />\n    </div>\n  );\n}\n\nfunction FieldInput({\n  field,\n  value,\n  onValueChange,\n  onFileUpload,\n  classNames,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  onFileUpload?: (file: File, field: ProductInputField) => Promise<string>;\n  classNames?: CustomerInputFieldsClassNames;\n}): React.ReactElement {\n  const inputClass = cn(\n    \"w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring\",\n    classNames?.input,\n  );\n\n  switch (field.field_type) {\n    case INPUT_FIELD_TYPE.Text:\n    case INPUT_FIELD_TYPE.Url:\n      return (\n        <input\n          type={field.field_type === INPUT_FIELD_TYPE.Url ? \"url\" : \"text\"}\n          value={typeof value === \"string\" ? value : \"\"}\n          onChange={(e) => onValueChange(e.target.value)}\n          placeholder={field.placeholder}\n          required={field.is_required}\n          maxLength={field.validation?.max_length ?? undefined}\n          className={inputClass}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Textarea:\n      return (\n        <textarea\n          value={typeof value === \"string\" ? value : \"\"}\n          onChange={(e) => onValueChange(e.target.value)}\n          placeholder={field.placeholder}\n          required={field.is_required}\n          maxLength={field.validation?.max_length ?? undefined}\n          rows={3}\n          className={cn(inputClass, \"resize-none\", classNames?.textarea)}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Number:\n      return (\n        <input\n          type=\"number\"\n          value={typeof value === \"number\" ? value : \"\"}\n          onChange={(e) => onValueChange(e.target.value ? parseFloat(e.target.value) : undefined)}\n          placeholder={field.placeholder}\n          required={field.is_required}\n          min={field.validation?.min_value ?? undefined}\n          max={field.validation?.max_value ?? undefined}\n          className={inputClass}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Select:\n      return (\n        <select\n          value={typeof value === \"string\" ? value : \"\"}\n          onChange={(e) => onValueChange(e.target.value || undefined)}\n          required={field.is_required}\n          className={cn(inputClass, classNames?.select)}\n        >\n          <option value=\"\">{field.placeholder || \"Select...\"}</option>\n          {(field.options || []).map((opt) => (\n            <option key={opt} value={opt}>{opt}</option>\n          ))}\n        </select>\n      );\n\n    case INPUT_FIELD_TYPE.Radio:\n      return (\n        <div className={cn(\"space-y-2\", classNames?.radioGroup)}>\n          {(field.options || []).map((opt) => (\n            <label\n              key={opt}\n              className={cn(\n                \"flex items-center gap-2 text-sm cursor-pointer\",\n                classNames?.radioOption,\n              )}\n            >\n              <input\n                type=\"radio\"\n                name={field.id}\n                value={opt}\n                checked={value === opt}\n                onChange={() => onValueChange(opt)}\n                className=\"accent-primary\"\n              />\n              {opt}\n            </label>\n          ))}\n        </div>\n      );\n\n    case INPUT_FIELD_TYPE.Checkbox:\n      return (\n        <label className={cn(\"flex items-center gap-2 text-sm cursor-pointer\", classNames?.checkboxLabel)}>\n          <input\n            type=\"checkbox\"\n            checked={value === true}\n            onChange={(e) => onValueChange(e.target.checked)}\n            className=\"accent-primary w-4 h-4\"\n          />\n          {field.placeholder || field.name}\n        </label>\n      );\n\n    case INPUT_FIELD_TYPE.Color:\n      return (\n        <input\n          type=\"color\"\n          value={typeof value === \"string\" ? value : \"#000000\"}\n          onChange={(e) => onValueChange(e.target.value)}\n          className={cn(\"w-12 h-10 rounded-md border border-input cursor-pointer\", classNames?.colorInput)}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Date:\n      return (\n        <DatePicker\n          value={typeof value === \"string\" ? value : \"\"}\n          onChange={(next) => onValueChange(next || undefined)}\n          required={field.is_required}\n          placeholder={field.placeholder ?? \"Select a date\"}\n          aria-label={field.name}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.File:\n    case INPUT_FIELD_TYPE.Image:\n      return (\n        <FileUploadInput\n          field={field}\n          value={value}\n          onValueChange={onValueChange}\n          onFileUpload={onFileUpload}\n          classNames={classNames}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Email:\n      return (\n        <input\n          type=\"email\"\n          value={typeof value === \"string\" ? value : \"\"}\n          onChange={(e) => onValueChange(e.target.value)}\n          placeholder={field.placeholder || \"email@example.com\"}\n          required={field.is_required}\n          className={inputClass}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.DateTime: {\n      const stringValue = typeof value === \"string\" ? value : \"\";\n      const [datePart, timePartRaw] = stringValue.includes(\"T\")\n        ? stringValue.split(\"T\", 2)\n        : [stringValue, \"\"];\n      const timePart = (timePartRaw ?? \"\").slice(0, 5);\n      const commit = (nextDate: string, nextTime: string): void => {\n        if (!nextDate && !nextTime) {\n          onValueChange(undefined);\n          return;\n        }\n        if (!nextDate) {\n          onValueChange(`${nextTime}`);\n          return;\n        }\n        const combined = `${nextDate}T${nextTime || \"00:00\"}`;\n        const parsed = new Date(combined);\n        onValueChange(Number.isNaN(parsed.getTime()) ? combined : parsed.toISOString());\n      };\n      return (\n        <div className=\"grid grid-cols-1 sm:grid-cols-[1fr_auto] gap-2\">\n          <DatePicker\n            value={datePart ?? \"\"}\n            onChange={(next) => commit(next, timePart)}\n            placeholder={field.placeholder ?? \"Select a date\"}\n            aria-label={`${field.name} date`}\n            required={field.is_required}\n          />\n          <TimePicker\n            value={timePart}\n            onChange={(next) => commit(datePart ?? \"\", next)}\n            placeholder=\"Time\"\n            aria-label={`${field.name} time`}\n          />\n        </div>\n      );\n    }\n\n    case INPUT_FIELD_TYPE.Time:\n      return (\n        <TimePicker\n          value={typeof value === \"string\" ? value : \"\"}\n          onChange={(next) => onValueChange(next || undefined)}\n          required={field.is_required}\n          placeholder={field.placeholder ?? \"Select a time\"}\n          aria-label={field.name}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.MultiSelect:\n      return (\n        <MultiSelectInput\n          field={field}\n          value={value}\n          onValueChange={onValueChange}\n          classNames={classNames}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.DateRange:\n      return (\n        <DateRangeInput\n          field={field}\n          value={value}\n          onValueChange={onValueChange}\n          classNames={classNames}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Address:\n      return (\n        <AddressInput\n          field={field}\n          value={value}\n          onValueChange={onValueChange}\n          classNames={classNames}\n          inputClass={inputClass}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Location:\n      return (\n        <LocationInput\n          field={field}\n          value={value}\n          onValueChange={onValueChange}\n          classNames={classNames}\n          inputClass={inputClass}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Phone:\n      return (\n        <PhoneInput\n          field={field}\n          value={value}\n          onValueChange={onValueChange}\n          classNames={classNames}\n          inputClass={inputClass}\n        />\n      );\n\n    case INPUT_FIELD_TYPE.Signature:\n      return (\n        <SignatureInput\n          field={field}\n          value={value}\n          onValueChange={onValueChange}\n          classNames={classNames}\n        />\n      );\n\n    default:\n      return (\n        <input\n          type=\"text\"\n          value={typeof value === \"string\" ? value : \"\"}\n          onChange={(e) => onValueChange(e.target.value)}\n          placeholder={field.placeholder}\n          className={inputClass}\n        />\n      );\n  }\n}\n\nfunction FileUploadInput({\n  field,\n  value,\n  onValueChange,\n  onFileUpload,\n  classNames,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  onFileUpload?: (file: File, field: ProductInputField) => Promise<string>;\n  classNames?: CustomerInputFieldsClassNames;\n}): React.ReactElement {\n  const [isUploading, setIsUploading] = React.useState(false);\n  const fileUrl = typeof value === \"string\" ? value : undefined;\n  const acceptedFormats = field.validation?.accepted_formats;\n  const accept = acceptedFormats\n    ? acceptedFormats.map((f) => `.${f}`).join(\",\")\n    : field.field_type === INPUT_FIELD_TYPE.Image\n      ? \"image/*\"\n      : undefined;\n\n  const handleFile = async (file: File) => {\n    if (!onFileUpload) return;\n\n    if (field.validation?.max_size_mb) {\n      const maxBytes = field.validation.max_size_mb * 1024 * 1024;\n      if (file.size > maxBytes) {\n        return;\n      }\n    }\n\n    setIsUploading(true);\n    try {\n      const url = await onFileUpload(file, field);\n      onValueChange(url);\n    } finally {\n      setIsUploading(false);\n    }\n  };\n\n  return (\n    <div className={cn(\"space-y-2\", classNames?.fileInput)}>\n      {fileUrl ? (\n        <div className=\"flex items-center gap-3\">\n          {field.field_type === INPUT_FIELD_TYPE.Image && (\n            <img src={fileUrl} alt=\"Uploaded\" className=\"w-16 h-16 object-cover rounded-md border border-border\" />\n          )}\n          <div className=\"flex-1 min-w-0\">\n            <p className=\"text-sm text-foreground truncate\">{fileUrl.split(\"/\").pop()}</p>\n          </div>\n          <button\n            type=\"button\"\n            onClick={() => onValueChange(undefined)}\n            className=\"text-xs text-muted-foreground hover:text-destructive transition-colors\"\n          >\n            Remove\n          </button>\n        </div>\n      ) : (\n        <label className=\"flex flex-col items-center justify-center w-full h-24 border-2 border-dashed border-border rounded-lg cursor-pointer hover:border-primary/40 transition-colors\">\n          <input\n            type=\"file\"\n            accept={accept}\n            onChange={(e) => {\n              const file = e.target.files?.[0];\n              if (file) void handleFile(file);\n            }}\n            className=\"hidden\"\n            disabled={isUploading}\n          />\n          {isUploading ? (\n            <span className=\"text-sm text-muted-foreground\">Uploading...</span>\n          ) : (\n            <>\n              <span className=\"text-sm text-muted-foreground\">\n                {field.placeholder || `Upload ${field.field_type === INPUT_FIELD_TYPE.Image ? \"image\" : \"file\"}`}\n              </span>\n              {acceptedFormats && (\n                <span className=\"text-xs text-muted-foreground/60 mt-1\">\n                  {acceptedFormats.map((f) => f.toUpperCase()).join(\", \")}\n                  {field.validation?.max_size_mb && ` · Max ${field.validation.max_size_mb}MB`}\n                </span>\n              )}\n            </>\n          )}\n        </label>\n      )}\n    </div>\n  );\n}\n\nfunction MultiSelectInput({\n  field,\n  value,\n  onValueChange,\n  classNames,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  classNames?: CustomerInputFieldsClassNames;\n}): React.ReactElement {\n  const selected = Array.isArray(value) ? (value as string[]) : [];\n  const options = field.options || [];\n  const maxSelections = field.validation?.max_selections;\n\n  const toggle = (opt: string) => {\n    const next = selected.includes(opt)\n      ? selected.filter((s) => s !== opt)\n      : maxSelections && selected.length >= maxSelections\n        ? selected\n        : [...selected, opt];\n    onValueChange(next.length > 0 ? next : undefined);\n  };\n\n  return (\n    <div className={cn(\"space-y-2\", classNames?.multiSelectGroup)}>\n      {options.map((opt) => (\n        <label\n          key={opt}\n          className={cn(\n            \"flex items-center gap-2 text-sm cursor-pointer\",\n            classNames?.multiSelectOption,\n          )}\n        >\n          <input\n            type=\"checkbox\"\n            checked={selected.includes(opt)}\n            onChange={() => toggle(opt)}\n            className=\"accent-primary w-4 h-4\"\n          />\n          {opt}\n        </label>\n      ))}\n      {maxSelections && (\n        <p className=\"text-xs text-muted-foreground\">\n          {selected.length}/{maxSelections} selected\n        </p>\n      )}\n    </div>\n  );\n}\n\nfunction DateRangeInput({\n  field,\n  value,\n  onValueChange,\n  classNames,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  classNames?: CustomerInputFieldsClassNames;\n}): React.ReactElement {\n  const range = (value && typeof value === \"object\" ? value : {}) as DateRangeValue;\n\n  const update = (key: \"start\" | \"end\", v: string) => {\n    const next = { ...range, [key]: v };\n    onValueChange(next.start || next.end ? next : undefined);\n  };\n\n  return (\n    <div className={cn(\"grid grid-cols-1 sm:grid-cols-2 gap-3\", classNames?.dateRangeInput)}>\n      <div>\n        <label className=\"text-xs text-muted-foreground mb-1 block\">Start</label>\n        <DatePicker\n          value={range.start || \"\"}\n          onChange={(next) => update(\"start\", next)}\n          required={field.is_required}\n          placeholder=\"Start date\"\n          aria-label={`${field.name} start date`}\n          maxDate={range.end ? new Date(`${range.end}T00:00`) : undefined}\n        />\n      </div>\n      <div>\n        <label className=\"text-xs text-muted-foreground mb-1 block\">End</label>\n        <DatePicker\n          value={range.end || \"\"}\n          onChange={(next) => update(\"end\", next)}\n          required={field.is_required}\n          placeholder=\"End date\"\n          aria-label={`${field.name} end date`}\n          minDate={range.start ? new Date(`${range.start}T00:00`) : undefined}\n        />\n      </div>\n    </div>\n  );\n}\n\nfunction AddressInput({\n  field,\n  value,\n  onValueChange,\n  classNames,\n  inputClass,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  classNames?: CustomerInputFieldsClassNames;\n  inputClass: string;\n}): React.ReactElement {\n  const addr = (value && typeof value === \"object\" ? value : null) as AddressValue | null;\n\n  return (\n    <div className={cn(\"space-y-2\", classNames?.addressInput)}>\n      <AddressAutocomplete\n        value={addr}\n        onSelect={onValueChange}\n        placeholder={field.placeholder || \"Search for an address...\"}\n        required={field.is_required}\n        classNames={{ input: inputClass }}\n      />\n      {addr && (\n        <input\n          type=\"text\"\n          value={addr.apartment || \"\"}\n          onChange={(e) => onValueChange({ ...addr, apartment: e.target.value || undefined })}\n          placeholder=\"Apt, suite, unit (optional)\"\n          className={inputClass}\n        />\n      )}\n    </div>\n  );\n}\n\nfunction LocationInput({\n  field,\n  value,\n  onValueChange,\n  classNames,\n  inputClass,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  classNames?: CustomerInputFieldsClassNames;\n  inputClass: string;\n}): React.ReactElement {\n  const loc = (value && typeof value === \"object\" ? value : null) as LocationValue | null;\n\n  return (\n    <div className={cn(classNames?.locationInput)}>\n      <AddressAutocomplete\n        value={\n          loc\n            ? { formatted_address: loc.label ?? \"\", latitude: loc.latitude, longitude: loc.longitude }\n            : null\n        }\n        onSelect={(address) =>\n          onValueChange({\n            latitude: address.latitude,\n            longitude: address.longitude,\n            label: address.formatted_address,\n          } satisfies LocationValue)\n        }\n        placeholder={field.placeholder || \"Search for a location...\"}\n        required={field.is_required}\n        classNames={{ input: inputClass }}\n      />\n      {loc && (\n        <p className=\"text-xs text-muted-foreground mt-1\">\n          {loc.latitude.toFixed(4)}, {loc.longitude.toFixed(4)}\n        </p>\n      )}\n    </div>\n  );\n}\n\nfunction PhoneInput({\n  field,\n  value,\n  onValueChange,\n  classNames,\n  inputClass,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  classNames?: CustomerInputFieldsClassNames;\n  inputClass: string;\n}): React.ReactElement {\n  const phone = (value && typeof value === \"object\" ? value : null) as PhoneValue | null;\n  const [code, setCode] = React.useState(phone?.country_code ?? \"+1\");\n  const [number, setNumber] = React.useState(phone?.number ?? \"\");\n\n  const update = (nextCode: string, nextNumber: string) => {\n    setCode(nextCode);\n    setNumber(nextNumber);\n    if (nextNumber) {\n      onValueChange({\n        country_code: nextCode,\n        number: nextNumber,\n        formatted: `${nextCode} ${nextNumber}`,\n      } satisfies PhoneValue);\n    } else {\n      onValueChange(undefined);\n    }\n  };\n\n  return (\n    <div className={cn(\"flex gap-2\", classNames?.phoneInput)}>\n      <input\n        type=\"text\"\n        value={code}\n        onChange={(e) => update(e.target.value, number)}\n        placeholder=\"+1\"\n        className={cn(inputClass, \"w-20 shrink-0\")}\n      />\n      <input\n        type=\"tel\"\n        value={number}\n        onChange={(e) => update(code, e.target.value)}\n        placeholder={field.placeholder || \"Phone number\"}\n        required={field.is_required}\n        className={inputClass}\n      />\n    </div>\n  );\n}\n\nfunction SignatureInput({\n  field,\n  value,\n  onValueChange,\n  classNames,\n}: {\n  field: ProductInputField;\n  value: unknown;\n  onValueChange: (value: unknown) => void;\n  classNames?: CustomerInputFieldsClassNames;\n}): React.ReactElement {\n  const sig = (value && typeof value === \"object\" ? value : null) as SignatureValue | null;\n  const canvasRef = React.useRef<HTMLCanvasElement>(null);\n  const isDrawing = React.useRef(false);\n\n  const getCtx = () => canvasRef.current?.getContext(\"2d\") ?? null;\n\n  const startDraw = (e: React.PointerEvent) => {\n    const ctx = getCtx();\n    if (!ctx) return;\n    isDrawing.current = true;\n    const rect = canvasRef.current!.getBoundingClientRect();\n    ctx.beginPath();\n    ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top);\n    canvasRef.current!.setPointerCapture(e.pointerId);\n  };\n\n  const draw = (e: React.PointerEvent) => {\n    if (!isDrawing.current) return;\n    const ctx = getCtx();\n    if (!ctx) return;\n    const rect = canvasRef.current!.getBoundingClientRect();\n    ctx.lineWidth = 2;\n    ctx.lineCap = \"round\";\n    const style = getComputedStyle(canvasRef.current!);\n    ctx.strokeStyle = style.color || \"#000\";\n    ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top);\n    ctx.stroke();\n  };\n\n  const endDraw = () => {\n    if (!isDrawing.current) return;\n    isDrawing.current = false;\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    onValueChange({\n      data_url: canvas.toDataURL(\"image/png\"),\n      signer_name: sig?.signer_name,\n    } satisfies SignatureValue);\n  };\n\n  const clear = () => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx = canvas.getContext(\"2d\");\n    if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);\n    onValueChange(undefined);\n  };\n\n  return (\n    <div className={cn(\"space-y-2\", classNames?.signatureCanvas)}>\n      <div className=\"relative rounded-md border border-input overflow-hidden\">\n        <canvas\n          ref={canvasRef}\n          width={400}\n          height={150}\n          className=\"w-full touch-none cursor-crosshair bg-background text-foreground\"\n          onPointerDown={startDraw}\n          onPointerMove={draw}\n          onPointerUp={endDraw}\n          onPointerLeave={endDraw}\n        />\n        {sig?.data_url && (\n          <button\n            type=\"button\"\n            onClick={clear}\n            className=\"absolute top-1.5 right-1.5 text-xs text-muted-foreground hover:text-destructive transition-colors\"\n          >\n            Clear\n          </button>\n        )}\n      </div>\n      {!sig?.data_url && (\n        <p className=\"text-xs text-muted-foreground\">\n          {field.placeholder || \"Draw your signature above\"}\n        </p>\n      )}\n    </div>\n  );\n}\n"
    }
  ]
}
