{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "editable",
  "title": "Editable",
  "description": "An inline editable text component that allows users to edit text in place with customizable input or textarea modes.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://blok.sitecore.com/r/theme.json",
    "https://blok.sitecore.com/r/input.json",
    "https://blok.sitecore.com/r/textarea.json",
    "https://blok.sitecore.com/r/button.json"
  ],
  "files": [
    {
      "path": "src/components/ui/editable.tsx",
      "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport * as React from \"react\";\n\ntype ActivationMode = \"click\" | \"dblclick\";\n\ninterface EditableContextValue {\n  isEditing: boolean;\n  value: string;\n  placeholder: string;\n  isDisabled: boolean;\n  isPreviewFocusable: boolean;\n  submitOnBlur: boolean;\n  selectAllOnFocus: boolean;\n  activationMode: ActivationMode;\n  startEdit: () => void;\n  cancelEdit: () => void;\n  submitEdit: () => void;\n  handleChange: (value: string) => void;\n  inputRef: React.RefObject<HTMLInputElement | HTMLTextAreaElement | null>;\n}\n\nconst EditableContext = React.createContext<EditableContextValue | null>(null);\n\nfunction useEditableContext() {\n  const context = React.useContext(EditableContext);\n  if (!context) {\n    throw new Error(\n      \"Editable compound components must be used within an Editable component\",\n    );\n  }\n  return context;\n}\n\n// useEditable Hook\ninterface UseEditableProps {\n  /** The initial value (uncontrolled) */\n  defaultValue?: string;\n  /** The controlled value */\n  value?: string;\n  /** Placeholder text when empty */\n  placeholder?: string;\n  /** Whether the component is disabled */\n  isDisabled?: boolean;\n  /** Whether clicking the preview starts editing */\n  isPreviewFocusable?: boolean;\n  /** Whether to submit on blur */\n  submitOnBlur?: boolean;\n  /** Whether to start in edit mode */\n  startWithEditView?: boolean;\n  /** Whether to select all text on focus */\n  selectAllOnFocus?: boolean;\n  /** Activation mode: 'click' or 'dblclick' */\n  activationMode?: ActivationMode;\n  /** Error message to display */\n  hasError?: boolean;\n  /** Callback when value is submitted */\n  onSubmit?: (value: string) => void;\n  /** Callback when value changes */\n  onChange?: (value: string) => void;\n  /** Callback when value changes (alias for onChange) */\n  onValueChange?: (value: string) => void;\n  /** Callback when editing is cancelled */\n  onCancel?: (previousValue: string) => void;\n  /** Callback when entering edit mode */\n  onEdit?: () => void;\n}\n\ninterface UseEditableReturn extends EditableContextValue {\n  /** Whether the editable is currently in edit mode */\n  editing: boolean;\n}\n\nfunction useEditable(props: UseEditableProps = {}): UseEditableReturn {\n  const {\n    defaultValue = \"\",\n    value: controlledValue,\n    placeholder = \"Click to edit...\",\n    isDisabled = false,\n    isPreviewFocusable = true,\n    submitOnBlur = true,\n    startWithEditView = false,\n    selectAllOnFocus = true,\n    activationMode = \"click\",\n    hasError = false,\n    onSubmit,\n    onChange,\n    onValueChange,\n    onCancel,\n    onEdit,\n  } = props;\n\n  const [isEditing, setIsEditing] = React.useState(\n    startWithEditView || hasError,\n  );\n  const [internalValue, setInternalValue] = React.useState(defaultValue);\n  const [previousValue, setPreviousValue] = React.useState(defaultValue);\n  const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement | null>(\n    null,\n  );\n\n  // Support both controlled and uncontrolled modes\n  const isControlled = controlledValue !== undefined;\n  const value = isControlled ? controlledValue : internalValue;\n\n  const startEdit = React.useCallback(() => {\n    if (isDisabled) return;\n    setPreviousValue(value);\n    setIsEditing(true);\n    onEdit?.();\n  }, [isDisabled, value, onEdit]);\n\n  const cancelEdit = React.useCallback(() => {\n    if (!isControlled) {\n      setInternalValue(previousValue);\n    }\n    setIsEditing(hasError || false);\n    onCancel?.(previousValue);\n  }, [isControlled, previousValue, onCancel, hasError]);\n\n  const submitEdit = React.useCallback(() => {\n    setIsEditing(hasError || false);\n    onSubmit?.(value);\n  }, [value, onSubmit, hasError]);\n\n  const handleChange = React.useCallback(\n    (newValue: string) => {\n      if (!isControlled) {\n        setInternalValue(newValue);\n      }\n      onChange?.(newValue);\n      onValueChange?.(newValue);\n    },\n    [isControlled, onChange, onValueChange],\n  );\n\n  // Focus input when entering edit mode\n  React.useEffect(() => {\n    if (isEditing && inputRef.current) {\n      inputRef.current.focus();\n      if (selectAllOnFocus && inputRef.current instanceof HTMLInputElement) {\n        inputRef.current.select();\n      } else if (\n        selectAllOnFocus &&\n        inputRef.current instanceof HTMLTextAreaElement\n      ) {\n        inputRef.current.select();\n      }\n    }\n  }, [isEditing, selectAllOnFocus]);\n\n  return {\n    isEditing,\n    editing: isEditing, // Alias for Chakra compatibility\n    value,\n    placeholder,\n    isDisabled,\n    isPreviewFocusable,\n    submitOnBlur,\n    selectAllOnFocus,\n    activationMode,\n    startEdit,\n    cancelEdit,\n    submitEdit,\n    handleChange,\n    inputRef,\n  };\n}\n\n// Editable Root Component\n\nconst editableVariants = cva(\"inline-flex flex-col gap-1 relative\", {\n  variants: {\n    size: {\n      sm: \"text-sm\",\n      md: \"text-md\",\n      lg: \"text-lg\",\n    },\n  },\n  defaultVariants: {\n    size: \"md\",\n  },\n});\n\ninterface EditableProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\" | \"onSubmit\">,\n    VariantProps<typeof editableVariants> {\n  /** The initial value (uncontrolled) */\n  defaultValue?: string;\n  /** The controlled value */\n  value?: string;\n  /** Placeholder text when empty */\n  placeholder?: string;\n  /** Whether the component is disabled */\n  isDisabled?: boolean;\n  /** Whether clicking the preview starts editing */\n  isPreviewFocusable?: boolean;\n  /** Whether to submit on blur */\n  submitOnBlur?: boolean;\n  /** Whether to start in edit mode */\n  startWithEditView?: boolean;\n  /** Whether to select all text on focus */\n  selectAllOnFocus?: boolean;\n  /** Activation mode: 'click' or 'dblclick' */\n  activationMode?: ActivationMode;\n  /** Error message to display */\n  hasError?: boolean;\n  /** Callback when value is submitted */\n  onSubmit?: (value: string) => void;\n  /** Callback when value changes */\n  onChange?: (value: string) => void;\n  /** Callback when value changes (alias for onChange) */\n  onValueChange?: (value: string) => void;\n  /** Callback when editing is cancelled */\n  onCancel?: (previousValue: string) => void;\n  /** Callback when entering edit mode */\n  onEdit?: () => void;\n}\n\nfunction Editable({\n  className,\n  size,\n  defaultValue = \"\",\n  value: controlledValue,\n  placeholder = \"Click to edit...\",\n  isDisabled = false,\n  isPreviewFocusable = true,\n  submitOnBlur = true,\n  startWithEditView = false,\n  selectAllOnFocus = true,\n  activationMode = \"click\",\n  hasError = false,\n  onSubmit,\n  onChange,\n  onValueChange,\n  onCancel,\n  onEdit,\n  children,\n  ...props\n}: EditableProps) {\n  const [isEditing, setIsEditing] = React.useState(\n    startWithEditView || hasError,\n  );\n  const [internalValue, setInternalValue] = React.useState(defaultValue);\n  const [previousValue, setPreviousValue] = React.useState(defaultValue);\n  const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement | null>(\n    null,\n  );\n\n  // Support both controlled and uncontrolled modes\n  const isControlled = controlledValue !== undefined;\n  const value = isControlled ? controlledValue : internalValue;\n\n  const startEdit = React.useCallback(() => {\n    if (isDisabled) return;\n    setPreviousValue(value);\n    setIsEditing(true);\n    onEdit?.();\n  }, [isDisabled, value, onEdit]);\n\n  const cancelEdit = React.useCallback(() => {\n    if (!isControlled) {\n      setInternalValue(previousValue);\n    }\n    setIsEditing(hasError || false);\n    onCancel?.(previousValue);\n  }, [isControlled, previousValue, onCancel, hasError]);\n\n  const submitEdit = React.useCallback(() => {\n    setIsEditing(hasError || false);\n    onSubmit?.(value);\n  }, [value, onSubmit, hasError]);\n\n  const handleChange = React.useCallback(\n    (newValue: string) => {\n      if (!isControlled) {\n        setInternalValue(newValue);\n      }\n      onChange?.(newValue);\n      onValueChange?.(newValue);\n    },\n    [isControlled, onChange, onValueChange],\n  );\n\n  // Focus input when entering edit mode\n  React.useEffect(() => {\n    if (isEditing && inputRef.current) {\n      inputRef.current.focus();\n      if (selectAllOnFocus && inputRef.current instanceof HTMLInputElement) {\n        inputRef.current.select();\n      } else if (\n        selectAllOnFocus &&\n        inputRef.current instanceof HTMLTextAreaElement\n      ) {\n        inputRef.current.select();\n      }\n    }\n  }, [isEditing, selectAllOnFocus]);\n\n  const contextValue: EditableContextValue = {\n    isEditing,\n    value,\n    placeholder,\n    isDisabled,\n    isPreviewFocusable,\n    submitOnBlur,\n    selectAllOnFocus,\n    activationMode,\n    startEdit,\n    cancelEdit,\n    submitEdit,\n    handleChange,\n    inputRef,\n  };\n\n  return (\n    <EditableContext.Provider value={contextValue}>\n      <div\n        data-slot=\"editable\"\n        className={cn(editableVariants({ size }), className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </EditableContext.Provider>\n  );\n}\n\n// EditableRootProvider Component\n\ninterface EditableRootProviderProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\" | \"onSubmit\">,\n    VariantProps<typeof editableVariants> {\n  value: EditableContextValue;\n}\n\nfunction EditableRootProvider({\n  className,\n  size,\n  value,\n  children,\n  ...props\n}: EditableRootProviderProps) {\n  return (\n    <EditableContext.Provider value={value}>\n      <div\n        data-slot=\"editable\"\n        className={cn(editableVariants({ size }), className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </EditableContext.Provider>\n  );\n}\n\n// EditablePreview Component\n\nconst editablePreviewVariants = cva(\n  [\n    \"cursor-text rounded-md px-2 py-1 transition-colors\",\n    \"hover:bg-neutral-bg\",\n    \"min-h-8 flex items-center whitespace-pre-line break-words\",\n  ].join(\" \"),\n  {\n    variants: {\n      isEmpty: {\n        true: \"text-foreground\",\n        false: \"text-foreground\",\n      },\n    },\n    defaultVariants: {\n      isEmpty: false,\n    },\n  },\n);\n\ninterface EditablePreviewProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nfunction EditablePreview({ className, ...props }: EditablePreviewProps) {\n  const {\n    isEditing,\n    value,\n    placeholder,\n    isDisabled,\n    isPreviewFocusable,\n    activationMode,\n    startEdit,\n  } = useEditableContext();\n\n  if (isEditing) {\n    return null;\n  }\n\n  const isEmpty = !value || value.trim() === \"\";\n  const displayValue = isEmpty ? placeholder : value;\n\n  const handleClick =\n    activationMode === \"click\" && isPreviewFocusable && !isDisabled\n      ? startEdit\n      : undefined;\n  const handleDoubleClick =\n    activationMode === \"dblclick\" && isPreviewFocusable && !isDisabled\n      ? startEdit\n      : undefined;\n\n  return (\n    <span\n      data-slot=\"editable-preview\"\n      role={isPreviewFocusable && !isDisabled ? \"button\" : undefined}\n      tabIndex={isPreviewFocusable && !isDisabled ? 0 : undefined}\n      onClick={handleClick}\n      onDoubleClick={handleDoubleClick}\n      onKeyDown={(e) => {\n        if (\n          isPreviewFocusable &&\n          !isDisabled &&\n          (e.key === \"Enter\" || e.key === \" \")\n        ) {\n          e.preventDefault();\n          startEdit();\n        }\n      }}\n      className={cn(\n        editablePreviewVariants({ isEmpty }),\n        isDisabled && \"cursor-not-allowed opacity-50\",\n        !isPreviewFocusable && \"cursor-default hover:bg-transparent\",\n        className,\n      )}\n      {...props}\n    >\n      {displayValue}\n    </span>\n  );\n}\n\n// EditableInput Component\n\ninterface EditableInputProps\n  extends Omit<React.ComponentProps<typeof Input>, \"value\" | \"onChange\"> {}\n\nfunction EditableInput({ className, ...props }: EditableInputProps) {\n  const {\n    isEditing,\n    value,\n    placeholder,\n    isDisabled,\n    submitOnBlur,\n    handleChange,\n    submitEdit,\n    cancelEdit,\n    inputRef,\n  } = useEditableContext();\n\n  if (!isEditing) {\n    return null;\n  }\n\n  return (\n    <Input\n      ref={inputRef as React.RefObject<HTMLInputElement>}\n      data-slot=\"editable-input\"\n      value={value}\n      placeholder={placeholder}\n      disabled={isDisabled}\n      onChange={(e) => handleChange(e.target.value)}\n      onBlur={() => {\n        if (submitOnBlur) {\n          submitEdit();\n        }\n      }}\n      onKeyDown={(e) => {\n        if (e.key === \"Enter\") {\n          e.preventDefault();\n          submitEdit();\n        } else if (e.key === \"Escape\") {\n          e.preventDefault();\n          cancelEdit();\n        }\n      }}\n      className={cn(\n        \"w-full border-2 bg-transparent h-8 dark:bg-transparent focus-visible:ring-0 focus-visible:border-primary transition-colors\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n// EditableTextarea Component\ninterface EditableTextareaProps\n  extends Omit<React.ComponentProps<typeof Textarea>, \"value\" | \"onChange\"> {}\n\nfunction EditableTextarea({ className, ...props }: EditableTextareaProps) {\n  const {\n    isEditing,\n    value,\n    placeholder,\n    isDisabled,\n    submitOnBlur,\n    handleChange,\n    submitEdit,\n    cancelEdit,\n    inputRef,\n  } = useEditableContext();\n\n  if (!isEditing) {\n    return null;\n  }\n\n  return (\n    <Textarea\n      ref={inputRef as React.RefObject<HTMLTextAreaElement>}\n      data-slot=\"editable-textarea\"\n      value={value}\n      placeholder={placeholder}\n      disabled={isDisabled}\n      onChange={(e) => handleChange(e.target.value)}\n      onBlur={() => {\n        if (submitOnBlur) {\n          submitEdit();\n        }\n      }}\n      onKeyDown={(e) => {\n        // For textarea, Ctrl/Cmd + Enter submits, Escape cancels\n        if (e.key === \"Enter\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          submitEdit();\n        } else if (e.key === \"Escape\") {\n          e.preventDefault();\n          cancelEdit();\n        }\n      }}\n      className={cn(\n        \"w-36 border-2 bg-transparent dark:bg-transparent focus-visible:ring-0 focus-visible:border-primary transition-colors\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n// EditableControl Component\n\ninterface EditableControlProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nfunction EditableControl({\n  className,\n  children,\n  ...props\n}: EditableControlProps) {\n  return (\n    <div\n      data-slot=\"editable-control\"\n      className={cn(\"inline-flex items-center gap-1\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\n// EditableEditTrigger Component\n\ninterface EditableEditTriggerProps\n  extends React.ComponentProps<typeof Button> {}\n\nfunction EditableEditTrigger({\n  className,\n  children,\n  ...props\n}: EditableEditTriggerProps) {\n  const { isEditing, isDisabled, startEdit } = useEditableContext();\n\n  if (isEditing) {\n    return null;\n  }\n\n  return (\n    <Button\n      data-slot=\"editable-edit-trigger\"\n      type=\"button\"\n      variant=\"ghost\"\n      size=\"sm\"\n      disabled={isDisabled}\n      onClick={startEdit}\n      className={cn(className)}\n      {...props}\n    >\n      {children ?? \"Edit\"}\n    </Button>\n  );\n}\n\n// EditableCancelTrigger Component\n\ninterface EditableCancelTriggerProps\n  extends React.ComponentProps<typeof Button> {}\n\nfunction EditableCancelTrigger({\n  className,\n  children,\n  ...props\n}: EditableCancelTriggerProps) {\n  const { isEditing, cancelEdit } = useEditableContext();\n\n  if (!isEditing) {\n    return null;\n  }\n\n  return (\n    <Button\n      data-slot=\"editable-cancel-trigger\"\n      type=\"button\"\n      variant=\"ghost\"\n      size=\"sm\"\n      onClick={cancelEdit}\n      className={cn(className)}\n      {...props}\n    >\n      {children ?? \"Cancel\"}\n    </Button>\n  );\n}\n\n// EditableSubmitTrigger Component\n\ninterface EditableSubmitTriggerProps\n  extends React.ComponentProps<typeof Button> {}\n\nfunction EditableSubmitTrigger({\n  className,\n  children,\n  ...props\n}: EditableSubmitTriggerProps) {\n  const { isEditing, submitEdit } = useEditableContext();\n\n  if (!isEditing) {\n    return null;\n  }\n\n  return (\n    <Button\n      data-slot=\"editable-submit-trigger\"\n      type=\"button\"\n      variant=\"default\"\n      size=\"sm\"\n      onClick={submitEdit}\n      className={cn(className)}\n      {...props}\n    >\n      {children ?? \"Save\"}\n    </Button>\n  );\n}\n\ninterface EditableErrorProps extends React.HTMLAttributes<HTMLDivElement> {\n  errors?: { message?: string }[];\n}\n\nfunction EditableError({\n  errors,\n  children,\n  className,\n  ...props\n}: EditableErrorProps) {\n  const errorMessages = errors?.filter(Boolean) || [];\n\n  if (errorMessages.length === 0 && !children) {\n    return null;\n  }\n\n  return (\n    <div\n      role=\"alert\"\n      aria-live=\"polite\"\n      data-slot=\"editable-error\"\n      className={cn(\n        \"text-sm text-destructive absolute w-max bg-white rounded-sm shadow-lg py-1 px-2 bottom-[calc(-100%+var(--spacing)*0.5)] cursor-default z-10\",\n        className,\n      )}\n      {...props}\n    >\n      {children ||\n        (errorMessages.length === 1 ? (\n          <span>{errorMessages[0]?.message}</span>\n        ) : (\n          <ul className=\"list-disc list-inside space-y-1\">\n            {errorMessages.map((error, index) => (\n              <li key={index}>{error?.message}</li>\n            ))}\n          </ul>\n        ))}\n    </div>\n  );\n}\n\nexport {\n  Editable,\n  EditableRootProvider,\n  EditablePreview,\n  EditableInput,\n  EditableTextarea,\n  EditableControl,\n  EditableEditTrigger,\n  EditableCancelTrigger,\n  EditableSubmitTrigger,\n  EditableError,\n  editableVariants,\n  editablePreviewVariants,\n  useEditable,\n  useEditableContext,\n  type EditableProps,\n  type EditableRootProviderProps,\n  type EditableContextValue,\n  type UseEditableProps,\n  type UseEditableReturn,\n  type ActivationMode,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}