feat: add tooltip for progress bar seeking

This commit is contained in:
mmacierzanka committed 2026-08-21 21:12:24 +02:00
1 parent 9e568e0581
commit ca17c742c9
5 files changed
+113 -5

No files matched your search

+48 -2
View File
@@ -471,22 +471,68 @@ body {
background: rgba(255, 255, 255, 0.08);
border-radius: 4px;
position: relative;
overflow: hidden;
overflow: visible;
transition: var(--transition-smooth);
border: 1px solid rgba(255, 255, 255, 0.03);
}
.progress-time-tooltip {
position: absolute;
top: -30px;
transform: translateX(-50%);
padding: 3px 7px;
border-radius: 5px;
background: var(--bg-card, #1f1b2e);
border: 1px solid var(--border-glass);
color: var(--text-primary);
font-size: 0.7rem;
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
pointer-events: none;
z-index: 2;
}
.track-progress-slider {
position: absolute;
inset: -7px 0;
width: 100%;
width: auto;
height: 20px;
margin: 0;
padding: 0;
appearance: none;
-webkit-appearance: none;
box-sizing: border-box;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.track-progress-slider::-webkit-slider-runnable-track {
width: 100%;
height: 20px;
background: transparent;
}
.track-progress-slider::-webkit-slider-thumb {
width: 0;
height: 20px;
border: 0;
-webkit-appearance: none;
}
.track-progress-slider::-moz-range-track {
width: 100%;
height: 20px;
background: transparent;
}
.track-progress-slider::-moz-range-thumb {
width: 0;
height: 20px;
border: 0;
}
.track-progress-slider:disabled {
cursor: default;
}
+35 -3
View File
@@ -28,7 +28,10 @@ const current = computed(() => props.status?.current ?? null)
const playing = computed(() => props.status?.playing ?? false)
const loopMode = computed(() => loopModeOf(props.status))
const seeking = ref(false)
const seekSubmitting = ref(false)
const seekPosition = ref(0)
const hoverPosition = ref<number | null>(null)
const hoverOffset = ref(0)
const displayedElapsed = computed(() => seeking.value ? seekPosition.value : elapsed.value)
/** Podczas przeciągania suwaka pokazujemy wartość lokalną, nie tę z serwera. */
@@ -100,12 +103,30 @@ function onSeekInput(event: Event) {
seekPosition.value = Number((event.target as HTMLInputElement).value)
}
function onSeekHover(event: MouseEvent) {
if (duration.value <= 0) {
hoverPosition.value = null
return
}
const element = event.target as HTMLElement
const container = event.currentTarget as HTMLElement
const slider = element.tagName === 'INPUT' ? element : container
const sliderRect = slider.getBoundingClientRect()
const containerRect = container.getBoundingClientRect()
const { left, width } = sliderRect
const ratio = Math.min(Math.max((event.clientX - left) / width, 0), 1)
hoverPosition.value = ratio * duration.value
hoverOffset.value = event.clientX - containerRect.left
}
async function commitSeek() {
if (!current.value || duration.value <= 0) return
if (!current.value || duration.value <= 0 || seekSubmitting.value) return
const position = seekPosition.value
seekSubmitting.value = true
try {
await run(() => api.seek(position), 'Przewinięto utwór', 'Błąd przewijania utworu')
} finally {
seekSubmitting.value = false
seeking.value = false
}
}
@@ -182,7 +203,11 @@ async function cycleLoop() {
class="track-progress-container"
>
<span class="time-text">{{ formatTime(displayedElapsed) }}</span>
<div class="progress-bar-bg">
<div
class="progress-bar-bg"
@mousemove="onSeekHover"
@mouseleave="hoverPosition = null"
>
<div
class="progress-bar-fill"
:style="{ width: `${seeking ? (duration > 0 ? (seekPosition / duration) * 100 : 0) : percent}%` }"
@@ -194,11 +219,18 @@ async function cycleLoop() {
:max="duration"
step="0.1"
:value="seeking ? seekPosition : elapsed"
:disabled="duration <= 0"
:disabled="duration <= 0 || seekSubmitting"
aria-label="Pozycja utworu"
@input="onSeekInput"
@change="commitSeek"
>
<span
v-if="hoverPosition !== null"
class="progress-time-tooltip"
:style="{ left: `${hoverOffset}px` }"
>
{{ formatTime(hoverPosition) }}
</span>
</div>
<span class="time-text">{{ duration > 0 ? formatTime(duration) : 'Live' }}</span>
</div>