veza/apps/web/src/components/player/LyricsPanel.tsx

81 lines
2.8 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useRef, useState } from 'react';
import { useAudio } from '../../context/AudioContext';
import { Mic2, AlignLeft } from 'lucide-react';
export const LyricsPanel: React.FC = () => {
const { currentTrack, currentTime, seek, duration } = useAudio();
const scrollRef = useRef<HTMLDivElement>(null);
const [autoScroll, setAutoScroll] = useState(true);
// Auto-scroll logic
useEffect(() => {
if (autoScroll && scrollRef.current && currentTrack?.lyrics) {
const activeIndex = currentTrack.lyrics.findIndex(
(line: { time: number; text: string }, i: number) => {
return (
currentTime >= line.time &&
(i === currentTrack.lyrics!.length - 1 ||
currentTime < currentTrack.lyrics![i + 1].time)
);
},
);
if (activeIndex !== -1) {
const element = scrollRef.current.children[activeIndex] as HTMLElement;
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
}, [currentTime, currentTrack, autoScroll]);
if (!currentTrack?.lyrics) {
return (
<div className="flex flex-col items-center justify-center h-full text-muted-foreground opacity-50">
<Mic2 className="w-16 h-16 mb-4" />
<p>No lyrics available</p>
</div>
);
}
return (
<div
className="h-full flex flex-col relative group"
onMouseEnter={() => setAutoScroll(false)}
onMouseLeave={() => setAutoScroll(true)}
>
<div className="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => setAutoScroll(!autoScroll)}
className={`p-2 rounded-full backdrop-blur-md ${autoScroll ? 'bg-primary/20 text-primary' : 'bg-black/30 text-muted-foreground'}`}
title="Auto-scroll"
>
<AlignLeft className="w-4 h-4" />
</button>
</div>
<div
ref={scrollRef}
className="flex-1 overflow-y-auto custom-scrollbar px-4 space-y-6 text-center mask-image-linear-to-b py-24"
>
{currentTrack.lyrics.map(
(line: { time: number; text: string }, i: number) => {
const isActive =
currentTime >= line.time &&
(i === currentTrack.lyrics!.length - 1 ||
currentTime < currentTrack.lyrics![i + 1].time);
return (
<p
key={i}
className={`text-2xl md:text-3xl font-bold transition-all duration-[var(--sumi-duration-slow)] cursor-pointer hover:text-foreground ${isActive ? 'text-foreground scale-105 origin-center' : 'text-white/20 blur-[1px]'}`}
onClick={() => seek((line.time / duration) * 100)}
>
{line.text}
</p>
);
},
)}
</div>
</div>
);
};