2026-02-09 23:11:11 +00:00
|
|
|
import { useRef, useState, useCallback } from 'react';
|
2026-02-05 21:40:06 +00:00
|
|
|
import { Slider } from '@/components/ui/slider';
|
2026-02-09 23:11:11 +00:00
|
|
|
import { cn } from '@/lib/utils';
|
2026-02-05 21:40:06 +00:00
|
|
|
import { formatTime } from './types';
|
|
|
|
|
|
|
|
|
|
interface AudioPlayerProgressProps {
|
|
|
|
|
currentTime: number;
|
|
|
|
|
duration: number;
|
|
|
|
|
onSeek: (value: number[]) => void;
|
feat(ui): hover cards, Spotify player layout, scrollbar tokens, context menu integration
HoverCard component (new):
- Rich preview cards on hover with framer-motion animation
- Viewport-aware positioning, portal rendering, open/close delays
- UserHoverContent: Discord-style user preview (avatar, bio, stats, follow)
- TrackHoverContent: Spotify-style track preview (cover, stats, play)
Audio player — Spotify-like 3-column layout:
- grid-cols-3 layout: track info | controls | volume+queue
- Progress bar moved to top edge (minimal variant)
- Glassmorphism (bg-background/95 backdrop-blur-md)
- Prominent centered play button (h-10 w-10 rounded-full, active:scale-95)
- Title marquee animation for long track names
- Reduced padding for tighter premium feel
Scrollbar styling:
- Migrated hardcoded rgba() to semantic tokens via color-mix(in oklch)
- Added transition on thumb hover for smooth visual feedback
ContextMenu integration:
- TrackListRow wrapped with ContextMenu (play, like, more actions)
- Dynamic items based on available callbacks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:18:46 +00:00
|
|
|
/** "default" shows time labels on sides; "minimal" renders a thin full-width bar (for top-of-player placement). */
|
|
|
|
|
variant?: 'default' | 'minimal';
|
2026-02-05 21:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function AudioPlayerProgress({
|
|
|
|
|
currentTime,
|
|
|
|
|
duration,
|
|
|
|
|
onSeek,
|
feat(ui): hover cards, Spotify player layout, scrollbar tokens, context menu integration
HoverCard component (new):
- Rich preview cards on hover with framer-motion animation
- Viewport-aware positioning, portal rendering, open/close delays
- UserHoverContent: Discord-style user preview (avatar, bio, stats, follow)
- TrackHoverContent: Spotify-style track preview (cover, stats, play)
Audio player — Spotify-like 3-column layout:
- grid-cols-3 layout: track info | controls | volume+queue
- Progress bar moved to top edge (minimal variant)
- Glassmorphism (bg-background/95 backdrop-blur-md)
- Prominent centered play button (h-10 w-10 rounded-full, active:scale-95)
- Title marquee animation for long track names
- Reduced padding for tighter premium feel
Scrollbar styling:
- Migrated hardcoded rgba() to semantic tokens via color-mix(in oklch)
- Added transition on thumb hover for smooth visual feedback
ContextMenu integration:
- TrackListRow wrapped with ContextMenu (play, like, more actions)
- Dynamic items based on available callbacks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:18:46 +00:00
|
|
|
variant = 'default',
|
2026-02-05 21:40:06 +00:00
|
|
|
}: AudioPlayerProgressProps) {
|
2026-02-09 23:11:11 +00:00
|
|
|
const [hoverTime, setHoverTime] = useState<number | null>(null);
|
|
|
|
|
const [tooltipX, setTooltipX] = useState(0);
|
|
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
|
|
|
const barRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
const handleMouseMove = useCallback(
|
|
|
|
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
|
if (!barRef.current || !duration) return;
|
|
|
|
|
const rect = barRef.current.getBoundingClientRect();
|
|
|
|
|
const x = e.clientX - rect.left;
|
|
|
|
|
const pct = Math.max(0, Math.min(1, x / rect.width));
|
|
|
|
|
setHoverTime(pct * duration);
|
|
|
|
|
setTooltipX(pct * 100);
|
|
|
|
|
},
|
|
|
|
|
[duration],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleMouseLeave = useCallback(() => {
|
|
|
|
|
if (!isDragging) setHoverTime(null);
|
|
|
|
|
}, [isDragging]);
|
|
|
|
|
|
|
|
|
|
const handlePointerDown = useCallback(() => setIsDragging(true), []);
|
|
|
|
|
const handlePointerUp = useCallback(() => {
|
|
|
|
|
setIsDragging(false);
|
|
|
|
|
setHoverTime(null);
|
|
|
|
|
}, []);
|
|
|
|
|
|
feat(ui): hover cards, Spotify player layout, scrollbar tokens, context menu integration
HoverCard component (new):
- Rich preview cards on hover with framer-motion animation
- Viewport-aware positioning, portal rendering, open/close delays
- UserHoverContent: Discord-style user preview (avatar, bio, stats, follow)
- TrackHoverContent: Spotify-style track preview (cover, stats, play)
Audio player — Spotify-like 3-column layout:
- grid-cols-3 layout: track info | controls | volume+queue
- Progress bar moved to top edge (minimal variant)
- Glassmorphism (bg-background/95 backdrop-blur-md)
- Prominent centered play button (h-10 w-10 rounded-full, active:scale-95)
- Title marquee animation for long track names
- Reduced padding for tighter premium feel
Scrollbar styling:
- Migrated hardcoded rgba() to semantic tokens via color-mix(in oklch)
- Added transition on thumb hover for smooth visual feedback
ContextMenu integration:
- TrackListRow wrapped with ContextMenu (play, like, more actions)
- Dynamic items based on available callbacks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:18:46 +00:00
|
|
|
if (variant === 'minimal') {
|
|
|
|
|
return (
|
2026-02-09 23:11:11 +00:00
|
|
|
<div
|
|
|
|
|
ref={barRef}
|
|
|
|
|
className={cn(
|
|
|
|
|
'w-full group relative',
|
|
|
|
|
isDragging && 'cursor-grabbing',
|
|
|
|
|
)}
|
|
|
|
|
onMouseMove={handleMouseMove}
|
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
|
onPointerDown={handlePointerDown}
|
|
|
|
|
onPointerUp={handlePointerUp}
|
|
|
|
|
>
|
feat(ui): hover cards, Spotify player layout, scrollbar tokens, context menu integration
HoverCard component (new):
- Rich preview cards on hover with framer-motion animation
- Viewport-aware positioning, portal rendering, open/close delays
- UserHoverContent: Discord-style user preview (avatar, bio, stats, follow)
- TrackHoverContent: Spotify-style track preview (cover, stats, play)
Audio player — Spotify-like 3-column layout:
- grid-cols-3 layout: track info | controls | volume+queue
- Progress bar moved to top edge (minimal variant)
- Glassmorphism (bg-background/95 backdrop-blur-md)
- Prominent centered play button (h-10 w-10 rounded-full, active:scale-95)
- Title marquee animation for long track names
- Reduced padding for tighter premium feel
Scrollbar styling:
- Migrated hardcoded rgba() to semantic tokens via color-mix(in oklch)
- Added transition on thumb hover for smooth visual feedback
ContextMenu integration:
- TrackListRow wrapped with ContextMenu (play, like, more actions)
- Dynamic items based on available callbacks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:18:46 +00:00
|
|
|
<Slider
|
|
|
|
|
value={[currentTime]}
|
|
|
|
|
max={duration || 1}
|
|
|
|
|
step={0.1}
|
|
|
|
|
onValueChange={onSeek}
|
2026-02-09 23:11:11 +00:00
|
|
|
className={cn('w-full transition-all', isDragging && 'scale-y-150')}
|
|
|
|
|
aria-label="Track progress"
|
feat(ui): hover cards, Spotify player layout, scrollbar tokens, context menu integration
HoverCard component (new):
- Rich preview cards on hover with framer-motion animation
- Viewport-aware positioning, portal rendering, open/close delays
- UserHoverContent: Discord-style user preview (avatar, bio, stats, follow)
- TrackHoverContent: Spotify-style track preview (cover, stats, play)
Audio player — Spotify-like 3-column layout:
- grid-cols-3 layout: track info | controls | volume+queue
- Progress bar moved to top edge (minimal variant)
- Glassmorphism (bg-background/95 backdrop-blur-md)
- Prominent centered play button (h-10 w-10 rounded-full, active:scale-95)
- Title marquee animation for long track names
- Reduced padding for tighter premium feel
Scrollbar styling:
- Migrated hardcoded rgba() to semantic tokens via color-mix(in oklch)
- Added transition on thumb hover for smooth visual feedback
ContextMenu integration:
- TrackListRow wrapped with ContextMenu (play, like, more actions)
- Dynamic items based on available callbacks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:18:46 +00:00
|
|
|
/>
|
2026-02-09 23:11:11 +00:00
|
|
|
{/* Time preview tooltip */}
|
|
|
|
|
{hoverTime !== null && duration > 0 && (
|
|
|
|
|
<div
|
|
|
|
|
className="absolute bottom-full mb-2 px-2 py-1 bg-card text-foreground text-xs rounded shadow-lg pointer-events-none whitespace-nowrap z-50"
|
|
|
|
|
style={{
|
|
|
|
|
left: `${tooltipX}%`,
|
|
|
|
|
transform: 'translateX(-50%)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{formatTime(hoverTime)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
feat(ui): hover cards, Spotify player layout, scrollbar tokens, context menu integration
HoverCard component (new):
- Rich preview cards on hover with framer-motion animation
- Viewport-aware positioning, portal rendering, open/close delays
- UserHoverContent: Discord-style user preview (avatar, bio, stats, follow)
- TrackHoverContent: Spotify-style track preview (cover, stats, play)
Audio player — Spotify-like 3-column layout:
- grid-cols-3 layout: track info | controls | volume+queue
- Progress bar moved to top edge (minimal variant)
- Glassmorphism (bg-background/95 backdrop-blur-md)
- Prominent centered play button (h-10 w-10 rounded-full, active:scale-95)
- Title marquee animation for long track names
- Reduced padding for tighter premium feel
Scrollbar styling:
- Migrated hardcoded rgba() to semantic tokens via color-mix(in oklch)
- Added transition on thumb hover for smooth visual feedback
ContextMenu integration:
- TrackListRow wrapped with ContextMenu (play, like, more actions)
- Dynamic items based on available callbacks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:18:46 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 21:40:06 +00:00
|
|
|
return (
|
|
|
|
|
<div className="flex items-center gap-2 flex-1">
|
|
|
|
|
<span className="text-xs text-muted-foreground w-12 text-right">
|
|
|
|
|
{formatTime(currentTime)}
|
|
|
|
|
</span>
|
2026-02-09 23:11:11 +00:00
|
|
|
<div
|
|
|
|
|
ref={barRef}
|
|
|
|
|
className={cn(
|
|
|
|
|
'flex-1 group relative',
|
|
|
|
|
isDragging && 'cursor-grabbing',
|
|
|
|
|
)}
|
|
|
|
|
onMouseMove={handleMouseMove}
|
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
|
onPointerDown={handlePointerDown}
|
|
|
|
|
onPointerUp={handlePointerUp}
|
|
|
|
|
>
|
|
|
|
|
<Slider
|
|
|
|
|
value={[currentTime]}
|
|
|
|
|
max={duration || 1}
|
|
|
|
|
step={0.1}
|
|
|
|
|
onValueChange={onSeek}
|
|
|
|
|
className={cn('w-full transition-all', isDragging && 'scale-y-150')}
|
|
|
|
|
aria-label="Track progress"
|
|
|
|
|
/>
|
|
|
|
|
{hoverTime !== null && duration > 0 && (
|
|
|
|
|
<div
|
|
|
|
|
className="absolute bottom-full mb-2 px-2 py-1 bg-card text-foreground text-xs rounded shadow-lg pointer-events-none whitespace-nowrap z-50"
|
|
|
|
|
style={{
|
|
|
|
|
left: `${tooltipX}%`,
|
|
|
|
|
transform: 'translateX(-50%)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{formatTime(hoverTime)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-02-05 21:40:06 +00:00
|
|
|
<span className="text-xs text-muted-foreground w-12">
|
|
|
|
|
{formatTime(duration)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|