{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inline-edit",
  "title": "Inline Edit",
  "description": "Text that turns into a field in place with confirm and cancel buttons, validation, an async save and focus returned to the text afterwards.",
  "registryDependencies": [
    "button",
    "input"
  ],
  "files": [
    {
      "path": "registry/tealmac/inline-edit.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Pencil, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\n\nexport interface InlineEditProps {\n  value: string\n  /** Called with the trimmed new value when it is valid and changed. May be async. */\n  onSave: (next: string) => void | Promise<void>\n  /** Names the field for screen readers, for example \"Project name\". */\n  label: string\n  /** Returns a message when the value is not allowed. */\n  validate?: (next: string) => string | null\n  placeholder?: string\n  /** Show confirm and cancel buttons in edit mode. Default true. */\n  showActions?: boolean\n  /** Keep the field open and save on blur. Default true. */\n  saveOnBlur?: boolean\n  className?: string\n}\n\n/**\n * Text that turns into a field in place. A button shows the value; click,\n * Enter or F2 edits it. Enter saves, Escape cancels, focus returns to the\n * button afterwards. Errors stay beside the field and are announced.\n */\nexport function InlineEdit({\n  value,\n  onSave,\n  label,\n  validate,\n  placeholder = \"Add a value\",\n  showActions = true,\n  saveOnBlur = true,\n  className,\n}: InlineEditProps) {\n  const [editing, setEditing] = React.useState(false)\n  const [draft, setDraft] = React.useState(value)\n  const [error, setError] = React.useState<string | null>(null)\n  const [saving, setSaving] = React.useState(false)\n  const readRef = React.useRef<HTMLButtonElement>(null)\n  const inputRef = React.useRef<HTMLInputElement>(null)\n  const skipBlur = React.useRef(false)\n  const busy = React.useRef(false)\n  const errorId = React.useId()\n\n  React.useEffect(() => {\n    if (editing) {\n      inputRef.current?.focus()\n      inputRef.current?.select()\n    }\n  }, [editing])\n\n  const close = () => {\n    setEditing(false)\n    setError(null)\n    requestAnimationFrame(() => {\n      // Only restore focus if nothing else took it (the person may already be editing elsewhere).\n      const active = document.activeElement\n      if (!active || active === document.body) readRef.current?.focus()\n    })\n  }\n\n  const cancel = () => {\n    skipBlur.current = true\n    setDraft(value)\n    close()\n  }\n\n  const commit = async () => {\n    if (busy.current) return\n    const next = draft.trim()\n    const message = validate?.(next) ?? null\n    if (message) {\n      setError(message)\n      inputRef.current?.focus()\n      return\n    }\n    if (next === value) return close()\n    busy.current = true\n    setSaving(true)\n    try {\n      await onSave(next)\n      busy.current = false\n      setSaving(false)\n      close()\n    } catch (e) {\n      busy.current = false\n      setSaving(false)\n      setError(e instanceof Error ? e.message : \"Could not save\")\n      inputRef.current?.focus()\n    }\n  }\n\n  if (!editing) {\n    return (\n      <button\n        ref={readRef}\n        type=\"button\"\n        aria-label={`${label}: ${value || \"empty\"}. Edit`}\n        onClick={() => {\n          setDraft(value)\n          setEditing(true)\n        }}\n        onKeyDown={(e) => {\n          if (e.key === \"F2\") {\n            e.preventDefault()\n            setDraft(value)\n            setEditing(true)\n          }\n        }}\n        className={cn(\n          \"group hover:bg-accent focus-visible:ring-ring/50 -mx-2 inline-flex min-h-8 items-center gap-2 rounded-md px-2 text-start outline-none focus-visible:ring-3 motion-reduce:transition-none\",\n          className\n        )}\n      >\n        <span className={cn(!value && \"text-muted-foreground\")}>\n          {value || placeholder}\n        </span>\n        <Pencil\n          className=\"text-muted-foreground size-3.5 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100\"\n          aria-hidden\n        />\n      </button>\n    )\n  }\n\n  return (\n    <div className={cn(\"flex flex-col gap-1\", className)}>\n      <div className=\"flex items-center gap-1\">\n        <Input\n          ref={inputRef}\n          value={draft}\n          aria-label={label}\n          aria-invalid={error ? true : undefined}\n          aria-describedby={error ? errorId : undefined}\n          disabled={saving}\n          className=\"h-8\"\n          onChange={(e) => {\n            setDraft(e.target.value)\n            if (error) setError(null)\n          }}\n          onKeyDown={(e) => {\n            if (e.key === \"Enter\") {\n              e.preventDefault()\n              void commit()\n            } else if (e.key === \"Escape\") {\n              e.preventDefault()\n              cancel()\n            }\n          }}\n          onBlur={(e) => {\n            if (skipBlur.current) {\n              skipBlur.current = false\n              return\n            }\n            const next = e.relatedTarget as HTMLElement | null\n            if (next?.dataset.inlineEditAction) return\n            if (saveOnBlur) void commit()\n            else cancel()\n          }}\n        />\n        {showActions ? (\n          <>\n            <Button\n              type=\"button\"\n              size=\"icon-sm\"\n              variant=\"outline\"\n              aria-label=\"Save\"\n              data-inline-edit-action\n              disabled={saving}\n              onClick={() => void commit()}\n            >\n              <Check className=\"size-4\" aria-hidden />\n            </Button>\n            <Button\n              type=\"button\"\n              size=\"icon-sm\"\n              variant=\"outline\"\n              aria-label=\"Cancel\"\n              data-inline-edit-action\n              onClick={cancel}\n            >\n              <X className=\"size-4\" aria-hidden />\n            </Button>\n          </>\n        ) : null}\n      </div>\n      {error ? (\n        <p id={errorId} role=\"alert\" className=\"text-destructive text-sm\">\n          {error}\n        </p>\n      ) : null}\n    </div>\n  )\n}\n\nexport default InlineEdit\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}