Skip to main content

Media

Video Meta code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/video-meta.blade.php

@props([
    'title' => null,
    'description' => null,
    'uploader' => null,
    'uploaderAvatar' => null,
    'uploaderHref' => null,
    'uploaderLabel' => 'Uploaded by',
    'uploadedAt' => null,
    'uploadedAtDatetime' => null,
    'views' => null,
    'resolution' => null,
    'bitrate' => null,
    'duration' => null,
    'size' => null,
    'codec' => null,
    'format' => null,
    'fps' => null,
    'items' => [],
    'meta' => [],
    'tags' => [],
    'layout' => 'bar',
])

@php
    /*
    | Video metadata companion for <x-ui.video-player>. Renders the uploader
    | byline, published date, and technical facts (views, resolution, bitrate,
    | duration, size, codec, format, fps). Sit it directly under the player.
    |
    | layout:
    |   bar   — icon + value status strip (default)
    |   chips — badge-split key/value chips
    |   list  — compact description list
    |
    | Usage:
    |   <x-ui.video-meta
    |       title="Spring bloom"
    |       uploader="Ada Lovelace"
    |       uploaded-at="Jun 12, 2026"
    |       views="12.4k"
    |       resolution="1920 × 1080"
    |       bitrate="4.2 Mbps"
    |       duration="0:05"
    |   />
    */
    $layout = in_array($layout, ['bar', 'chips', 'list'], true) ? $layout : 'bar';

    $factDefs = [
        ['key' => 'views', 'label' => 'Views', 'icon' => 'eye', 'value' => $views, 'variant' => 'brand'],
        ['key' => 'uploadedAt', 'label' => 'Published', 'icon' => 'calendar-blank', 'value' => $uploadedAt, 'variant' => 'gray', 'datetime' => $uploadedAtDatetime],
        ['key' => 'duration', 'label' => 'Duration', 'icon' => 'timer', 'value' => $duration, 'variant' => 'gray'],
        ['key' => 'resolution', 'label' => 'Resolution', 'icon' => 'frame-corners', 'value' => $resolution, 'variant' => 'blue'],
        ['key' => 'bitrate', 'label' => 'Bitrate', 'icon' => 'waveform', 'value' => $bitrate, 'variant' => 'amber'],
        ['key' => 'fps', 'label' => 'Frame rate', 'icon' => 'film-strip', 'value' => $fps, 'variant' => 'gray'],
        ['key' => 'codec', 'label' => 'Codec', 'icon' => 'cpu', 'value' => $codec, 'variant' => 'gray'],
        ['key' => 'format', 'label' => 'Format', 'icon' => 'file-video', 'value' => $format, 'variant' => 'gray'],
        ['key' => 'size', 'label' => 'Size', 'icon' => 'hard-drive', 'value' => $size, 'variant' => 'gray'],
    ];

    $facts = collect($factDefs)
        ->filter(fn (array $fact) => filled($fact['value']))
        ->values();

    $statusItems = $facts
        ->map(function (array $fact) {
            $item = [
                'icon' => $fact['icon'],
                'value' => $fact['value'],
                'title' => $fact['label'],
            ];

            if ($fact['key'] === 'uploadedAt' && filled($fact['datetime'] ?? null)) {
                $item['label'] = null;
            }

            return $item;
        })
        ->merge($items)
        ->filter(fn ($item) => filled($item['icon'] ?? null) || filled($item['label'] ?? null) || filled($item['value'] ?? null))
        ->values()
        ->all();

    $detailItems = $facts
        ->mapWithKeys(fn (array $fact) => [$fact['label'] => $fact['value']])
        ->merge($meta)
        ->filter(fn ($value) => filled($value))
        ->all();

    $hasUploader = filled($uploader) || filled($uploaderAvatar);
    $hasHeader = filled($title) || filled($description) || $hasUploader || isset($actions);
    $hasFacts = $layout === 'bar'
        ? $statusItems !== []
        : ($layout === 'chips' ? $facts->isNotEmpty() || $items !== [] : $detailItems !== []);
    $hasTags = $tags !== [];
@endphp

<section
    {{ $attributes->merge(['class' => 'min-w-0 max-w-full rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900 sm:p-5']) }}
    @if (filled($title)) aria-label="{{ $title }} metadata" @else aria-label="Video metadata" @endif
