Textarea
Overview
The Textarea component is an enhanced version of the standard HTML textarea with beautiful animations and interactive features. Built with Framer Motion, it provides various animation variants, auto-expanding capabilities, and interactive effects.
Preview
- Preview
- Code
import { AnimatedTextarea } from './components/ui';
function TextareaDemo() {
const [value, setValue] = useState('');
return (
<AnimatedTextarea
placeholder="Type something..."
variant="clean"
value={value}
onChange={setValue}
/>
);
}
Installation
- npm
- yarn
- pnpm
- manual
npx @mindfiredigital/ignix-ui add textarea
yarn @mindfiredigital/ignix-ui add textarea
pnpm @mindfiredigital/ignix-ui add textarea
"use client";
import type React from "react";
import { useState, useRef, useEffect } from "react";
import { motion, Variants, useMotionValue, useSpring, useTransform } from "framer-motion";
import { cn } from "../../../utils/cn";
interface AnimatedTextareaProps {
placeholder: string;
variant: string;
textareaClassName?: string;
labelClassName?: string;
value: string;
onChange: (value: string) => void;
onFocus?: () => void;
onBlur?: () => void;
disabled?: boolean;
error?: string;
success?: boolean;
icon?: React.ElementType;
maxLength?: number;
minRows?: number;
maxRows?: number;
size?: "sm" | "md" | "lg";
showCharacterCount?: boolean;
autoResize?: boolean;
theme?: "light" | "dark" | "auto";
glowEffect?: boolean;
}
interface TextareaVariant {
label: Variants;
textarea: Variants;
extra?: Variants;
container?: Variants;
}
// Enhanced particle creation with better physics
const createAdvancedParticles = (container: HTMLElement, count = 12) => {
const particles: HTMLElement[] = [];
for (let i = 0; i < count; i++) {
const particle = document.createElement("div");
particle.className = cn(
"absolute rounded-full pointer-events-none",
"bg-gradient-to-r from-blue-400 to-cyan-400",
"shadow-lg shadow-blue-400/50"
);
const size = Math.random() * 4 + 2;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.left = `${Math.random() * 100}%`;
particle.style.top = `${Math.random() * 100}%`;
particle.style.boxShadow = `0 0 ${size * 2}px rgba(59, 130, 246, 0.6)`;
// Enhanced animation
particle.style.animation = `particleFloat ${Math.random() * 3 + 2}s ease-in-out infinite`;
particle.style.animationDelay = `${Math.random() * 2}s`;
container.appendChild(particle);
particles.push(particle);
setTimeout(() => {
if (container.contains(particle)) {
container.removeChild(particle);
}
}, 4000);
}
return particles;
};
const AnimatedTextarea: React.FC<AnimatedTextareaProps> = ({
placeholder,
variant,
textareaClassName = "",
labelClassName = "",
onChange,
value,
onFocus,
onBlur,
disabled = false,
error,
success,
icon: Icon,
maxLength = 1000,
minRows = 3,
maxRows = 10,
size = "md",
showCharacterCount = false,
autoResize = true,
glowEffect = false,
}) => {
const [isFocused, setIsFocused] = useState(false);
const [height, setHeight] = useState("auto");
const textareaRef = useRef<HTMLTextAreaElement>(null);
const particleRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
// Advanced mouse tracking for premium effects
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const rotateX = useSpring(useTransform(mouseY, [-100, 100], [2, -2]));
const rotateY = useSpring(useTransform(mouseX, [-100, 100], [-2, 2]));
// Size configurations
const sizeConfig = {
sm: {
textarea: "min-h-[80px] px-3 py-2 text-sm",
label: "text-sm",
icon: "h-4 w-4",
minHeight: 80
},
md: {
textarea: "min-h-[100px] px-4 py-3 text-base",
label: "text-base",
icon: "h-5 w-5",
minHeight: 100
},
lg: {
textarea: "min-h-[120px] px-5 py-4 text-lg",
label: "text-lg",
icon: "h-6 w-6",
minHeight: 120
}
};
const config = sizeConfig[size];
const handleFocus = () => {
setIsFocused(true);
onFocus?.();
};
const handleBlur = () => {
setIsFocused(false);
onBlur?.();
};
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (disabled) return;
const newValue = e.target.value;
if (maxLength && newValue.length > maxLength) return;
onChange?.(newValue);
// Auto-resize functionality
if ((variant === "expandable" || variant === "smoothExpand" || autoResize) && textareaRef.current) {
const textarea = textareaRef.current;
textarea.style.height = 'auto';
const scrollHeight = textarea.scrollHeight;
const maxHeight = config.minHeight * maxRows / minRows;
const newHeight = Math.min(Math.max(scrollHeight, config.minHeight), maxHeight);
setHeight(`${newHeight}px`);
}
};
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
mouseX.set((e.clientX - centerX) * 0.3);
mouseY.set((e.clientY - centerY) * 0.3);
};
// Auto-resize effect
useEffect(() => {
if ((variant === "expandable" || variant === "smoothExpand" || autoResize) && textareaRef.current) {
const textarea = textareaRef.current;
textarea.style.height = 'auto';
const scrollHeight = textarea.scrollHeight;
const maxHeight = config.minHeight * maxRows / minRows;
const newHeight = Math.min(Math.max(scrollHeight, config.minHeight), maxHeight);
setHeight(`${newHeight}px`);
}
}, [value, variant, autoResize, config.minHeight, maxRows, minRows]);
// Enhanced particle effects
useEffect(() => {
if (variant === "particleField" && particleRef.current && isFocused) {
const interval = setInterval(() => {
if (particleRef.current) {
createAdvancedParticles(particleRef.current, 8);
}
}, 300);
return () => clearInterval(interval);
}
}, [variant, isFocused]);
const variants = textareaVariants[variant as keyof typeof textareaVariants];
const hasValue = value.length > 0;
const isActive = isFocused || hasValue;
// Character count logic
const characterCount = value.length;
const isNearLimit = maxLength && characterCount > maxLength * 0.8;
const isOverLimit = maxLength && characterCount > maxLength;
return (
<motion.div
ref={containerRef}
className={cn(
"relative mb-6 group",
disabled && "opacity-60 cursor-not-allowed"
)}
initial="initial"
animate={isActive ? "animate" : "initial"}
style={{
perspective: 2000,
rotateX: variant === "holographic3D" ? rotateX : undefined,
rotateY: variant === "holographic3D" ? rotateY : undefined
}}
onMouseMove={handleMouseMove}
onMouseLeave={() => {
mouseX.set(0);
mouseY.set(0);
}}
variants={variants.container}
>
{/* Enhanced Background Effects */}
{(variant === "glassmorphism" || variant === "premiumGlass") && (
<motion.div
className="absolute inset-0 bg-gradient-to-br from-white/10 via-white/5 to-transparent backdrop-blur-xl rounded-2xl border border-white/20 dark:border-white/10"
initial={{ opacity: 0 }}
animate={{ opacity: isActive ? 1 : 0.7 }}
transition={{ duration: 0.3 }}
/>
)}
{/* Premium Shimmer Effect */}
{(variant === "luxuryShimmer" || variant === "gradientBorder") && (
<motion.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-blue-500/20 to-transparent -skew-x-12 rounded-xl"
transition={{
x: {
duration: 1.5,
repeat: isActive ? Infinity : 0,
repeatDelay: 1,
ease: "easeInOut"
}
}}
/>
)}
{/* Enhanced glow effect */}
{glowEffect && isActive && (
<motion.div
className="absolute inset-0 rounded-xl opacity-30"
style={{
background: `radial-gradient(ellipse, rgba(59, 130, 246, 0.4) 0%, transparent 70%)`,
filter: "blur(12px)",
transform: "scale(1.1)"
}}
animate={{
scale: [1.1, 1.2, 1.1],
opacity: [0.3, 0.5, 0.3]
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut"
}}
/>
)}
{/* Enhanced Animated Label */}
<motion.label
className={cn(
"absolute left-4 pointer-events-none transition-colors duration-300 z-10 origin-left",
"text-muted-foreground group-focus-within:text-primary",
error && "text-red-500",
success && "text-emerald-500",
config.label,
labelClassName
)}
variants={variants.label}
style={{
originX: 0,
originY: 0.5,
}}
>
{placeholder}
</motion.label>
{/* Textarea Container */}
<div className="relative">
{/* Leading Icon */}
{Icon && (
<motion.div
className={cn(
"absolute left-3 top-3 z-20",
"text-muted-foreground group-focus-within:text-primary transition-colors duration-300",
config.icon
)}
initial={{ scale: 0.8, opacity: 0.6 }}
animate={{
scale: isActive ? 1 : 0.8,
opacity: isActive ? 1 : 0.6,
}}
transition={{ duration: 0.3 }}
>
<Icon className={config.icon} />
</motion.div>
)}
{/* Enhanced Textarea */}
<motion.textarea
ref={textareaRef}
className={cn(
// Base enhanced styles
"w-full bg-background/90 backdrop-blur-sm border border-border/60 rounded-xl resize-none",
"text-foreground placeholder:text-transparent transition-all duration-300",
"focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/60",
"disabled:cursor-not-allowed disabled:opacity-50",
"shadow-sm hover:shadow-md focus:shadow-lg",
"shadow-black/5 dark:shadow-white/5",
// Size configuration
config.textarea,
// Icon padding
Icon && "pl-10",
// Status variants
error && "border-red-500/60 focus:border-red-500 focus:ring-red-500/20",
success && "border-emerald-500/60 focus:border-emerald-500 focus:ring-emerald-500/20",
// Theme adaptations
"dark:bg-background/60 dark:border-border/40",
"hover:border-border/80 dark:hover:border-border/60",
textareaClassName
)}
style={{
height: (variant === "expandable" || variant === "smoothExpand" || autoResize) ? height : undefined,
minHeight: `${config.minHeight}px`,
maxHeight: variant === "expandable" || variant === "smoothExpand" || autoResize
? `${config.minHeight * maxRows / minRows}px`
: undefined,
}}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={handleChange}
value={value}
disabled={disabled}
maxLength={maxLength}
variants={variants.textarea}
rows={minRows}
/>
{/* Enhanced Particle Field */}
{variant === "particleField" && (
<motion.div
ref={particleRef}
className="absolute inset-0 pointer-events-none overflow-hidden rounded-xl"
initial={{ opacity: 0 }}
animate={{ opacity: isActive ? 1 : 0 }}
transition={{ duration: 0.3 }}
/>
)}
{/* Enhanced Character Count */}
{(variant === "characterCount" || showCharacterCount) && (
<motion.div
className={cn(
"absolute bottom-3 right-3 text-xs font-medium px-2 py-1 rounded-md backdrop-blur-sm z-20",
"bg-background/80 border border-border/40",
isNearLimit && !isOverLimit && "text-amber-600 border-amber-400/40 bg-amber-50/80 dark:bg-amber-950/80",
isOverLimit && "text-red-600 border-red-400/40 bg-red-50/80 dark:bg-red-950/80",
!isNearLimit && "text-muted-foreground"
)}
initial={{ opacity: 0, scale: 0.8 }}
animate={{
opacity: isActive ? 1 : 0.7,
scale: isOverLimit ? 1.1 : 1,
}}
transition={{ duration: 0.2 }}
>
{characterCount}{maxLength && ` / ${maxLength}`}
</motion.div>
)}
{/* Enhanced Line Highlight */}
{variant === "lineHighlight" && value && (
<motion.div
className="absolute left-0 w-full h-6 bg-gradient-to-r from-primary/10 to-primary/5 pointer-events-none rounded"
style={{
top: `${Math.floor(value.split('\n').length - 1) * 24 + 12}px`,
}}
initial={{ opacity: 0, scaleX: 0 }}
animate={{ opacity: 0.5, scaleX: 1 }}
transition={{ duration: 0.3 }}
/>
)}
{/* Ripple Effect */}
{variant === "rippleEffect" && isActive && (
<motion.div
className="absolute inset-0 rounded-xl pointer-events-none"
initial={{ scale: 0, opacity: 0.5 }}
animate={{
scale: 2,
opacity: 0,
}}
transition={{
duration: 1,
ease: "easeOut",
}}
style={{
background: "radial-gradient(circle, rgba(59,130,246,0.3) 0%, transparent 70%)"
}}
/>
)}
</div>
</motion.div>
);
};
// Enhanced textarea variants (ALL existing variants with premium improvements)
const textareaVariants: Record<string, TextareaVariant> = {
clean: {
container: {
initial: { scale: 1 },
animate: { scale: 1.005 },
},
label: {
initial: { y: 0, scale: 1, color: "#6b7280" },
animate: {
y: -32,
scale: 0.85,
color: "var(--primary)",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
borderColor: "var(--border)",
boxShadow: "0 1px 3px rgba(0,0,0,0.1)"
},
animate: {
borderColor: "var(--primary)",
boxShadow: "0 0 0 3px rgba(var(--primary), 0.1), 0 4px 12px rgba(var(--primary), 0.1)"
},
},
},
expandable: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
borderColor: "var(--border)",
transform: "scale(1)"
},
animate: {
borderColor: "var(--primary)",
transform: "scale(1.01)",
boxShadow: "0 4px 12px rgba(59, 130, 246, 0.15)"
},
},
},
smoothExpand: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
transition: { type: "spring", stiffness: 300, damping: 20 },
},
},
textarea: {
initial: {
borderColor: "var(--border)",
boxShadow: "0 1px 3px rgba(0,0,0,0.1)"
},
animate: {
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)",
transition: { duration: 0.3, ease: "easeOut" },
},
},
},
glowBorder: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
textShadow: "0 0 12px rgba(var(--primary), 0.6)"
},
},
textarea: {
initial: {
boxShadow: "0 0 0 0 rgba(var(--border), 0)",
borderColor: "var(--border)"
},
animate: {
boxShadow: "0 0 0 3px rgba(var(--border), 0.3), 0 0 20px rgba(var(--border), 0.2)",
borderColor: "var(--primary)"
},
},
},
characterCount: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
paddingBottom: "2.5rem",
borderColor: "var(--border)"
},
animate: {
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)"
},
},
},
lineHighlight: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
lineHeight: "1.5",
borderColor: "var(--border)"
},
animate: {
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)"
},
},
},
typewriterSound: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "#3b82f6",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
borderColor: "var(--border)",
fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace"
},
animate: {
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)",
transition: { type: "spring", stiffness: 500, damping: 20 },
},
},
},
markdownPreview: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "#3b82f6",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
borderRadius: "12px",
borderColor: "var(--border)"
},
animate: {
borderRadius: "16px",
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)"
},
},
},
autoComplete: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "#3b82f6",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
backgroundColor: "var(--background)",
borderColor: "var(--border)"
},
animate: {
backgroundColor: "var(--card)",
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)"
},
},
},
syntaxHighlight: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "#3b82f6",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace",
borderColor: "var(--border)",
backgroundColor: "var(--background)"
},
animate: {
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)"
},
},
},
rippleEffect: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "#3b82f6",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
transform: "scale(1)",
borderColor: "var(--border)"
},
animate: {
transform: "scale(1.005)",
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)",
transition: { type: "spring", stiffness: 300, damping: 20 },
},
},
},
gradientBorder: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "#3b82f6",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
border: "2px solid transparent",
backgroundImage: `
linear-gradient(white, white),
linear-gradient(90deg, #3b82f6, #8b5cf6, #3b82f6)
`,
backgroundClip: "padding-box, border-box",
backgroundOrigin: "padding-box, border-box",
backgroundSize: "100% 100%, 300% 100%",
backgroundPosition: "0 0, 100% 0",
},
animate: {
backgroundPosition: ["0 0, 100% 0", "0 0, -100% 0"],
boxShadow: "0 4px 15px rgba(var(--primary), 0.15)",
transition: {
backgroundPosition: { duration: 2, repeat: Infinity, ease: "linear" },
},
},
},
},
neonGlow: {
label: {
initial: {
textShadow: "0 0 0px #fff",
color: "var(--primary)"
},
animate: {
textShadow: "0 0 12px var(--primary), 0 0 24px var(--primary), 0 0 36px var(--primary)",
y: -32,
color: "var(--primary)",
},
},
textarea: {
initial: {
boxShadow: "0 0 0px #fff",
borderColor: "var(--border)"
},
animate: {
boxShadow: "0 0 8px var(--primary), 0 0 16px var(--primary), 0 0 24px var(--primary)",
borderColor: "var(--primary)",
transition: {
boxShadow: { repeat: Infinity, duration: 2, repeatType: "reverse" as const },
},
},
},
},
particleField: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
textShadow: "0 0 12px rgba(var(--primary), 0.4)"
},
},
textarea: {
initial: {
borderColor: "var(--border)",
boxShadow: "0 0 0 rgba(59, 130, 246, 0)",
},
animate: {
borderColor: "var(--primary)",
boxShadow: "0 0 20px rgba(var(--primary), 0.3)",
},
},
extra: {
initial: { opacity: 0 },
animate: {
opacity: 1,
transition: { duration: 0.2 },
},
},
},
elastic: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
transition: { type: "spring", stiffness: 400, damping: 15 },
},
},
textarea: {
initial: {
scale: 1,
borderColor: "var(--border)"
},
animate: {
scale: 1.02,
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)",
transition: {
scale: {
type: "spring",
stiffness: 300,
damping: 15,
restDelta: 0.001,
restSpeed: 10
}
}
},
},
},
wave: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
y: 0,
borderColor: "var(--border)"
},
animate: {
y: [0, -3, 3, 0],
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)",
transition: {
y: { repeat: Infinity, duration: 2.5, ease: "easeInOut" }
},
},
},
},
spotlight: {
label: {
initial: {
y: 0,
filter: "brightness(1)",
color: "var(--primary)"
},
animate: {
y: -32,
filter: "brightness(1.3) drop-shadow(0 0 8px rgba(var(--primary), 0.5))",
color: "var(--primary)"
},
},
textarea: {
initial: {
filter: "brightness(1)",
borderColor: "var(--border)"
},
animate: {
filter: ["brightness(1)", "brightness(1.1)", "brightness(1)"],
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)",
transition: {
filter: { repeat: Infinity, duration: 3, ease: "easeInOut" }
},
},
},
},
liquid: {
label: {
initial: { y: 0, color: "#6b7280" },
animate: {
y: -32,
color: "var(--primary)",
transition: { type: "spring", stiffness: 300, damping: 20 }
},
},
textarea: {
initial: {
borderRadius: "12px",
borderColor: "var(--border)"
},
animate: {
borderRadius: ["12px", "16px", "20px", "16px", "12px"],
borderColor: "var(--primary)",
boxShadow: "0 4px 12px rgba(var(--primary), 0.15)",
transition: {
borderRadius: { repeat: Infinity, duration: 4, ease: "easeInOut" }
},
},
},
},
cosmic: {
label: {
initial: {
y: 0,
rotate: 0,
color: "#6b7280",
filter: "hue-rotate(0deg)"
},
animate: {
y: -32,
rotate: 360,
color: "#8b5cf6",
filter: "hue-rotate(360deg)",
textShadow: "0 0 15px rgba(139, 92, 246, 0.6)",
transition: {
rotate: { duration: 2, repeat: Infinity, ease: "linear" },
filter: { duration: 3, repeat: Infinity, ease: "linear" }
}
},
},
textarea: {
initial: {
scale: 1,
borderColor: "rgba(139, 92, 246, 0.2)",
background: "radial-gradient(ellipse at center, rgba(139, 92, 246, 0.05) 0%, transparent 50%)"
},
animate: {
scale: [1, 1.01, 0.99, 1],
borderColor: "rgba(139, 92, 246, 0.6)",
background: "radial-gradient(ellipse at center, rgba(139, 92, 246, 0.1) 0%, rgba(236, 72, 153, 0.05) 50%, transparent 100%)",
boxShadow: "0 0 25px rgba(139, 92, 246, 0.3)",
transition: {
scale: { repeat: Infinity, duration: 3, ease: "easeInOut" }
},
},
},
},
hologram: {
label: {
initial: {
y: 0,
opacity: 0.8,
color: "var(--primary)",
filter: "blur(0px)"
},
animate: {
y: -32,
opacity: [0.8, 1, 0.6, 1],
color: "var(--primary)",
filter: "blur(0px) drop-shadow(0 0 8px rgba(var(--primary), 0.5))",
textShadow: "0 0 15px rgba(var(--primary), 0.6)",
transition: {
opacity: { repeat: Infinity, duration: 2.5, ease: "easeInOut" }
},
},
},
textarea: {
initial: {
opacity: 0.9,
borderColor: "var(--border)"
},
animate: {
opacity: [0.9, 1, 0.8, 1],
borderColor: "var(--primary)",
boxShadow: "0 0 20px rgba(var(--primary), 0.4), inset 0 0 20px rgba(var(--primary), 0.1)",
transition: {
opacity: { repeat: Infinity, duration: 2.5, ease: "easeInOut" }
},
},
},
extra: {
initial: { opacity: 0 },
animate: {
opacity: [0, 0.6, 0],
transition: { repeat: Infinity, duration: 2.5, ease: "easeInOut" },
},
},
},
};
export default AnimatedTextarea;
Usage
Import the component:
import { AnimatedTextarea } from './components/ui';
Basic Usage
function BasicTextarea() {
const [value, setValue] = useState('');
return (
<AnimatedTextarea
placeholder="Enter your message"
variant="clean"
value={value}
onChange={setValue}
/>
);
}
Animation Variants
- Preview
- Code
<AnimatedTextarea
placeholder="Type something..."
variant="clean"
value={value}
onChange={setValue}
/>