@@ -13,7 +13,7 @@ import {
1313 Volume2 ,
1414 VolumeX ,
1515} from "lucide-react"
16- import { memo , useCallback , useState } from "react"
16+ import { memo , useCallback , useState , useRef } from "react"
1717import ShareDrawer from "@/components/Posts/ShareDrawer"
1818import { Button } from "@/components/ui/button"
1919import { Slider } from "@/components/ui/slider"
@@ -63,9 +63,8 @@ export const PlaylistActions = ({ onPlayAll, onShuffle, disabled, showShare = tr
6363
6464export const LoadingState = ( { message, height } ) => (
6565 < div
66- className = { `flex ${
67- height ? height : "h-full"
68- } items-center justify-center bg-background/50 backdrop-blur-xs`}
66+ className = { `flex ${ height ? height : "h-full"
67+ } items-center justify-center bg-background/50 backdrop-blur-xs`}
6968 >
7069 < div className = "flex flex-col items-center gap-4" >
7170 < Loader2 className = "w-10 h-10 animate-spin text-emerald-500" />
@@ -266,23 +265,71 @@ export const ProgressBarMusic = memo(({ isTimeVisible = false }) => {
266265 const currentTime = usePlayerStore ( ( s ) => s . currentTime )
267266 const duration = usePlayerStore ( ( s ) => s . duration )
268267 const handleTimeSeek = usePlayerStore ( ( s ) => s . handleTimeSeek )
268+ const [ hoverTime , setHoverTime ] = useState ( null )
269+ const [ hoverX , setHoverX ] = useState ( 0 )
270+ const [ isDragging , setIsDragging ] = useState ( false )
271+ const containerRef = useRef ( null )
272+
273+ const handleMouseMove = ( e ) => {
274+ const rect = e . currentTarget . getBoundingClientRect ( )
275+ const x = e . clientX - rect . left
276+ const pct = Math . max ( 0 , Math . min ( 1 , x / rect . width ) )
277+ setHoverTime ( pct * duration )
278+ setHoverX ( x )
279+ }
280+
281+ const handleMouseLeave = ( ) => {
282+ setHoverTime ( null )
283+ }
284+
285+ const showTooltip = ( hoverTime !== null || isDragging ) && duration > 0
286+ const displayTime = isDragging ? currentTime : hoverTime
287+
288+ let activeX = hoverX
289+ if ( isDragging && containerRef . current ) {
290+ const rect = containerRef . current . getBoundingClientRect ( )
291+ const pct = currentTime / ( duration || 1 )
292+ activeX = pct * rect . width
293+ }
269294
270295 return (
271- < div className = "space-y-1.5 group/progress" >
296+ < div
297+ ref = { containerRef }
298+ className = "relative group/progress cursor-pointer overflow-visible"
299+ onMouseMove = { handleMouseMove }
300+ onMouseLeave = { handleMouseLeave }
301+ >
302+ { showTooltip && (
303+ < div
304+ className = "absolute z-[100] bg-[#0a0c12]/95 border border-white/10 text-white text-[10px] font-semibold px-2 py-0.5 rounded shadow-xl pointer-events-none -translate-x-1/2"
305+ style = { {
306+ left : `${ activeX } px` ,
307+ bottom : "18px" ,
308+ } }
309+ >
310+ { formatTime ( displayTime ) }
311+ </ div >
312+ ) }
313+
272314 < Slider
273315 value = { [ currentTime ] }
274316 min = { 0 }
275317 max = { duration || 1 }
276318 step = { 0.1 }
277319 onValueChange = { ( [ value ] ) => handleTimeSeek ( value ) }
320+ onPointerDown = { ( ) => setIsDragging ( true ) }
321+ onPointerUp = { ( ) => setIsDragging ( false ) }
322+ onPointerCancel = { ( ) => setIsDragging ( false ) }
278323 className = { cn (
279- "h-1.5 cursor-pointer transition-all duration-200" ,
280- "[&_[data-slider-track]]:h-[5px] [&_[data-slider-track]]:bg-white/15 [&_[data-slider-track]]:rounded-full" ,
281- "[&_[data-slider-range]]:bg-white [&_[data-slider-range]]:rounded-full" ,
282- "**:[[role=slider]]:h-4 **:[[role=slider]]:w-4 **:[[role=slider]]:bg-white **:[[role=slider]]:border-0" ,
283- "**:[[role=slider]]:shadow-[0_0_8px_rgba(255,255,255,0.3)]" ,
324+ "relative h-5 py-2 -my-2 cursor-pointer transition-colors duration-200" ,
325+ "[&>span:first-child]:h-[4px] [&>span:first-child]:bg-white/15 [&>span:first-child]:rounded-full [&>span:first-child]:transition-[height,background-color] [&>span:first-child]:duration-200" ,
326+ "group-hover/progress:[&>span:first-child]:h-[6px] group-hover/progress:[&>span:first-child]:bg-white/20" ,
327+ "[&>span:first-child>span]:bg-white [&>span:first-child>span]:rounded-full [&>span:first-child>span]:transition-colors [&>span:first-child>span]:duration-200" ,
328+ "group-hover/progress:[&>span:first-child>span]:bg-primary" ,
329+ "**:[[role=slider]]:h-3.5 **:[[role=slider]]:w-3.5 **:[[role=slider]]:bg-white **:[[role=slider]]:border-0" ,
330+ "**:[[role=slider]]:shadow-[0_0_10px_rgba(255,255,255,0.4)]" ,
284331 "**:[[role=slider]]:opacity-0 **:[[role=slider]]:scale-50" ,
285- "**:[[role=slider]]:transition-all **:[[role=slider]]:duration-200" ,
332+ "**:[[role=slider]]:transition-[opacity,transform] **:[[role=slider]]:duration-200" ,
286333 "group-hover/progress:**:[[role=slider]]:opacity-100 group-hover/progress:**:[[role=slider]]:scale-100" ,
287334 ) }
288335 />
@@ -301,32 +348,32 @@ export const ensureHttpsForDownloadUrls = (song) => {
301348
302349 const updatedDownloadUrls = Array . isArray ( song . download_url )
303350 ? song . download_url . map ( ( item ) => {
304- if ( ! item || typeof item !== "object" ) return item
305- return {
306- ...item ,
307- link :
308- item . link && typeof item . link === "string"
309- ? item . link . startsWith ( "http://" )
310- ? item . link . replace ( "http://" , "https://" )
311- : item . link
312- : item . link ,
313- }
314- } )
351+ if ( ! item || typeof item !== "object" ) return item
352+ return {
353+ ...item ,
354+ link :
355+ item . link && typeof item . link === "string"
356+ ? item . link . startsWith ( "http://" )
357+ ? item . link . replace ( "http://" , "https://" )
358+ : item . link
359+ : item . link ,
360+ }
361+ } )
315362 : song . download_url
316363
317364 const updatedArtworkUrls = Array . isArray ( song . image )
318365 ? song . image . map ( ( item ) => {
319- if ( ! item || typeof item !== "object" ) return item
320- return {
321- ...item ,
322- link :
323- item . link && typeof item . link === "string"
324- ? item . link . startsWith ( "http://" )
325- ? item . link . replace ( "http://" , "https://" )
326- : item . link
327- : item . link ,
328- }
329- } )
366+ if ( ! item || typeof item !== "object" ) return item
367+ return {
368+ ...item ,
369+ link :
370+ item . link && typeof item . link === "string"
371+ ? item . link . startsWith ( "http://" )
372+ ? item . link . replace ( "http://" , "https://" )
373+ : item . link
374+ : item . link ,
375+ }
376+ } )
330377 : song . image
331378
332379 return {
0 commit comments