{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ds-inline-edit",
  "title": "Inline Edit",
  "description": "Edição in-place com click-to-edit, estados de salvamento e validação inline.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "@lema-ds/input",
    "@lema-ds/button",
    "@lema-ds/spinner",
    "@lema-ds/ui-i18n",
    "@lema-ds/ds-locale-provider"
  ],
  "files": [
    {
      "path": "components/ds/ds-inline-edit.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Pencil, Check, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Input } from \"@/components/ui/input\"\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { UI_I18N, type UILocale } from \"@/lib/ui-i18n\"\nimport { useUILocale } from \"@/components/ui/ds-locale-provider\"\n\n// ── Variants ──\n\nexport const inlineEditViewVariants = cva(\n  \"group inline-flex cursor-pointer items-center gap-2 rounded-md transition-colors select-none hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none\",\n  {\n    variants: {\n      size: {\n        sm: \"min-h-7 px-2 py-0.5 text-xs\",\n        md: \"min-h-8 px-2.5 py-1 text-sm\",\n        lg: \"min-h-9 px-3 py-1.5 text-base\",\n      },\n    },\n    defaultVariants: {\n      size: \"md\",\n    },\n  }\n)\n\n// ── Types ──\n\nexport interface InlineEditProps\n  extends\n    Omit<React.HTMLAttributes<HTMLDivElement>, \"onSave\" | \"defaultValue\">,\n    VariantProps<typeof inlineEditViewVariants> {\n  /**\n   * The current text value.\n   */\n  value: string\n  /**\n   * Callback fired when value is confirmed/saved.\n   */\n  onSave: (newValue: string) => void | Promise<void>\n  /**\n   * Placeholder shown when the value is empty.\n   */\n  placeholder?: string\n  /**\n   * Validation function returning an error string or undefined if valid.\n   */\n  validate?: (val: string) => string | undefined\n  /**\n   * Whether editing is disabled.\n   */\n  disabled?: boolean\n  /**\n   * Locale for tooltips and button labels.\n   */\n  locale?: UILocale\n}\n\n// ── Component ──\n\nexport const InlineEdit = React.forwardRef<HTMLDivElement, InlineEditProps>(\n  (\n    {\n      value,\n      onSave,\n      placeholder,\n      validate,\n      disabled = false,\n      size = \"md\",\n      locale: localeProp,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const locale = useUILocale(localeProp)\n    const [isEditing, setIsEditing] = React.useState(false)\n    const [currentValue, setCurrentValue] = React.useState(value)\n    const [isSaving, setIsSaving] = React.useState(false)\n    const [error, setError] = React.useState<string | undefined>()\n\n    const inputRef = React.useRef<HTMLInputElement>(null)\n    const t = UI_I18N[locale].inlineEdit\n\n    React.useEffect(() => {\n      setCurrentValue(value)\n    }, [value])\n\n    React.useEffect(() => {\n      if (isEditing && inputRef.current) {\n        inputRef.current.focus()\n        inputRef.current.select()\n      }\n    }, [isEditing])\n\n    const startEditing = () => {\n      if (disabled || isSaving) return\n      setError(undefined)\n      setCurrentValue(value)\n      setIsEditing(true)\n    }\n\n    const cancelEditing = () => {\n      setIsEditing(false)\n      setCurrentValue(value)\n      setError(undefined)\n    }\n\n    const handleSave = async () => {\n      if (validate) {\n        const err = validate(currentValue)\n        if (err) {\n          setError(err)\n          return\n        }\n      }\n      setError(undefined)\n\n      if (currentValue === value) {\n        setIsEditing(false)\n        return\n      }\n\n      try {\n        setIsSaving(true)\n        await onSave(currentValue)\n        setIsEditing(false)\n      } catch (err) {\n        setError(err instanceof Error ? err.message : \"Error saving\")\n      } finally {\n        setIsSaving(false)\n      }\n    }\n\n    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault()\n        handleSave()\n      } else if (e.key === \"Escape\") {\n        e.preventDefault()\n        cancelEditing()\n      }\n    }\n\n    const iconSize =\n      size === \"sm\" ? \"size-3.5\" : size === \"lg\" ? \"size-5\" : \"size-4\"\n    const buttonHeight = size === \"sm\" ? \"h-7\" : size === \"lg\" ? \"h-9\" : \"h-8\"\n    const displayPlaceholder = placeholder ?? t.placeholder\n\n    if (isEditing) {\n      return (\n        <div\n          ref={ref}\n          className={cn(\"flex w-full max-w-sm flex-col gap-1\", className)}\n          {...props}\n        >\n          <div className=\"flex items-center gap-1.5\">\n            <Input\n              ref={inputRef}\n              value={currentValue}\n              disabled={isSaving}\n              aria-label={props[\"aria-label\"] ?? (placeholder || \"Edit text\")}\n              onChange={(e) => {\n                setCurrentValue(e.target.value)\n                if (error) setError(undefined)\n              }}\n              onKeyDown={handleKeyDown}\n              className={cn(\n                buttonHeight,\n                size === \"sm\" && \"text-xs\",\n                size === \"md\" && \"text-sm\",\n                size === \"lg\" && \"text-base\",\n                error && \"border-destructive focus-visible:ring-destructive\"\n              )}\n            />\n            <Button\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon\"\n              disabled={isSaving}\n              onClick={handleSave}\n              className={cn(buttonHeight, \"w-8 shrink-0\")}\n              aria-label={t.save}\n            >\n              {isSaving ? (\n                <Spinner className={iconSize} />\n              ) : (\n                <Check className={iconSize} />\n              )}\n            </Button>\n            <Button\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon\"\n              disabled={isSaving}\n              onClick={cancelEditing}\n              className={cn(\n                buttonHeight,\n                \"w-8 shrink-0 text-muted-foreground hover:text-destructive\"\n              )}\n              aria-label={t.cancel}\n            >\n              <X className={iconSize} />\n            </Button>\n          </div>\n          {error && (\n            <span className=\"px-1 text-xs font-medium text-destructive\">\n              {error}\n            </span>\n          )}\n        </div>\n      )\n    }\n\n    return (\n      <div\n        ref={ref}\n        role=\"button\"\n        tabIndex={disabled ? -1 : 0}\n        aria-label={t.clickToEdit}\n        onClick={startEditing}\n        onKeyDown={(e) => {\n          if (e.key === \"Enter\" || e.key === \" \") {\n            e.preventDefault()\n            startEditing()\n          }\n        }}\n        className={cn(\n          inlineEditViewVariants({ size }),\n          disabled && \"pointer-events-none cursor-not-allowed opacity-50\",\n          className\n        )}\n        {...props}\n      >\n        <span\n          className={cn(\n            \"truncate font-medium text-foreground\",\n            !value && \"font-normal text-muted-foreground italic\"\n          )}\n        >\n          {value || displayPlaceholder}\n        </span>\n        {!disabled && (\n          <Pencil\n            className={cn(\n              iconSize,\n              \"text-muted-foreground/60 opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100\"\n            )}\n          />\n        )}\n      </div>\n    )\n  }\n)\n\nInlineEdit.displayName = \"InlineEdit\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
