Media
Playback Speed Menu code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/playback-speed-menu.blade.php
@props([
'rates' => [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
'player' => 'default',
'label' => 'Speed',
'selected' => 1,
])
@php
/*
| Playback speed menu for a paired video/audio player. Dispatches
| `video-playback-rate` with the chosen rate.
|
| Usage:
| <x-ui.playback-speed-menu player="watch" :selected="1" />
*/
$normalized = collect($rates)
->map(fn ($rate) => round((float) $rate, 2))
->filter(fn ($rate) => $rate > 0)
->unique()
->values()
->all();
if ($normalized === []) {
$normalized = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
}
$initial = is_numeric($selected) ? round((float) $selected, 2) : 1.0;
if (! in_array($initial, $normalized, true)) {
$initial = in_array(1.0, $normalized, true) ? 1.0 : $normalized[0];
}
@endphp
<div
x-data="{
open: false,
player: @js((string) $player),
rates: @js($normalized),
selected: @js($initial),
format(rate) {
return Number(rate) === 1 ? 'Normal' : `${rate}×`;
},
choose(rate) {
this.selected = rate;
this.open = false;
this.$dispatch('video-playback-rate', {
player: this.player,
rate: Number(rate),
});
},
}"
@click.outside="open = false"
@keydown.escape.window="open = false"
{{ $attributes->merge(['class' => 'relative inline-flex max-w-full']) }}
>
<button
type="button"
@click="open = ! open"
class="inline-flex h-10 max-w-full items-center gap-2 rounded-lg border border-gray-200 bg-white px-3 text-sm font-medium text-gray-700 shadow-sm transition hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800 sm:h-9"
:aria-expanded="open.toString()"
aria-haspopup="listbox"
aria-label="{{ $label }}"
>
<x-ui.icon name="gauge" size="sm" class="shrink-0 text-gray-400" />
<span class="truncate" x-text="format(selected)"></span>
<x-ui.icon name="caret-down" weight="bold" size="sm" class="shrink-0 text-gray-400" />
</button>
<div
x-show="open"
x-cloak
x-transition.opacity.duration.150ms
role="listbox"
aria-label="{{ $label }}"
class="absolute end-0 z-40 mt-2 w-[min(100vw-2rem,12rem)] overflow-hidden rounded-xl border border-gray-200 bg-white p-1.5 shadow-lg ring-1 ring-black/5 dark:border-gray-800 dark:bg-gray-900"
>
<template x-for="rate in rates" :key="rate">
<button
type="button"
role="option"
@click="choose(rate)"
class="flex w-full items-center justify-between gap-3 rounded-lg px-3 py-2.5 text-start text-sm transition hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:hover:bg-gray-800"
:class="selected === rate ? 'bg-brand-50 text-brand-700 dark:bg-brand-950/40 dark:text-brand-300' : 'text-gray-700 dark:text-gray-200'"
:aria-selected="(selected === rate).toString()"
>
<span class="font-medium tabular-nums" x-text="format(rate)"></span>
<x-ui.icon x-show="selected === rate" name="check" weight="bold" size="sm" />
</button>
</template>
</div>
</div>