{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "editable",
  "title": "Editable",
  "description": "An inline click-to-edit control that swaps preview text for an input in place, for renaming titles, labels and notes.",
  "dependencies": [
    "@untitledui/icons@^0.0.22"
  ],
  "registryDependencies": [
    "@nx-ui/utils",
    "@nx-ui/button"
  ],
  "files": [
    {
      "path": "components/ui/editable.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, PencilLine, XClose } from \"@untitledui/icons\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nfunction useControllableState<T>(\n  controlled: T | undefined,\n  defaultValue: T,\n  onChange?: (value: T) => void\n) {\n  const [uncontrolled, setUncontrolled] = React.useState<T>(defaultValue)\n  const isControlled = controlled !== undefined\n  const value = isControlled ? (controlled as T) : uncontrolled\n  const set = React.useCallback(\n    (next: T) => {\n      if (!isControlled) setUncontrolled(next)\n      onChange?.(next)\n    },\n    [isControlled, onChange]\n  )\n  return [value, set] as const\n}\n\n// Mirrors components/ui/input.tsx, sized to sit inline with the preview text.\nconst fieldClass =\n  \"w-full min-w-0 rounded-lg border border-input bg-background px-2 py-1.5 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-4 focus-visible:ring-ring/25 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\"\n\nfunction Editable({\n  value: valueProp,\n  defaultValue = \"\",\n  onValueChange,\n  onSubmit,\n  onCancel,\n  editing: editingProp,\n  defaultEditing = false,\n  onEditingChange,\n  placeholder = \"Enter text…\",\n  submitOnBlur = true,\n  maxLength,\n  textarea = false,\n  disabled = false,\n  activateOnDoubleClick = false,\n  showActions = false,\n  className,\n  previewClassName,\n  inputClassName,\n  id,\n  name,\n  \"aria-label\": ariaLabel,\n  ...props\n}: Omit<\n  React.ComponentProps<\"div\">,\n  \"onSubmit\" | \"defaultValue\" | \"onChange\"\n> & {\n  /** Controlled text value. */\n  value?: string\n  /** Uncontrolled initial text value. */\n  defaultValue?: string\n  onValueChange?: (value: string) => void\n  /** Fired with the committed value on Enter / blur / confirm. */\n  onSubmit?: (value: string) => void\n  /** Fired when editing is abandoned via Escape / cancel. */\n  onCancel?: () => void\n  /** Controlled editing state. */\n  editing?: boolean\n  defaultEditing?: boolean\n  onEditingChange?: (editing: boolean) => void\n  placeholder?: string\n  /** Commit (rather than cancel) when the field loses focus. Default true. */\n  submitOnBlur?: boolean\n  maxLength?: number\n  /** Multi-line mode: renders a textarea (Cmd/Ctrl+Enter commits). */\n  textarea?: boolean\n  disabled?: boolean\n  /** Require a double-click to enter edit mode (single click otherwise). */\n  activateOnDoubleClick?: boolean\n  /** Show confirm / cancel action buttons beside the field. */\n  showActions?: boolean\n  previewClassName?: string\n  inputClassName?: string\n  name?: string\n}) {\n  const [value, setValue] = useControllableState(\n    valueProp,\n    defaultValue,\n    onValueChange\n  )\n  const [editing, setEditing] = useControllableState(\n    editingProp,\n    defaultEditing,\n    onEditingChange\n  )\n  const [draft, setDraft] = React.useState(value)\n\n  const previewRef = React.useRef<HTMLButtonElement>(null)\n  const inputRef = React.useRef<HTMLInputElement & HTMLTextAreaElement>(null)\n  const actionsRef = React.useRef<HTMLDivElement>(null)\n  const returnFocus = React.useRef(false)\n\n  const startEditing = React.useCallback(() => {\n    if (disabled) return\n    setDraft(value)\n    setEditing(true)\n  }, [disabled, value, setEditing])\n\n  const commit = React.useCallback(\n    (refocus = true) => {\n      setValue(draft)\n      onSubmit?.(draft)\n      returnFocus.current = refocus\n      setEditing(false)\n    },\n    [draft, onSubmit, setValue, setEditing]\n  )\n\n  const cancel = React.useCallback(\n    (refocus = true) => {\n      setDraft(value)\n      onCancel?.()\n      returnFocus.current = refocus\n      setEditing(false)\n    },\n    [value, onCancel, setEditing]\n  )\n\n  React.useEffect(() => {\n    if (editing) {\n      const el = inputRef.current\n      if (el) {\n        el.focus()\n        el.select()\n      }\n    } else if (returnFocus.current) {\n      returnFocus.current = false\n      previewRef.current?.focus()\n    }\n  }, [editing])\n\n  if (!editing) {\n    return (\n      <div\n        data-slot=\"editable\"\n        data-editing={false}\n        className={cn(\"inline-flex max-w-full\", className)}\n        {...props}\n      >\n        <button\n          ref={previewRef}\n          type=\"button\"\n          data-slot=\"editable-preview\"\n          disabled={disabled}\n          aria-label={ariaLabel}\n          onClick={activateOnDoubleClick ? undefined : startEditing}\n          onDoubleClick={activateOnDoubleClick ? startEditing : undefined}\n          onKeyDown={(event) => {\n            if (event.key === \"Enter\") {\n              event.preventDefault()\n              startEditing()\n            }\n          }}\n          className={cn(\n            \"group/editable inline-flex min-w-0 max-w-full cursor-text items-center gap-1.5 rounded-lg border border-transparent px-2 py-1.5 text-left text-sm text-foreground transition-colors outline-none hover:bg-accent focus-visible:border-ring focus-visible:ring-4 focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50\",\n            !value && \"text-muted-foreground\",\n            textarea && \"items-start whitespace-pre-wrap\",\n            previewClassName\n          )}\n        >\n          <span className={cn(textarea ? \"whitespace-pre-wrap\" : \"truncate\")}>\n            {value || placeholder}\n          </span>\n          {showActions && !disabled ? (\n            <PencilLine className=\"ml-1 size-4 shrink-0 text-fg-quaternary opacity-0 transition-opacity group-hover/editable:opacity-100\" />\n          ) : null}\n        </button>\n      </div>\n    )\n  }\n\n  const commonProps = {\n    ref: inputRef,\n    id,\n    name,\n    value: draft,\n    maxLength,\n    disabled,\n    placeholder,\n    \"aria-label\": ariaLabel,\n    onChange: (\n      event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n    ) => setDraft(event.target.value),\n    onKeyDown: (\n      event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>\n    ) => {\n      if (event.key === \"Escape\") {\n        event.preventDefault()\n        cancel()\n      } else if (event.key === \"Enter\") {\n        if (textarea) {\n          if (event.metaKey || event.ctrlKey) {\n            event.preventDefault()\n            commit()\n          }\n        } else {\n          event.preventDefault()\n          commit()\n        }\n      }\n    },\n    onBlur: (\n      event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>\n    ) => {\n      if (actionsRef.current?.contains(event.relatedTarget as Node)) return\n      if (submitOnBlur) commit(false)\n      else cancel(false)\n    },\n  }\n\n  return (\n    <div\n      data-slot=\"editable\"\n      data-editing={true}\n      className={cn(\"flex max-w-full items-start gap-1.5\", className)}\n      {...props}\n    >\n      {textarea ? (\n        <textarea\n          {...commonProps}\n          rows={3}\n          data-slot=\"editable-input\"\n          className={cn(fieldClass, \"resize-y field-sizing-content\", inputClassName)}\n        />\n      ) : (\n        <input\n          {...commonProps}\n          type=\"text\"\n          data-slot=\"editable-input\"\n          className={cn(fieldClass, inputClassName)}\n        />\n      )}\n      {showActions ? (\n        <div ref={actionsRef} className=\"flex shrink-0 items-center gap-1\">\n          <Button\n            type=\"button\"\n            size=\"icon-sm\"\n            variant=\"secondary\"\n            aria-label=\"Confirm\"\n            onMouseDown={(event) => event.preventDefault()}\n            onClick={() => commit()}\n          >\n            <Check />\n          </Button>\n          <Button\n            type=\"button\"\n            size=\"icon-sm\"\n            variant=\"ghost\"\n            aria-label=\"Cancel\"\n            onMouseDown={(event) => event.preventDefault()}\n            onClick={() => cancel()}\n          >\n            <XClose />\n          </Button>\n        </div>\n      ) : null}\n    </div>\n  )\n}\n\nexport { Editable }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}