{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"textarea","title":"Textarea","type":"registry:ui","description":"An advance textarea component with auto-size and header/footer support, built on top of Radix UI.","dependencies":["class-variance-authority"],"registryDependencies":["utils","https://fysk.dev/r/fysk-hooks.json","https://fysk.dev/r/fysk-provider.json"],"files":[{"path":"fysk/textarea.tsx","type":"registry:ui","content":"\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { cva, type VariantProps } from \"class-variance-authority\"\r\nimport { cn } from \"@/lib/utils\"\r\nimport { useFyskConfig, type FyskIconPosition } from \"@/components/fysk-provider\"\r\nimport { useFyskAnimation } from \"@/components/hooks/useFyskAnimation\"\r\n\r\nconst textareaVariants = cva(\r\n    \"flex w-full rounded-md shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive min-h-[80px]\",\r\n    {\r\n        variants: {\r\n            variant: {\r\n                default: \"border border-input bg-transparent dark:bg-input/30 shadow-xs\",\r\n                secondary: \"border border-transparent bg-muted focus-within:bg-background focus-within:border-input\",\r\n                outline: \"border-2 border-border bg-transparent focus-within:border-ring/50\",\r\n                ghost: \"border border-transparent bg-transparent hover:bg-muted focus-within:bg-background\",\r\n                glass: \"bg-foreground/5 backdrop-blur-md border border-border/50 text-foreground placeholder:text-muted-foreground/50 shadow-xs\",\r\n            },\r\n            size: {\r\n                xs: \"text-xs min-h-[60px]\",\r\n                sm: \"text-sm min-h-[70px]\",\r\n                md: \"text-base md:text-sm min-h-[80px]\",\r\n                lg: \"text-base md:text-sm min-h-[100px]\",\r\n                xl: \"text-lg min-h-[120px]\",\r\n            },\r\n        },\r\n        defaultVariants: {\r\n            variant: \"default\",\r\n            size: \"md\",\r\n        },\r\n    }\r\n)\r\n\r\nexport interface TextareaProps\r\n    extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>,\r\n    VariantProps<typeof textareaVariants> {\r\n    /**\r\n     * Optional className for the header.\r\n     */\r\n    headerClassName?: string;\r\n    /**\r\n     * Optional className for the footer.\r\n     */\r\n    footerClassName?: string;\r\n    /**\r\n     * Automatically adjust the height of the textarea based on content.\r\n     */\r\n    autoSize?: boolean\r\n    /**\r\n     * Maximum height when autoSize is enabled.\r\n     */\r\n    maxHeight?: number\r\n    /**\r\n     * Optional header content (e.g., info pills, toolbar).\r\n     */\r\n    header?: React.ReactNode\r\n    /**\r\n     * Optional footer content (e.g., send button, character count).\r\n     */\r\n    footer?: React.ReactNode\r\n    /**\r\n     * Show character count in the footer. Requires maxLength.\r\n     */\r\n    showCount?: boolean\r\n    state?: \"idle\" | \"loading\" | \"success\" | \"error\"\r\n    /** The icon to display. */\r\n    icon?: React.ReactNode\r\n    /** Custom loading icon to override the global default. */\r\n    iconLoading?: React.ReactNode\r\n    /** Custom success icon to override the global default. */\r\n    iconSuccess?: React.ReactNode\r\n    /** Custom error icon to override the global default. */\r\n    iconError?: React.ReactNode\r\n    /** Override the global icon position. */\r\n    iconPosition?: FyskIconPosition\r\n}\r\n\r\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\r\n    (\r\n        {\r\n            className,\r\n            headerClassName,\r\n            footerClassName,\r\n            variant,\r\n            size,\r\n            autoSize = false,\r\n            maxHeight,\r\n            header,\r\n            footer,\r\n            showCount = false,\r\n            state = \"idle\",\r\n            icon,\r\n            iconLoading: propIconLoading,\r\n            iconSuccess: propIconSuccess,\r\n            iconError: propIconError,\r\n            iconPosition: propIconPosition,\r\n            ...props\r\n        },\r\n        ref\r\n    ) => {\r\n        const config = useFyskConfig()\r\n        const { isEnabled, motion, AnimatePresence, variants } = useFyskAnimation()\r\n        const internalRef = React.useRef<HTMLTextAreaElement>(null)\r\n        const [charCount, setCharCount] = React.useState(0)\r\n\r\n        const isLoading = state === \"loading\"\r\n        const isSuccess = state === \"success\"\r\n        const isError = state === \"error\"\r\n\r\n        const activeIconPosition = propIconPosition || config.iconPosition || \"start\"\r\n        const finalIconLoading = propIconLoading || config.icons?.loading\r\n        const finalIconSuccess = propIconSuccess || config.icons?.success\r\n        const finalIconError = propIconError || config.icons?.error\r\n\r\n        const currentIcon = isLoading ? finalIconLoading : isSuccess ? finalIconSuccess : isError ? finalIconError : icon\r\n        const MotionDiv = isEnabled && motion ? motion.div : \"div\"\r\n\r\n        // Merge refs: one for internal logic (autoSize) and one for parent (forwardRef)\r\n        const combinedRef = React.useCallback((node: HTMLTextAreaElement) => {\r\n            internalRef.current = node\r\n            if (typeof ref === \"function\") {\r\n                ref(node)\r\n            } else if (ref) {\r\n                (ref as React.RefObject<HTMLTextAreaElement | null>).current = node\r\n            }\r\n        }, [ref])\r\n\r\n        const adjustHeight = React.useCallback(() => {\r\n            if (autoSize && internalRef.current) {\r\n                internalRef.current.style.height = 'auto'\r\n                const nextHeight = internalRef.current.scrollHeight\r\n                if (maxHeight && nextHeight > maxHeight) {\r\n                    internalRef.current.style.height = `${maxHeight}px`\r\n                    internalRef.current.style.overflowY = 'auto'\r\n                } else {\r\n                    internalRef.current.style.height = `${nextHeight}px`\r\n                    internalRef.current.style.overflowY = 'hidden'\r\n                }\r\n            }\r\n        }, [autoSize, maxHeight])\r\n\r\n        React.useEffect(() => {\r\n            adjustHeight()\r\n        }, [adjustHeight, props.value])\r\n\r\n        const handleInput = (e: React.FormEvent<HTMLTextAreaElement>) => {\r\n            adjustHeight()\r\n            setCharCount(e.currentTarget.value.length)\r\n            props.onInput?.(e)\r\n        }\r\n\r\n        const renderStatusIndicator = () => {\r\n            if (!currentIcon && state === \"idle\") return null\r\n\r\n            return (\r\n                <AnimatePresence {...(isEnabled && motion ? { mode: \"wait\" } : {})}>\r\n                    {currentIcon && (\r\n                        <MotionDiv\r\n                            key={state + (currentIcon ? \"has-icon\" : \"no-icon\")}\r\n                            className={cn(\r\n                                \"absolute flex items-center justify-center text-muted-foreground z-10\",\r\n                                size === \"xs\" ? \"[&_svg]:size-3.5\" : \"[&_svg]:size-4\",\r\n                                \"right-3 bottom-3\",\r\n                                isSuccess && \"text-green-600 dark:text-green-400\",\r\n                                isError && \"text-destructive\",\r\n                                isLoading && \"text-primary [&_svg]:animate-spin\"\r\n                            )}\r\n                            {...(isEnabled && motion && variants ? variants.iconPop : {})}\r\n                        >\r\n                            {currentIcon}\r\n                        </MotionDiv>\r\n                    )}\r\n                </AnimatePresence>\r\n            )\r\n        }\r\n\r\n        const textareaElement = (\r\n            <textarea\r\n                className={cn(\r\n                    \"flex w-full bg-transparent text-base placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm px-0 py-0\",\r\n                    // Tailwind scrollbar styles\r\n                    \"[&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-border/30 [&::-webkit-scrollbar-thumb]:rounded-[10px] hover:[&::-webkit-scrollbar-thumb]:bg-border/60\",\r\n                    (autoSize || header || footer || showCount) && \"resize-none overflow-hidden\",\r\n                    currentIcon && \"pr-8\",\r\n                    className\r\n                )}\r\n                ref={combinedRef}\r\n                onInput={handleInput}\r\n                {...props}\r\n            />\r\n        )\r\n\r\n        const containerClasses = cn(\r\n            textareaVariants({ variant, size }),\r\n            isSuccess && \"border-green-500/50 focus-within:ring-green-500/20\",\r\n            isError && \"border-destructive/50 focus-within:ring-destructive/20\"\r\n        )\r\n\r\n        return (\r\n            <div className={cn(\r\n                \"relative flex flex-col w-full transition-all overflow-hidden\",\r\n                containerClasses,\r\n                (header || footer) && \"p-0\"\r\n            )}>\r\n                {header && (\r\n                    <div className={`px-3 py-2 bg-transparent ${headerClassName}`}>\r\n                        {header}\r\n                    </div>\r\n                )}\r\n                <div className={cn(\r\n                    \"flex flex-col flex-1 relative px-3 py-2\",\r\n                    header && \"pt-0\",\r\n                    footer && \"pb-0\",\r\n                    showCount && \"pb-0\"\r\n                )}>\r\n                    <div className=\"relative flex flex-1 w-full h-full\">\r\n                        {textareaElement}\r\n                        {renderStatusIndicator()}\r\n                    </div>\r\n                </div>\r\n                {(footer || showCount) && (\r\n                    <div className={cn(\r\n                        \"px-3 py-2 mt-auto bg-transparent border-t border-border/10 flex items-center justify-between gap-4\",\r\n                        footerClassName\r\n                    )}>\r\n                        <div className=\"flex-1\">{footer}</div>\r\n                        {showCount && (\r\n                            <div className=\"text-[10px] uppercase tracking-wider font-bold text-muted-foreground tabular-nums ml-auto\">\r\n                                {charCount}{props.maxLength ? ` / ${props.maxLength}` : \"\"}\r\n                            </div>\r\n                        )}\r\n                    </div>\r\n                )}\r\n            </div>\r\n        )\r\n    }\r\n)\r\nTextarea.displayName = \"Textarea\"\r\n\r\nexport { Textarea, textareaVariants }\r\n"}]}