'use client' import * as React from 'react' import { Field as BaseField } from '@base-ui/react/field' import { type VariantProps } from 'class-variance-authority' import { cn, useComposedRefs } from '../../internal/utils' import { inputVariants } from '../input/input-variants' type BaseControlProps = React.ComponentProps type TextareaVariant = NonNullable['variant']> type TextareaSize = NonNullable['size']> interface TextareaProps extends Omit, 'size'> { /** * Field appearance - bordered or filled. * @default 'outline' */ variant?: TextareaVariant /** * Scales padding and text. Named `inputSize` to avoid colliding with native attributes. * @default 'md' */ inputSize?: TextareaSize /** * Show a clear (✕) button once the field has a value. * @default false */ clearable?: boolean /** Adornment rendered before the field, aligned to the first line. */ startSlot?: React.ReactNode /** Adornment rendered after the field, aligned to the first line. */ endSlot?: React.ReactNode /** Called when the clear button is pressed. */ onClear?: () => void } const sizePaddingY: Record = { sm: 'py-2', md: 'py-2.5', lg: 'py-3', } const sizeMinHeight: Record = { sm: 'min-h-16', md: 'min-h-20', lg: 'min-h-24', } const sizePaddingYRem: Record = { sm: '0.5rem', md: '0.625rem', lg: '0.75rem', } function setNativeValue(textarea: HTMLTextAreaElement, value: string) { const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set setter?.call(textarea, value) textarea.dispatchEvent(new Event('input', { bubbles: true })) } function Textarea({ className, variant = 'outline', inputSize = 'md', clearable, startSlot, endSlot, onClear, ref, rows = 3, ...props }: TextareaProps) { const innerRef = React.useRef(null) const composedRef = useComposedRefs(ref, innerRef) const hasWrapper = Boolean(clearable || startSlot || endSlot) const ariaInvalid = props['aria-invalid'] const invalid = ariaInvalid === true || ariaInvalid === 'true' const handleClear = () => { if (innerRef.current && props.value === undefined) { setNativeValue(innerRef.current, '') } innerRef.current?.focus() onClear?.() } if (!hasWrapper) { return ( } className={cn( inputVariants({ variant, size: inputSize, state: 'self' }), 'placeholder:text-foreground-subtle h-auto resize-y', sizePaddingY[inputSize], sizeMinHeight[inputSize], className, )} {...(props as BaseControlProps)} {...(invalid ? { 'data-invalid': '' } : {})} /> ) } return (
{startSlot && (
{startSlot}
)} } placeholder={props.placeholder ?? ' '} className="peer text-foreground placeholder:text-foreground-subtle min-w-0 flex-1 resize-none self-stretch bg-transparent outline-none disabled:cursor-not-allowed" {...(props as BaseControlProps)} /> {clearable && ( )} {endSlot && (
{endSlot}
)}
) } export { Textarea } export type { TextareaProps }