158 lines
7.8 KiB
TypeScript
158 lines
7.8 KiB
TypeScript
|
|
import React from 'react';
|
||
|
|
import { usePlayerStore } from '../store/playerStore';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { Slider } from '@/components/ui/slider';
|
||
|
|
import {
|
||
|
|
ChevronDown, Heart, MoreHorizontal, Share2,
|
||
|
|
MessageSquare, Mic2, FileText, Music2
|
||
|
|
} from 'lucide-react';
|
||
|
|
import { PlayPauseButton } from './PlayPauseButton'; // We might reuse or inline for consistent style
|
||
|
|
import { NextPreviousButtons } from './NextPreviousButtons';
|
||
|
|
import { RepeatShuffleButtons } from './RepeatShuffleButtons';
|
||
|
|
|
||
|
|
interface PlayerExpandedProps {
|
||
|
|
isOpen: boolean;
|
||
|
|
onClose: () => void;
|
||
|
|
currentTime: number;
|
||
|
|
duration: number;
|
||
|
|
onSeek: (time: number) => void;
|
||
|
|
player: any; // Using the player hook object
|
||
|
|
}
|
||
|
|
|
||
|
|
export function PlayerExpanded({ isOpen, onClose, currentTime, duration, onSeek, player }: PlayerExpandedProps) {
|
||
|
|
const { currentTrack } = usePlayerStore();
|
||
|
|
|
||
|
|
if (!isOpen || !currentTrack) return null;
|
||
|
|
|
||
|
|
const formatTime = (seconds: number) => {
|
||
|
|
if (!seconds && seconds !== 0) return '0:00';
|
||
|
|
const m = Math.floor(seconds / 60);
|
||
|
|
const s = Math.floor(seconds % 60);
|
||
|
|
return `${m}:${s.toString().padStart(2, '0')}`;
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={cn(
|
||
|
|
"fixed inset-0 z-[110] bg-black/95 backdrop-blur-3xl overflow-hidden flex flex-col transition-all duration-500",
|
||
|
|
isOpen ? "opacity-100 translate-y-0" : "opacity-0 translate-y-full pointer-events-none"
|
||
|
|
)}>
|
||
|
|
{/* Dynamic Background */}
|
||
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||
|
|
<div
|
||
|
|
className="absolute inset-0 bg-cover bg-center opacity-30 blur-[100px] scale-110 transition-all duration-1000"
|
||
|
|
style={{ backgroundImage: `url(${currentTrack.cover || '/placeholder.svg'})` }}
|
||
|
|
/>
|
||
|
|
<div className="absolute inset-0 bg-gradient-to-b from-black/20 via-black/60 to-black/90" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Header */}
|
||
|
|
<div className="relative z-10 flex items-center justify-between p-6">
|
||
|
|
<Button variant="ghost" className="text-white hover:bg-white/10 rounded-full" onClick={onClose}>
|
||
|
|
<ChevronDown className="w-6 h-6" />
|
||
|
|
</Button>
|
||
|
|
<span className="text-xs font-bold tracking-widest uppercase text-white/50">Following the Signal</span>
|
||
|
|
<Button variant="ghost" className="text-white hover:bg-white/10 rounded-full">
|
||
|
|
<MoreHorizontal className="w-6 h-6" />
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Main Content */}
|
||
|
|
<div className="flex-1 flex flex-col md:flex-row items-center justify-center gap-12 px-8 pb-12 relative z-10 max-w-7xl mx-auto w-full">
|
||
|
|
|
||
|
|
{/* Left: Album Art */}
|
||
|
|
<div className="w-full max-w-md md:max-w-xl aspect-square relative group">
|
||
|
|
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/20 to-magenta-500/20 rounded-xl blur-2xl transform group-hover:scale-105 transition-transform duration-700" />
|
||
|
|
<img
|
||
|
|
src={currentTrack.cover || '/placeholder.svg'}
|
||
|
|
alt={currentTrack.title}
|
||
|
|
className="w-full h-full object-cover rounded-xl shadow-[0_20px_50px_rgba(0,0,0,0.5)] relative z-10 border border-white/10"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Right: Info & Controls */}
|
||
|
|
<div className="w-full max-w-xl flex flex-col justify-end space-y-8">
|
||
|
|
|
||
|
|
<div className="flex items-end justify-between">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<h2 className="text-4xl md:text-5xl font-display font-bold text-white leading-tight">
|
||
|
|
{currentTrack.title}
|
||
|
|
</h2>
|
||
|
|
<p className="text-xl md:text-2xl text-muted-foreground font-medium">
|
||
|
|
{currentTrack.artist}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<Button size="icon" variant="ghost" className="text-muted-foreground hover:text-red-500 hover:bg-red-500/10 rounded-full h-12 w-12 transition-all">
|
||
|
|
<Heart className="w-6 h-6" />
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Progress */}
|
||
|
|
<div className="space-y-4 group/progress">
|
||
|
|
<Slider
|
||
|
|
value={[currentTime]}
|
||
|
|
onValueChange={(val) => onSeek(val[0])}
|
||
|
|
max={duration || 100}
|
||
|
|
step={0.1}
|
||
|
|
className="py-2"
|
||
|
|
/>
|
||
|
|
<div className="flex items-center justify-between text-xs font-mono text-muted-foreground">
|
||
|
|
<span>{formatTime(currentTime)}</span>
|
||
|
|
<span>{formatTime(duration)}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Controls */}
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div className="flex items-center gap-4">
|
||
|
|
{/* Todo: Reuse or reimplement buttons with larger sizes */}
|
||
|
|
<RepeatShuffleButtons
|
||
|
|
repeat={player.repeat}
|
||
|
|
shuffle={player.shuffle}
|
||
|
|
onRepeatChange={player.setRepeat}
|
||
|
|
onShuffleToggle={player.toggleShuffle}
|
||
|
|
size="lg"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center gap-6 md:gap-8">
|
||
|
|
<NextPreviousButtons
|
||
|
|
onNext={player.next}
|
||
|
|
onPrevious={player.previous}
|
||
|
|
canGoNext={true}
|
||
|
|
canGoPrevious={true}
|
||
|
|
size="lg"
|
||
|
|
/>
|
||
|
|
<PlayPauseButton
|
||
|
|
isPlaying={player.isPlaying}
|
||
|
|
onClick={() => player.isPlaying ? player.pause() : player.resume()}
|
||
|
|
size="xl" // We need to support 'xl' maybe or modify the component
|
||
|
|
className="scale-125"
|
||
|
|
/>
|
||
|
|
<NextPreviousButtons
|
||
|
|
onNext={player.next}
|
||
|
|
onPrevious={player.previous}
|
||
|
|
canGoNext={true}
|
||
|
|
canGoPrevious={true}
|
||
|
|
size="lg"
|
||
|
|
className="hidden" // HACK: reusing comp just for previous button structure if needed
|
||
|
|
/>
|
||
|
|
{/* Wait, NextPrevious contains both buttons. I was using it wrong above. */}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center gap-4">
|
||
|
|
<Button size="icon" variant="ghost" className="text-muted-foreground hover:text-white">
|
||
|
|
<Share2 className="w-5 h-5" />
|
||
|
|
</Button>
|
||
|
|
{/* Lyrics toggle placeholder */}
|
||
|
|
<Button size="icon" variant="ghost" className="text-muted-foreground hover:text-white">
|
||
|
|
<Mic2 className="w-5 h-5" />
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|