>
    @if ($hasHeader)
        <div @class(['flex flex-col gap-4', 'mb-4' => $hasFacts || $hasTags || ! $slot->isEmpty()])>
            <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
                <div class="min-w-0 flex-1 space-y-3">
                    @if (filled($title) || filled($description))
                        <div class="min-w-0">
                            @if (filled($title))
                                <h3 class="text-base font-semibold tracking-tight text-gray-900 dark:text-white sm:truncate">{{ $title }}</h3>
                            @endif
                            @if (filled($description))
                                <p class="mt-1 text-sm leading-6 text-gray-600 dark:text-gray-400">{{ $description }}</p>
                            @endif
                        </div>
                    @endif

                    @if ($hasUploader || filled($uploadedAt))
                        <div class="flex flex-wrap items-center gap-x-4 gap-y-2">
                            @if ($hasUploader)
                                @if ($uploaderHref)
                                    <a href="{{ $uploaderHref }}" class="group flex min-w-0 items-center gap-3 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500">
                                        <x-ui.avatar :src="$uploaderAvatar" :name="$uploader" size="sm" />
                                        <div class="min-w-0">
                                            <p class="truncate text-sm font-semibold text-gray-900 group-hover:text-brand-600 dark:text-white dark:group-hover:text-brand-400">{{ $uploader }}</p>
                                            <p class="text-xs text-gray-500 dark:text-gray-400">{{ $uploaderLabel }}</p>
                                        </div>
                                    </a>
                                @else
                                    <div class="flex min-w-0 items-center gap-3">
                                        <x-ui.avatar :src="$uploaderAvatar" :name="$uploader" size="sm" />
                                        <div class="min-w-0">
                                            <p class="truncate text-sm font-semibold text-gray-900 dark:text-white">{{ $uploader }}</p>
                                            <p class="text-xs text-gray-500 dark:text-gray-400">{{ $uploaderLabel }}</p>
                                        </div>
                                    </div>
                                @endif
                            @endif

                            @if (filled($uploadedAt) && ! $hasFacts)
                                <div class="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
                                    <x-ui.icon name="calendar-blank" size="sm" class="text-gray-400 dark:text-gray-500" />
                                    <time @if (filled($uploadedAtDatetime)) datetime="{{ $uploadedAtDatetime }}" @endif>{{ $uploadedAt }}</time>
                                </div>
                            @endif
                        </div>
                    @endif
                </div>

                @isset($actions)
                    <div class="flex w-full min-w-0 flex-wrap items-center gap-2 sm:w-auto sm:max-w-[min(100%,24rem)] sm:shrink-0 sm:justify-end">
                        {{ $actions }}
                    </div>
                @endisset
            </div>
        </div>
    @endif

    @if ($hasFacts)
        @if ($layout === 'bar')
            <x-ui.status-bar :items="$statusItems" @class(['mb-4' => $hasTags || ! $slot->isEmpty()]) />
        @elseif ($layout === 'chips')
            <div @class(['flex flex-wrap items-center gap-2', 'mb-4' => $hasTags || ! $slot->isEmpty()])>
                @foreach ($facts as $fact)
                    <x-ui.badge-split :label="$fact['label']" :variant="$fact['variant']" :mono="in_array($fact['key'], ['bitrate', 'resolution', 'fps', 'codec', 'format', 'size', 'duration'], true)">
                        @if ($fact['key'] === 'uploadedAt' && filled($fact['datetime'] ?? null))
                            <time datetime="{{ $fact['datetime'] }}">{{ $fact['value'] }}</time>
                        @else
                            {{ $fact['value'] }}
                        @endif
                    </x-ui.badge-split>
                @endforeach

                @foreach ($items as $item)
                    @if (filled($item['label'] ?? null) && filled($item['value'] ?? null))
                        <x-ui.badge-split :label="$item['label']" variant="gray" mono>
                            {{ $item['value'] }}
                        </x-ui.badge-split>
                    @endif
                @endforeach
            </div>
        @else
            <div @class(['mb-4' => $hasTags || ! $slot->isEmpty()])>
                <x-ui.description-list compact :items="$detailItems" />
            </div>
        @endif
    @endif

    @if ($hasTags)
        <div @class(['flex flex-wrap gap-1.5', 'mb-4' => ! $slot->isEmpty()])>
            @foreach ($tags as $tag)
                <x-ui.badge variant="gray" size="sm">{{ $tag }}</x-ui.badge>
            @endforeach
        </div>
    @endif

    @if (! $slot->isEmpty())
        <div>
            {{ $slot }}
        </div>
    @endif
</section>