{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "textarea",
  "type": "registry:component",
  "title": "Textarea",
  "description": "A field for more than one line that grows as it is written in, up to a limit.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/panelui/textarea.tsx",
      "type": "registry:component",
      "target": "@components/panelui/textarea.tsx",
      "content": "'use client';\n\n/**\n * Textarea — a field for more than one line, that grows as it is written in.\n *\n * ```tsx\n * <Textarea placeholder=\"What happened?\" />\n * <Textarea minRows={1} maxRows={6} variant=\"ghost\" />\n * ```\n *\n * ## Growing\n *\n * It starts `minRows` lines tall and grows a line at a time as text is\n * written, up to `maxRows`. Past that it scrolls, with a thin scrollbar of its\n * own rather than the browser's. Pass `autoResize={false}` for a field that\n * keeps the size `rows` gives it and can be resized by hand.\n *\n * The height is measured from the text, so it is right on the first render\n * for a value that was already there, and for a value set from outside.\n *\n * ## Ghost\n *\n * `variant=\"ghost\"` drops the border, background and ring, for a field set\n * inside something that already draws them, such as a card or a composer.\n *\n * ## Inside a Field\n *\n * Put in a `Field`, it takes the field's label, description and error without\n * ids passed by hand, and turns invalid when the field does.\n */\nimport { Field as BaseField } from '@base-ui/react/field';\nimport { useCallback, useLayoutEffect, useRef, type ComponentProps, type Ref } from 'react';\nimport { tv } from 'tailwind-variants';\n\nconst textareaVariants = tv({\n  base: [\n    'block w-full min-w-0 bg-transparent text-foreground outline-none',\n    'placeholder:text-muted-foreground/80 selection:bg-primary selection:text-primary-foreground',\n    'disabled:cursor-not-allowed disabled:opacity-50',\n    // A thin bar in the theme's border colour, never the platform's wide one.\n    '[scrollbar-color:var(--border)_transparent] [scrollbar-width:thin]',\n  ],\n  variants: {\n    variant: {\n      default: [\n        'rounded-lg border border-input bg-background shadow-xs transition-[border-color,box-shadow] dark:bg-input/30',\n        'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/30',\n        'aria-invalid:border-destructive aria-invalid:ring-destructive/20',\n      ],\n      ghost: 'rounded-none border-0 p-0 shadow-none',\n    },\n    size: {\n      sm: 'text-[0.8125rem] leading-5',\n      md: 'text-sm leading-6',\n      lg: 'text-base leading-7',\n    },\n    autoResize: {\n      true: 'resize-none',\n      false: 'resize-y',\n    },\n  },\n  compoundVariants: [\n    { variant: 'default', size: 'sm', className: 'px-2.5 py-1.5' },\n    { variant: 'default', size: 'md', className: 'px-3 py-2' },\n    { variant: 'default', size: 'lg', className: 'px-3.5 py-2.5' },\n  ],\n  defaultVariants: { variant: 'default', size: 'md', autoResize: true },\n});\n\nexport interface TextareaProps extends Omit<ComponentProps<'textarea'>, 'size'> {\n  /** `ghost` draws no box, for a field inside something that draws one. Default `default`. */\n  variant?: 'default' | 'ghost';\n  /** Its text size and line height. Default `md`. */\n  size?: 'sm' | 'md' | 'lg';\n  /** Whether it grows with what is written in it. Default `true`. */\n  autoResize?: boolean;\n  /** How many lines tall it starts, while growing. Default `2`. */\n  minRows?: number;\n  /** How many lines tall it can grow before it scrolls. Default `8`. */\n  maxRows?: number;\n}\n\nfunction setRef<T>(ref: Ref<T> | undefined, value: T | null) {\n  if (typeof ref === 'function') ref(value);\n  else if (ref) (ref as { current: T | null }).current = value;\n}\n\n/** A multi-line field. Every attribute of `<textarea>` passes through. */\nfunction TextareaRoot({\n  variant,\n  size,\n  autoResize = true,\n  minRows = 2,\n  maxRows = 8,\n  rows,\n  value,\n  onInput,\n  className,\n  style,\n  ref,\n  ...props\n}: TextareaProps) {\n  const field = useRef<HTMLTextAreaElement>(null);\n\n  // Set to one row, read how tall the text wants to be, and clamp that to the\n  // rows allowed. The padding and border are part of both ends of the clamp.\n  const fit = useCallback(() => {\n    const node = field.current;\n    if (!node || !autoResize) return;\n    const computed = getComputedStyle(node);\n    const line = parseFloat(computed.lineHeight) || 20;\n    const frame =\n      parseFloat(computed.paddingTop) +\n      parseFloat(computed.paddingBottom) +\n      parseFloat(computed.borderTopWidth) +\n      parseFloat(computed.borderBottomWidth);\n    node.style.height = 'auto';\n    const wanted = node.scrollHeight + parseFloat(computed.borderTopWidth) + parseFloat(computed.borderBottomWidth);\n    const min = line * minRows + frame;\n    const max = line * Math.max(maxRows, minRows) + frame;\n    node.style.height = `${Math.min(Math.max(wanted, min), max)}px`;\n    node.style.overflowY = wanted > max ? 'auto' : 'hidden';\n  }, [autoResize, minRows, maxRows]);\n\n  // Before paint, so a value that was already there never shows at the wrong height.\n  useLayoutEffect(fit, [fit, value]);\n\n  const control = {\n    'data-slot': 'textarea',\n    rows: autoResize ? minRows : rows,\n    value,\n    onInput: (event: Parameters<NonNullable<TextareaProps['onInput']>>[0]) => {\n      onInput?.(event);\n      fit();\n    },\n    className: textareaVariants({ variant, size, autoResize, className }),\n    style,\n    ...props,\n  };\n\n  // Rendered through the field's control, so a Field around it can label,\n  // describe and validate it. Outside a Field it is a plain textarea.\n  return (\n    <BaseField.Control\n      ref={(node: HTMLElement | null) => {\n        field.current = node as HTMLTextAreaElement | null;\n        setRef(ref, node as HTMLTextAreaElement | null);\n      }}\n      render={<textarea />}\n      {...(control as unknown as BaseField.Control.Props)}\n    />\n  );\n}\nTextareaRoot.displayName = 'Textarea';\n\nexport const Textarea = TextareaRoot;\n"
    }
  ]
}
