AI_Manager_Dashboard / client /src /components /dashboard /SoundwaveIndicator.tsx
maitrang04's picture
Upload 91 files
d125a03 verified
import { cn } from "@/lib/utils";
import { useEffect, useState } from "react";
interface SoundwaveIndicatorProps {
speaker: "ai" | "customer";
isActive?: boolean;
className?: string;
}
export function SoundwaveIndicator({ speaker, isActive = true, className }: SoundwaveIndicatorProps) {
const [bars, setBars] = useState<number[]>([0.3, 0.5, 0.7, 0.4, 0.6, 0.8, 0.5, 0.3]);
useEffect(() => {
if (!isActive) return;
const interval = setInterval(() => {
setBars(prev => prev.map(() => 0.2 + Math.random() * 0.8));
}, 100);
return () => clearInterval(interval);
}, [isActive]);
const speakerConfig = {
ai: {
color: "bg-primary",
label: "AI Speaking",
gradientFrom: "from-primary/20",
gradientTo: "to-primary/5",
},
customer: {
color: "bg-emerald-500",
label: "Customer Speaking",
gradientFrom: "from-emerald-500/20",
gradientTo: "to-emerald-500/5",
},
};
const config = speakerConfig[speaker];
return (
<div
className={cn(
"flex flex-col items-center gap-2 p-4 rounded-lg",
`bg-gradient-to-b ${config.gradientFrom} ${config.gradientTo}`,
className
)}
role="status"
aria-label={isActive ? config.label : "No one speaking"}
data-testid={`soundwave-indicator-${speaker}`}
>
<div className="flex items-end justify-center gap-1 h-12">
{bars.map((height, index) => (
<div
key={index}
className={cn(
"w-1.5 rounded-full transition-all duration-100",
isActive ? config.color : "bg-muted"
)}
style={{
height: isActive ? `${height * 48}px` : "8px",
opacity: isActive ? 0.7 + height * 0.3 : 0.3,
}}
aria-hidden="true"
/>
))}
</div>
<span className={cn(
"text-xs font-medium",
speaker === "ai" ? "text-primary" : "text-emerald-600 dark:text-emerald-400"
)}>
{isActive ? config.label : "Waiting..."}
</span>
</div>
);
}