{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gummy-input",
  "type": "registry:ui",
  "title": "Gummy Input",
  "description": "Native labelled Input with descriptions, validation, adornments, filled state, disabled and read-only behaviour, and stable editing material.",
  "registryDependencies": [
    "https://gummyui.dev/r/gummy-core-styles.json"
  ],
  "files": [
    {
      "path": "components/ui/GummyInput.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport type GummyInputProps = Omit<\n  React.InputHTMLAttributes<HTMLInputElement>,\n  \"size\"\n> & {\n  /** Visible label programmatically associated with the native input. */\n  label: React.ReactNode;\n  /** Supporting copy associated through aria-describedby. */\n  description?: React.ReactNode;\n  /** Error feedback; takes precedence over success feedback. */\n  errorMessage?: React.ReactNode;\n  /** Positive feedback that supplements, rather than replaces, colour. */\n  successMessage?: React.ReactNode;\n  /** Decorative content placed before the editing surface. */\n  leadingAdornment?: React.ReactNode;\n  /** Decorative content placed after the editing surface. */\n  trailingAdornment?: React.ReactNode;\n  /** Class applied to the outer field composition. */\n  wrapperClassName?: string;\n};\n\nfunction joinClassNames(...values: Array<string | false | null | undefined>) {\n  return values.filter(Boolean).join(\" \");\n}\n\nfunction hasInputValue(value: unknown) {\n  return value !== undefined && value !== null && String(value).length > 0;\n}\n\nexport const GummyInput = React.forwardRef<HTMLInputElement, GummyInputProps>(\n  function GummyInput(\n    {\n      label,\n      description,\n      errorMessage,\n      successMessage,\n      leadingAdornment,\n      trailingAdornment,\n      wrapperClassName,\n      className,\n      id: providedId,\n      required,\n      disabled,\n      readOnly,\n      value,\n      defaultValue,\n      onChange,\n      \"aria-describedby\": ariaDescribedBy,\n      \"aria-errormessage\": ariaErrorMessage,\n      \"aria-invalid\": ariaInvalid,\n      ...inputProps\n    },\n    ref,\n  ) {\n    const generatedId = React.useId();\n    const inputId = providedId ?? `gummy-input-${generatedId.replace(/:/g, \"\")}`;\n    const descriptionId = description ? `${inputId}-description` : undefined;\n    const errorId = errorMessage ? `${inputId}-error` : undefined;\n    const successId = !errorMessage && successMessage\n      ? `${inputId}-success`\n      : undefined;\n    const describedBy = [\n      ariaDescribedBy,\n      descriptionId,\n      errorId,\n      successId,\n    ]\n      .filter(Boolean)\n      .join(\" \") || undefined;\n    const status = errorMessage ? \"error\" : successMessage ? \"success\" : \"default\";\n    const [filled, setFilled] = React.useState(\n      hasInputValue(value) || hasInputValue(defaultValue),\n    );\n\n    React.useEffect(() => {\n      if (value !== undefined) setFilled(hasInputValue(value));\n    }, [value]);\n\n    return (\n      <div\n        className={joinClassNames(\"gummy-input\", wrapperClassName)}\n        data-status={status}\n        data-disabled={disabled || undefined}\n        data-read-only={readOnly || undefined}\n        data-filled={filled || undefined}\n      >\n        <div className=\"gummy-input__label-row\">\n          <label className=\"gummy-input__label\" htmlFor={inputId}>\n            {label}\n          </label>\n          {required ? <span className=\"gummy-input__required\">Required</span> : null}\n          {readOnly ? <span className=\"gummy-input__read-only\">Read only</span> : null}\n        </div>\n\n        <div className=\"gummy-input__control\">\n          {leadingAdornment ? (\n            <span className=\"gummy-input__adornment\" aria-hidden=\"true\">\n              {leadingAdornment}\n            </span>\n          ) : null}\n          <input\n            {...inputProps}\n            ref={ref}\n            id={inputId}\n            className={joinClassNames(\"gummy-input__field\", className)}\n            required={required}\n            disabled={disabled}\n            readOnly={readOnly}\n            value={value}\n            defaultValue={defaultValue}\n            aria-describedby={describedBy}\n            aria-errormessage={ariaErrorMessage ?? errorId}\n            aria-invalid={ariaInvalid ?? (errorMessage ? true : undefined)}\n            onChange={(event) => {\n              setFilled(hasInputValue(event.currentTarget.value));\n              onChange?.(event);\n            }}\n          />\n          {trailingAdornment ? (\n            <span className=\"gummy-input__adornment\" aria-hidden=\"true\">\n              {trailingAdornment}\n            </span>\n          ) : null}\n          {status !== \"default\" ? (\n            <span className=\"gummy-input__status-icon\" aria-hidden=\"true\">\n              {status === \"error\" ? \"!\" : \"✓\"}\n            </span>\n          ) : null}\n        </div>\n\n        {description ? (\n          <p className=\"gummy-input__message\" id={descriptionId}>\n            {description}\n          </p>\n        ) : null}\n        {errorMessage ? (\n          <p className=\"gummy-input__message gummy-input__message--error\" id={errorId} role=\"alert\">\n            <span className=\"gummy-input__message-mark\" aria-hidden=\"true\" />\n            <span>{errorMessage}</span>\n          </p>\n        ) : successId ? (\n          <p className=\"gummy-input__message gummy-input__message--success\" id={successId}>\n            <span className=\"gummy-input__message-mark\" aria-hidden=\"true\" />\n            <span>{successMessage}</span>\n          </p>\n        ) : null}\n      </div>\n    );\n  },\n);\n\nGummyInput.displayName = \"GummyInput\";\n"
    }
  ]
}
