Skip to main content

Media

Video Card code

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

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

@props([
    'title',
    'href' => null,
    'src' => null,
    'poster' => null,
    'uploader' => null,
    'uploaderAvatar' => null,
    'duration' => null,
    'views' => null,
    'uploadedAt' => null,
    'player' => 'default',
    'active' => false,
    'orientation' => 'vertical',
])

@php
    /*
    | Thumbnail card for video grids, playlists and “up next” rails.
    | When `href` is omitted and `src` is set, clicking dispatches
    | `video-source` for a paired <x-ui.video-player :name="$player">.
    |
    | orientation: vertical (default), horizontal
    |
    | Usage:
    |   <x-ui.video-card title="Spring bloom" poster="..." duration="0:05" views="12.4k" />
    */
    $orientation = in_array($orientation, ['vertical', 'horizontal'], true) ? $orientation : 'vertical';
    $isHorizontal = $orientation === 'horizontal';
    $isButton = blank($href);
@endphp

@if ($isButton)
    <button
        type="button"
        @if (filled($src))
            @click="$dispatch('video-source', {
                player: @js((string) $player),
                src: @js((string) $src),
                poster: @js($poster),
                title: @js((string) $title),
                play: true,
            })"
        @endif
        {{ $attributes->merge([
            'class' => ($isHorizontal
                ? 'group flex w-full items-stretch gap-3 rounded-2xl border border-gray-200 bg-white p-2 text-start shadow-sm transition hover:border-brand-300 hover:bg-brand-50/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:border-gray-800 dark:bg-gray-900 dark:hover:border-brand-700 dark:hover:bg-brand-950/30'
                : 'group flex w-full flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white text-start shadow-sm transition hover:border-brand-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:border-gray-800 dark:bg-gray-900 dark:hover:border-brand-700'
            ).($active ? ' border-brand-500 ring-1 ring-brand-500/30 dark:border-brand-400' : ''),
        ]) }}
    >
@else
    <a
        href="{{ $href }}"
        {{ $attributes->merge([
            'class' => ($isHorizontal
                ? 'group flex w-full items-stretch gap-3 rounded-2xl border border-gray-200 bg-white p-2 text-start shadow-sm transition hover:border-brand-300 hover:bg-brand-50/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:border-gray-800 dark:bg-gray-900 dark:hover:border-brand-700 dark:hover:bg-brand-950/30'
                : 'group flex w-full flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white text-start shadow-sm transition hover:border-brand-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:border-gray-800 dark:bg-gray-900 dark:hover:border-brand-700'
            ).($active ? ' border-brand-500 ring-1 ring-brand-500/30 dark:border-brand-400' : ''),
        ]) }}
    >
@endif
    <div @class([
        'relative overflow-hidden bg-gray-100 dark:bg-gray-800',
        'aspect-video w-full' => ! $isHorizontal,
        'aspect-video w-32 shrink-0 rounded-xl sm:w-40' => $isHorizontal,
    ])>
        @if (filled($poster))
            <img src="{{ $poster }}" alt="" class="h-full w-full object-cover transition duration-300 group-hover:scale-[1.02]" loading="lazy">
        @else
            <div class="flex h-full w-full items-center justify-center text-gray-400 dark:text-gray-500">
                <x-ui.icon name="play" weight="fill" size="2xl" />
            </div>
        @endif

        @if (filled($duration))
            <span class="absolute bottom-2 end-2 rounded-md bg-black/75 px-1.5 py-0.5 font-mono text-[11px] font-medium tabular-nums text-white">{{ $duration }}</span>
        @endif

        @if ($active)
            <span class="absolute start-2 top-2 inline-flex 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>
        @endif
    </div>

    <div @class(['min-w-0 flex-1', 'p-3 sm:p-4' => ! $isHorizontal, 'py-1 pe-1' => $isHorizontal])>
        <p class="line-clamp-2 text-sm font-semibold text-gray-900 dark:text-white">{{ $title }}</p>

        @if (filled($uploader) || filled($views) || filled($uploadedAt))
            <div class="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-gray-500 dark:text-gray-400">
                @if (filled($uploader))
                    <span class="inline-flex min-w-0 items-center gap-1.5">
                        <x-ui.avatar :src="$uploaderAvatar" :name="$uploader" size="xs" />
                        <span class="truncate font-medium text-gray-700 dark:text-gray-300">{{ $uploader }}</span>
                    </span>
                @endif
                @if (filled($views))
                    <span class="tabular-nums">{{ $views }} views</span>
                @endif
                @if (filled($uploadedAt))
                    <span>{{ $uploadedAt }}</span>
                @endif
            </div>
        @endif

        {{ $slot }}
    </div>
@if ($isButton)
    </button>
@else
    </a>
@endif