{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "editable-text",
  "title": "Editable Text",
  "description": "Inline editable text with animated border reveal, shimmer effect, and smooth save/cancel controls.",
  "dependencies": [
    "motion",
    "@tabler/icons-react",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "registry/wise-ui/components/editable-text.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion } from \"motion/react\"\nimport { IconPencil, IconCheck, IconX } from \"@tabler/icons-react\"\nimport { cn } from \"@/registry/wise-ui/lib/utils\"\n\ninterface EditableTextProps {\n  value: string\n  onChange: (value: string) => void\n  className?: string\n  placeholder?: string\n}\n\nconst EditableText = React.forwardRef<HTMLDivElement, EditableTextProps>(\n  ({ value, onChange, className, placeholder = \"Click to edit...\" }, ref) => {\n    const [isEditing, setIsEditing] = React.useState(false)\n    const [isHovered, setIsHovered] = React.useState(false)\n    const [draft, setDraft] = React.useState(value)\n    const [shimmerKey, setShimmerKey] = React.useState(0)\n    const [textWidth, setTextWidth] = React.useState<number | undefined>(\n      undefined\n    )\n    const inputRef = React.useRef<HTMLInputElement>(null)\n    const textRef = React.useRef<HTMLSpanElement>(null)\n    const blurTimeoutRef = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n\n    React.useEffect(() => {\n      if (!isEditing) setDraft(value)\n    }, [value, isEditing])\n\n    React.useEffect(() => {\n      if (isEditing) {\n        const timeout = setTimeout(() => {\n          inputRef.current?.focus()\n        }, 50)\n        return () => clearTimeout(timeout)\n      }\n    }, [isEditing])\n\n    React.useEffect(() => {\n      return () => clearTimeout(blurTimeoutRef.current)\n    }, [])\n\n    function startEditing() {\n      if (textRef.current) {\n        setTextWidth(textRef.current.offsetWidth)\n      }\n      setDraft(value)\n      setIsEditing(true)\n      setIsHovered(false)\n    }\n\n    function save() {\n      clearTimeout(blurTimeoutRef.current)\n      onChange(draft)\n      setIsEditing(false)\n    }\n\n    function cancel() {\n      clearTimeout(blurTimeoutRef.current)\n      setDraft(value)\n      setIsEditing(false)\n    }\n\n    function handleKeyDown(e: React.KeyboardEvent) {\n      if (e.key === \"Enter\") save()\n      if (e.key === \"Escape\") cancel()\n    }\n\n    function handleBlur() {\n      blurTimeoutRef.current = setTimeout(save, 150)\n    }\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\"relative inline-flex items-center\", className)}\n        onMouseEnter={() => {\n          if (!isEditing) {\n            setIsHovered(true)\n            setShimmerKey((k) => k + 1)\n          }\n        }}\n        onMouseLeave={() => setIsHovered(false)}\n      >\n        {isEditing ? (\n          <div className=\"relative inline-flex items-center\">\n            {/* Input with animated border - same width as display text */}\n            <div className=\"relative\">\n              <motion.div\n                className=\"pointer-events-none absolute inset-0 rounded-md border border-neutral-400\"\n                initial={{ clipPath: \"inset(99% 50% 0% 50%)\" }}\n                animate={{ clipPath: \"inset(0% 0% 0% 0%)\" }}\n                transition={{ duration: 0.35, ease: [0.4, 0, 0.2, 1] }}\n              />\n              <input\n                ref={inputRef}\n                value={draft}\n                onChange={(e) => setDraft(e.target.value)}\n                onKeyDown={handleKeyDown}\n                onBlur={handleBlur}\n                placeholder={placeholder}\n                className=\"bg-transparent px-2 py-1 text-inherit outline-none placeholder:text-neutral-500\"\n                style={{\n                  font: \"inherit\",\n                  width: textWidth ? `${textWidth}px` : undefined,\n                  minWidth: \"80px\",\n                }}\n              />\n            </div>\n\n            {/* Save / Cancel - positioned outside layout flow */}\n            <div className=\"absolute left-full ml-1.5 flex items-center gap-0.5\">\n              <motion.button\n                type=\"button\"\n                onClick={save}\n                className=\"rounded p-0.5 text-muted-foreground transition-colors hover:text-emerald-500\"\n                initial={{ opacity: 0, scale: 0.5 }}\n                animate={{ opacity: 1, scale: 1 }}\n                transition={{\n                  delay: 0.25,\n                  type: \"spring\",\n                  stiffness: 500,\n                  damping: 25,\n                }}\n              >\n                <IconCheck size={14} strokeWidth={2.5} />\n              </motion.button>\n              <motion.button\n                type=\"button\"\n                onClick={cancel}\n                className=\"rounded p-0.5 text-muted-foreground transition-colors hover:text-red-500\"\n                initial={{ opacity: 0, scale: 0.5 }}\n                animate={{ opacity: 1, scale: 1 }}\n                transition={{\n                  delay: 0.3,\n                  type: \"spring\",\n                  stiffness: 500,\n                  damping: 25,\n                }}\n              >\n                <IconX size={14} strokeWidth={2.5} />\n              </motion.button>\n            </div>\n          </div>\n        ) : (\n          <div\n            className=\"relative inline-flex cursor-pointer items-center\"\n            onClick={startEditing}\n          >\n            <span ref={textRef} className=\"relative overflow-hidden rounded-md px-2 py-1\">\n              <span className=\"text-inherit\">\n                {value || (\n                  <span className=\"text-muted-foreground\">{placeholder}</span>\n                )}\n              </span>\n              {isHovered && (\n                <motion.span\n                  key={shimmerKey}\n                  className=\"pointer-events-none absolute inset-0\"\n                  style={{\n                    background:\n                      \"linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.12) 50%, transparent 100%)\",\n                  }}\n                  initial={{ x: \"-100%\" }}\n                  animate={{ x: \"100%\" }}\n                  transition={{ duration: 0.6, ease: \"easeInOut\" }}\n                />\n              )}\n            </span>\n\n            {/* Edit icon - slides out from the right */}\n            <motion.span\n              className=\"inline-flex items-center overflow-hidden text-muted-foreground\"\n              initial={false}\n              animate={\n                isHovered\n                  ? { width: 22, opacity: 1 }\n                  : { width: 0, opacity: 0 }\n              }\n              transition={{ duration: 0.2, ease: \"easeOut\" }}\n            >\n              <IconPencil size={14} />\n            </motion.span>\n          </div>\n        )}\n      </div>\n    )\n  }\n)\nEditableText.displayName = \"EditableText\"\n\nexport { EditableText }\nexport type { EditableTextProps }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}