{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "autosize-textarea",
  "title": "Autosize Textarea",
  "description": "A textarea that grows with its content via CSS field-sizing with a scrollHeight-based JS fallback, capped between minRows and maxRows.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/autosize-textarea.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n/** SSR-safe: `CSS` doesn't exist in Node, so this stays `false` until it's read on the client. */\nconst supportsFieldSizing =\n  typeof CSS !== \"undefined\" && typeof CSS.supports === \"function\" && CSS.supports(\"field-sizing\", \"content\")\n\nexport interface AutosizeTextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {\n  /** Rows visible before the textarea starts growing. */\n  minRows?: number\n  /** Rows it grows to before switching to internal scrolling. */\n  maxRows?: number\n}\n\ninterface Metrics {\n  lineHeight: number\n  verticalPadding: number\n  verticalBorder: number\n}\n\n/** Reads line-height/padding/border once per element so minRows/maxRows convert to real pixels. */\nfunction measure(el: HTMLTextAreaElement): Metrics {\n  const style = window.getComputedStyle(el)\n  let lineHeight = parseFloat(style.lineHeight)\n  if (Number.isNaN(lineHeight)) lineHeight = parseFloat(style.fontSize) * 1.2\n  return {\n    lineHeight,\n    verticalPadding: parseFloat(style.paddingTop) + parseFloat(style.paddingBottom),\n    verticalBorder: parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth),\n  }\n}\n\nexport const AutosizeTextarea = React.forwardRef<HTMLTextAreaElement, AutosizeTextareaProps>(\n  ({ minRows = 2, maxRows = 8, rows, className, style, onInput, value, defaultValue, ...props }, ref) => {\n    const innerRef = React.useRef<HTMLTextAreaElement | null>(null)\n    const metricsRef = React.useRef<Metrics | null>(null)\n\n    const setRefs = React.useCallback(\n      (node: HTMLTextAreaElement | null) => {\n        innerRef.current = node\n        if (typeof ref === \"function\") ref(node)\n        else if (ref) ref.current = node\n      },\n      [ref],\n    )\n\n    // Writes the DOM directly rather than setState: where field-sizing is supported,\n    // min/max-height is all it takes and the browser does the growing; otherwise it is\n    // height:auto → measure scrollHeight → clamp to [minHeight, maxHeight] every time.\n    const resize = React.useCallback(() => {\n      const el = innerRef.current\n      if (!el) return\n      if (!metricsRef.current) metricsRef.current = measure(el)\n      const { lineHeight, verticalPadding, verticalBorder } = metricsRef.current\n      const box = verticalPadding + verticalBorder\n      const minHeight = lineHeight * minRows + box\n      const maxHeight = lineHeight * maxRows + box\n\n      el.style.minHeight = `${minHeight}px`\n      el.style.maxHeight = `${maxHeight}px`\n\n      if (supportsFieldSizing) {\n        // Set imperatively in a post-mount effect, never through the JSX style prop:\n        // CSS.supports is always false during SSR, so declaring it in JSX is a\n        // hydration mismatch — and this leaf never re-renders after mount, so once\n        // mismatched the style would never be applied at all.\n        el.style.setProperty(\"field-sizing\", \"content\")\n        el.style.overflowY = \"auto\"\n        return\n      }\n\n      el.style.height = \"auto\"\n      const next = Math.min(Math.max(el.scrollHeight, minHeight), maxHeight)\n      el.style.height = `${next}px`\n      el.style.overflowY = el.scrollHeight > maxHeight ? \"auto\" : \"hidden\"\n    }, [minRows, maxRows])\n\n    // Measure once on mount, and again when a controlled value changes — a\n    // programmatic assignment fires no input event. Only DOM measurement and style\n    // writes, no setState, so an effect is the right place for it.\n    React.useLayoutEffect(() => {\n      resize()\n    }, [resize, value])\n\n    const handleInput: React.InputEventHandler<HTMLTextAreaElement> = e => {\n      resize()\n      onInput?.(e)\n    }\n\n    return (\n      <textarea\n        className={cn(\n          \"flex w-full min-w-0 resize-none rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none\",\n          \"placeholder:text-muted-foreground\",\n          \"focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50\",\n          \"disabled:cursor-not-allowed disabled:opacity-50\",\n          \"aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20\",\n          \"md:text-sm\",\n          className,\n        )}\n        defaultValue={defaultValue}\n        onInput={handleInput}\n        ref={setRefs}\n        rows={rows ?? minRows}\n        style={style}\n        value={value}\n        {...props}\n      />\n    )\n  },\n)\n\nAutosizeTextarea.displayName = \"AutosizeTextarea\"\n\nexport default AutosizeTextarea\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}