veza/apps/web/src/features/player/components/MiniPlayer.tsx
2026-02-12 21:55:25 +01:00

160 lines
5 KiB
TypeScript

/**
* Composant MiniPlayer
* Version compacte du player avec position fixe et toggle
*/
import { ChevronUp, X } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTranslation } from '@/hooks/useTranslation';
import { Tooltip } from '@/components/ui/tooltip';
import { usePlayer } from '../hooks/usePlayer';
import { TrackInfo } from './TrackInfo';
import { PlayPauseButton } from './PlayPauseButton';
import { NextPreviousButtons } from './NextPreviousButtons';
import { ProgressBar } from './ProgressBar';
import { VolumeControl } from './VolumeControl';
export interface MiniPlayerProps {
isVisible: boolean;
onToggle: () => void;
onClose?: () => void;
className?: string;
position?: 'bottom' | 'top';
}
export function MiniPlayer({
isVisible,
onToggle,
onClose,
className,
position = 'bottom',
}: MiniPlayerProps) {
const { t } = useTranslation();
const player = usePlayer();
if (!isVisible || !player.currentTrack) {
return null;
}
const handlePlayPause = () => {
if (player.isPlaying) {
player.pause();
} else {
player.resume();
}
};
const canGoNext =
player.queue.length > 0 && player.currentIndex < player.queue.length - 1;
const canGoPrevious = player.queue.length > 0 && player.currentIndex > 0;
return (
<div
className={cn(
'fixed left-0 right-0 z-[var(--sumi-z-sticky)] bg-[var(--sumi-glass-bg)] backdrop-blur-[16px] border-t border-[var(--sumi-border-faint)] shadow-[var(--sumi-shadow-lg)]',
'transition-transform duration-[var(--sumi-duration-normal)] ease-in-out',
position === 'bottom' ? 'bottom-0' : 'top-0',
className,
)}
role="region"
aria-label={t('player.miniPlayerAriaLabel')}
>
<div className="container mx-auto px-4 py-2">
<div className="flex items-center gap-4">
{/* Track Info - Compact */}
<div className="flex-1 min-w-0" aria-live="polite">
<TrackInfo
track={player.currentTrack}
showCover={true}
coverSize="sm"
showMetadata={false}
className="p-0"
/>
</div>
{/* Progress Bar - Compact */}
<div className="hidden md:flex flex-1 max-w-xs">
<ProgressBar
currentTime={player.currentTime}
duration={player.duration}
onSeek={player.seek}
className="h-1"
showTooltip={false}
/>
</div>
{/* Controls */}
<div className="flex items-center gap-2">
<NextPreviousButtons
onNext={player.next}
onPrevious={player.previous}
canGoNext={canGoNext}
canGoPrevious={canGoPrevious}
size="sm"
variant="ghost"
disabled={!player.currentTrack}
/>
<PlayPauseButton
isPlaying={player.isPlaying}
onClick={handlePlayPause}
size="sm"
variant="default"
/>
<div className="hidden sm:block">
<VolumeControl
volume={player.volume}
muted={player.muted}
onVolumeChange={player.setVolume}
onMuteToggle={player.toggleMute}
showValue={false}
showSlider={true}
/>
</div>
{/* Toggle Button */}
<Tooltip content={t('player.expandPlayer')}>
<button
type="button"
onClick={onToggle}
className={cn(
'p-2 rounded-lg text-muted-foreground',
'hover:bg-muted',
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
'transition-colors duration-[var(--sumi-duration-fast)]',
)}
aria-label={t('player.expandPlayer')}
>
<ChevronUp className="h-5 w-5" aria-hidden="true" />
<span className="sr-only">{t('player.expandPlayer')}</span>
</button>
</Tooltip>
{/* Close Button (optional) */}
{onClose && (
<Tooltip content={t('player.closeMiniPlayer')}>
<button
type="button"
onClick={onClose}
className={cn(
'p-2 rounded-lg text-muted-foreground',
'hover:bg-muted',
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
'transition-colors duration-[var(--sumi-duration-fast)]',
)}
aria-label={t('player.closeMiniPlayer')}
>
<X className="h-5 w-5" aria-hidden="true" />
<span className="sr-only">{t('player.closeMiniPlayer')}</span>
</button>
</Tooltip>
)}
</div>
</div>
</div>
</div>
);
}
export default MiniPlayer;