aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
160 lines (157 loc) • 5.88 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import { forwardRef, useRef, useEffect } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
import { AlertCircle } from 'lucide-react';
const GlassTextarea = /*#__PURE__*/forwardRef(({
variant = "default",
size = "md",
state = "default",
fullWidth = false,
label,
helperText,
errorText,
autoResize = false,
minRows = 3,
maxRows = 10,
showCharCount = false,
maxLength,
loading = false,
icon,
className,
id,
rows = minRows,
value,
onChange,
...props
}, ref) => {
const textareaRef = useRef(null);
const combinedRef = ref || textareaRef;
const textareaId = id || `textarea-${Math.random().toString(36).substr(2, 9)}`;
const currentLength = typeof value === "string" ? value?.length || 0 : 0;
const sizeConfig = {
sm: {
text: "glass-text-sm",
padding: "glass-px-3 glass-py-2",
minHeight: "min-h-[80px]"
},
md: {
text: "glass-text-sm",
padding: "glass-px-4 glass-py-3",
minHeight: "min-h-[100px]"
},
lg: {
text: "glass-text-base",
padding: "glass-px-4 glass-py-4",
minHeight: "min-h-[120px]"
}
};
const variantConfig = {
default: {
base: "bg-background/50 border border-border/20",
focus: "focus:border-primary focus:ring-1 focus:ring-primary"
},
filled: {
base: "bg-muted/50 border border-transparent",
focus: "focus:bg-background/50 focus:border-primary"
},
outlined: {
base: "bg-transparent border-2 border-border/20",
focus: "focus:border-primary"
},
minimal: {
base: "bg-transparent border-0 border-b border-border/20",
focus: "focus:border-primary"
}
};
const stateConfig = {
default: "",
error: "border-red-400 focus:border-red-400 focus:ring-red-400",
warning: "border-amber-400 focus:border-amber-400 focus:ring-amber-400",
success: "border-green-400 focus:border-green-400 focus:ring-green-400"
};
const config = sizeConfig?.[size];
const variantStyles = variantConfig?.[variant];
const stateStyles = stateConfig?.[state];
const hasError = state === "error" || !!errorText;
// Auto-resize functionality
useEffect(() => {
if (autoResize && combinedRef && "current" in combinedRef && combinedRef.current) {
const textarea = combinedRef.current;
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight);
const paddingTop = parseInt(getComputedStyle(textarea).paddingTop);
const paddingBottom = parseInt(getComputedStyle(textarea).paddingBottom);
textarea.style.height = "auto";
const scrollHeight = textarea.scrollHeight;
const minHeight = lineHeight * minRows + paddingTop + paddingBottom;
const maxHeight = lineHeight * maxRows + paddingTop + paddingBottom;
textarea.style.height = `${Math.min(Math.max(scrollHeight, minHeight), maxHeight)}px`;
}
}, [value, autoResize, minRows, maxRows, combinedRef]);
const handleChange = event => {
onChange?.(event);
};
return jsxs("div", {
"data-glass-component": true,
className: cn("glass-textarea-wrapper", fullWidth && "w-full"),
children: [label && jsxs("label", {
htmlFor: textareaId,
className: cn("block font-medium text-foreground glass-mb-2", config.text, props?.disabled && "opacity-50"),
children: [label, props?.required && jsx("span", {
className: 'text-primary glass-ml-1',
"aria-label": "required",
children: "*"
})]
}), jsxs("div", {
className: 'relative',
children: [jsx("textarea", {
ref: combinedRef,
id: textareaId,
rows: autoResize ? undefined : rows,
value: value,
onChange: handleChange,
maxLength: maxLength,
"aria-describedby": helperText ? `${textareaId}-helper` : errorText ? `${textareaId}-error` : undefined,
"aria-invalid": hasError,
className: cn("glass-textarea", "w-full resize-none glass-radius-lg glass-backdrop-blur-md transition-all duration-200 glass-pulse-ring", "placeholder:glass-text-secondary", "focus:outline-none focus:ring-offset-2 focus:ring-offset-background",
// Size
config.text, config.padding, !autoResize && config.minHeight,
// Variant
variantStyles.base, variantStyles.focus,
// State
stateStyles,
// Disabled
props?.disabled && "opacity-50 cursor-not-allowed",
// Loading
loading && "animate-pulse cursor-wait",
// Full width
fullWidth && "w-full", className),
...props
}), icon && jsx("div", {
className: cn("absolute top-3 right-3 glass-text-secondary", props?.disabled && "opacity-50"),
children: icon
}), hasError && jsx("div", {
className: 'absolute top-3 right-3 text-primary',
children: jsx(AlertCircle, {
className: 'h-5 w-5'
})
})]
}), showCharCount && maxLength && jsxs("div", {
className: cn("flex justify-end glass-mt-1 glass-text-xs", currentLength > maxLength * 0.9 ? "text-amber-400" : "glass-text-secondary", currentLength >= maxLength && "text-red-400"),
children: [currentLength, "/", maxLength]
}), helperText && !hasError && jsx("p", {
id: `${textareaId}-helper`,
className: cn("glass-mt-2 glass-text-xs glass-text-secondary", props?.disabled && "opacity-50"),
children: helperText
}), errorText && jsxs("p", {
id: `${textareaId}-error`,
className: 'glass-mt-2 glass-text-xs text-primary glass-flex glass-items-center glass-gap-1',
children: [jsx(AlertCircle, {
className: 'h-3 w-3'
}), errorText]
})]
});
});
GlassTextarea.displayName = "GlassTextarea";
export { GlassTextarea, GlassTextarea as default };
//# sourceMappingURL=GlassTextarea.js.map