feat(web/m7-364): QueueTrackRow with neodrag reorder + remove

This commit is contained in:
2026-05-03 21:41:59 -04:00
parent b2e3913df8
commit 57a353a138
2 changed files with 135 additions and 0 deletions
@@ -0,0 +1,71 @@
<script lang="ts">
import { GripVertical, X } from 'lucide-svelte';
import { draggable } from '@neodrag/svelte';
import type { DragEventData } from '@neodrag/svelte';
import type { TrackRef } from '$lib/api/types';
import { playFromQueueIndex, removeFromQueue, moveQueueItem } from '$lib/player/store.svelte';
let { track, index, isCurrent } = $props<{
track: TrackRef;
index: number;
isCurrent: boolean;
}>();
// Approximate row height in px — used to translate the drop offset
// into a destination index. The row's Tailwind sets h-16 (64px).
const ROW_HEIGHT = 64;
function handleBodyClick() {
if (isCurrent) return;
playFromQueueIndex(index);
}
function handleRemove(e: MouseEvent) {
e.stopPropagation();
removeFromQueue(index);
}
function handleDragEnd(data: DragEventData) {
const delta = Math.round(data.offsetY / ROW_HEIGHT);
if (delta === 0) return;
moveQueueItem(index, index + delta);
}
</script>
<div
use:draggable={{ axis: 'y', bounds: 'parent', onDragEnd: handleDragEnd }}
class="flex items-center gap-2 border-b border-border px-3 py-2 h-16
{isCurrent ? 'border-l-2 border-l-accent bg-surface-hover' : ''}"
>
<button
type="button"
aria-label="Reorder track"
class="cursor-grab text-text-secondary hover:text-text-primary flex-shrink-0"
>
<GripVertical size={16} />
</button>
<button
type="button"
onclick={handleBodyClick}
class="flex-1 text-left min-w-0 {isCurrent ? 'cursor-default' : 'cursor-pointer'}"
aria-label={isCurrent ? `Now playing — ${track.title}` : `Play ${track.title}`}
>
<div class="flex items-center gap-2 min-w-0">
{#if isCurrent}
<span class="text-xs text-accent uppercase tracking-wide flex-shrink-0">Now playing</span>
{/if}
<span class="font-medium truncate text-text-primary">{track.title}</span>
</div>
<div class="text-xs text-text-secondary truncate">{track.artist_name}</div>
</button>
<button
type="button"
onclick={handleRemove}
aria-label="Remove from queue"
class="text-text-secondary hover:text-text-primary flex-shrink-0"
>
<X size={16} />
</button>
</div>