Media
Audio Playlist code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/audio-playlist.blade.php
@props([
'items' => [],
'title' => 'Up next',
'player' => 'default',
'activeId' => null,
])
@php
/*
| Audio playlist / queue for <x-ui.audio-player> and <x-ui.audio-wave-card>.
| Selecting a track dispatches `audio-source` scoped by `player`.
|
| Usage:
| <x-ui.audio-playlist player="radio" active-id="ep-12" :items="[...]" />
*/
$normalized = collect($items)->map(function ($item, $index) use ($activeId) {
$id = (string) ($item['id'] ?? $index);
return [
'id' => $id,
'title' => $item['title'] ?? 'Untitled',
'src' => $item['src'] ?? null,
'artwork' => $item['artwork'] ?? ($item['poster'] ?? null),
'duration' => $item['duration'] ?? null,
'artist' => $item['artist'] ?? ($item['uploader'] ?? null),
'active' => array_key_exists('active', $item)
? (bool) $item['active']
: ($activeId !== null && (string) $activeId === $id),
];
})->values()->all();
@endphp
<section
x-data="{
player: @js((string) $player),
items: @js($normalized),
activeId: @js($activeId !== null ? (string) $activeId : ($normalized[0]['id'] ?? null)),
select(item) {
if (! item?.src) { return; }
this.activeId = item.id;
this.$dispatch('audio-source', {
player: this.player,
src: item.src,
artwork: item.artwork,
title: item.title,
artist: item.artist,
play: true,
});
},
}"
{{ $attributes->merge(['class' => 'flex min-h-0 min-w-0 max-w-full flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900']) }}
aria-label="{{ $title }}"
>
<div class="flex items-center justify-between gap-3 border-b border-gray-200 px-3 py-3 sm:px-4 dark:border-gray-800">
<div class="min-w-0">
<h3 class="truncate text-sm font-semibold text-gray-900 dark:text-white">{{ $title }}</h3>
<p class="text-xs text-gray-500 dark:text-gray-400">
<span x-text="items.length"></span> tracks
</p>
</div>
</div>
<div class="max-h-[24rem] space-y-1.5 overflow-y-auto p-2 sm:max-h-[32rem] sm:space-y-2 sm:p-3">
<template x-for="item in items" :key="item.id">
<button
type="button"
@click="select(item)"
class="group flex w-full min-w-0 items-center gap-3 rounded-xl border border-transparent p-2 text-start transition hover:border-gray-200 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:hover:border-gray-800 dark:hover:bg-gray-950/60"
:class="activeId === item.id ? 'border-brand-200 bg-brand-50/70 dark:border-brand-900 dark:bg-brand-950/40' : ''"
:aria-current="activeId === item.id ? 'true' : 'false'"
>
<div class="relative h-12 w-12 shrink-0 overflow-hidden rounded-lg bg-gray-100 dark:bg-gray-800 sm:h-14 sm:w-14">
<template x-if="item.artwork">
<img :src="item.artwork" alt="" class="h-full w-full object-cover">
</template>
<template x-if="! item.artwork">
<div class="flex h-full w-full items-center justify-center text-gray-400 dark:text-gray-500">
<x-ui.icon name="music-notes" weight="fill" size="lg" />
</div>
</template>
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-semibold text-gray-900 dark:text-white" x-text="item.title"></p>
<p class="mt-0.5 truncate text-xs text-gray-500 dark:text-gray-400">
<span x-show="item.artist" x-text="item.artist"></span>
<span x-show="item.artist && item.duration"> · </span>
<span x-show="item.duration" class="font-mono tabular-nums" x-text="item.duration"></span>
</p>
</div>
<span
x-show="activeId === item.id"
class="inline-flex shrink-0 items-center gap-1 rounded-md bg-brand-600 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-white"
>
<x-ui.icon name="play" weight="fill" size="xs" />
Now
</span>
</button>
</template>
<template x-if="items.length === 0">
<p class="px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400">No tracks in this queue yet.</p>
</template>
</div>
</section>