music-web/web/src/components/player/ProgressBar.tsx
Jarian Cottingham bc40a19c49 Initial commit: web frontend
React web app for the self-hosted music streaming project,
carved out from the music-app monorepo. Uses @music-app/shared
for shared types and utilities.
2026-08-21 18:44:05 +00:00

69 lines
2.1 KiB
TypeScript

import { useState, useRef, useCallback } from 'react'
interface ProgressBarProps {
progress: number
duration: number
onSeek: (time: number) => void
className?: string
}
export function ProgressBar({ progress, duration, onSeek, className = '' }: ProgressBarProps) {
const [isDragging, setIsDragging] = useState(false)
const barRef = useRef<HTMLDivElement>(null)
const percent = duration > 0 ? (progress / duration) * 100 : 0
const handleSeek = useCallback((clientX: number) => {
if (!barRef.current) return
const rect = barRef.current.getBoundingClientRect()
const x = Math.max(0, Math.min(clientX - rect.left, rect.width))
const ratio = x / rect.width
onSeek(duration * ratio)
}, [duration, onSeek])
const handleClick = (e: React.MouseEvent) => handleSeek(e.clientX)
const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true)
handleSeek(e.clientX)
}
const handleMouseMove = (e: React.MouseEvent) => {
if (isDragging) handleSeek(e.clientX)
}
const handleMouseUp = () => setIsDragging(false)
const formatTime = (sec: number) => {
const m = Math.floor(sec / 60)
const s = Math.floor(sec % 60)
return `${m}:${s.toString().padStart(2, '0')}`
}
return (
<div className={`w-full ${className}`}>
<div
ref={barRef}
className="relative h-1 bg-music-border rounded-full cursor-pointer group"
onClick={handleClick}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
<div
className="absolute left-0 top-0 h-full bg-music-accent rounded-full transition-none"
style={{ width: `${percent}%` }}
/>
<div
className="absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-music-accent rounded-full opacity-0 group-hover:opacity-100 transition-opacity -ml-1.5"
style={{ left: `${percent}%` }}
/>
</div>
<div className="flex justify-between mt-1 text-xs text-music-muted">
<span>{formatTime(progress)}</span>
<span>{formatTime(duration)}</span>
</div>
</div>
)
}