{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "textarea",
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "textarea.tsx",
      "target": "components/ui/textarea.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst useIsomorphicLayoutEffect =\n  typeof window === \"undefined\" ? React.useEffect : React.useLayoutEffect;\n\n/** Computed styles come back empty for anything unset, and `normal` is not a number. */\nconst px = (value: string): number => Number.parseFloat(value) || 0;\n\n/** React 18 types name this event FormEvent and React 19 types name it InputEvent. */\ntype InputHandler = NonNullable<\n  React.TextareaHTMLAttributes<HTMLTextAreaElement>[\"onInput\"]\n>;\n\nexport interface TextareaProps\n  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {\n  /** Grow with the content instead of scrolling, bounded by minRows and maxRows. */\n  autoGrow?: boolean;\n  /** Shortest the field ever gets. Also seeds the `rows` attribute under autoGrow. */\n  minRows?: number;\n  /** Tallest the field gets before it starts scrolling. Unbounded when unset. */\n  maxRows?: number;\n}\n\nexport const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n  (\n    { className, autoGrow = false, minRows = 2, maxRows, rows, ...props },\n    ref,\n  ) => {\n    const inner = React.useRef<HTMLTextAreaElement | null>(null);\n\n    const attach = React.useCallback(\n      (node: HTMLTextAreaElement | null) => {\n        inner.current = node;\n        if (typeof ref === \"function\") ref(node);\n        else if (ref) ref.current = node;\n      },\n      [ref],\n    );\n\n    const measure = React.useCallback(() => {\n      const el = inner.current;\n      if (!el || !autoGrow) return;\n\n      const styles = window.getComputedStyle(el);\n      const lineHeight =\n        px(styles.lineHeight) || px(styles.fontSize) * 1.5 || 20;\n      const borders = px(styles.borderTopWidth) + px(styles.borderBottomWidth);\n      const chrome = px(styles.paddingTop) + px(styles.paddingBottom) + borders;\n\n      const min = lineHeight * minRows + chrome;\n      const max = maxRows\n        ? lineHeight * maxRows + chrome\n        : Number.POSITIVE_INFINITY;\n\n      el.style.height = \"auto\";\n      const natural = el.scrollHeight + borders;\n      el.style.height = `${Math.min(Math.max(natural, min), max)}px`;\n      el.style.overflowY = natural > max ? \"auto\" : \"hidden\";\n    }, [autoGrow, minRows, maxRows]);\n\n    useIsomorphicLayoutEffect(measure, [measure, props.value]);\n\n    const handleInput: InputHandler = (event) => {\n      props.onInput?.(event);\n      measure();\n    };\n\n    return (\n      <textarea\n        ref={attach}\n        rows={rows ?? (autoGrow ? minRows : undefined)}\n        className={cn(\n          \"w-full rounded-lg border border-border bg-bg px-3 py-2 text-sm text-ink placeholder:text-muted\",\n          \"outline-none transition-control duration-base ease-soft\",\n          \"hover:border-border-strong\",\n          \"focus-visible:border-transparent focus-visible:ring-focus\",\n          \"aria-invalid:border-danger aria-invalid:ring-2 aria-invalid:ring-danger/40\",\n          \"disabled:cursor-not-allowed disabled:opacity-50\",\n          autoGrow ? \"resize-none\" : \"resize-y\",\n          className,\n        )}\n        {...props}\n        onInput={autoGrow ? handleInput : props.onInput}\n      />\n    );\n  },\n);\nTextarea.displayName = \"Textarea\";\n"
    }
  ]
}