Skip to main content

General

File Viewer code

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

resources/views/components/ui/file-viewer.blade.php

@props([
    'name' => 'Untitled file',
    'type' => null,
    'size' => null,
    'src' => null,
    'icon' => null,
    'modified' => null,
    'created' => null,
    'owner' => null,
    'ownerAvatar' => null,
    'location' => null,
    'dimensions' => null,
    'meta' => [],
    'tags' => [],
    'downloadHref' => null,
    'closeHref' => null,
])

@php
    /*
    | File viewer (details). A preview pane (image preview or a type-aware icon)
    | with a header, action toolbar, a details list (type, size, owner, dates,
    | location, dimensions plus any extra `meta`), optional tags and footer/extra
    | slots. Drop it in a drawer, modal, split panel or detail column.
    |
    | Usage:
    |   <x-ui.file-viewer
    |       name="annual-report.pdf" type="PDF document" size="2.4 MB"
    |       owner="Ada Lovelace" modified="2 hours ago" location="Documents / 2026"
    |       :download-href="route('files.download', $file)"
    |   />
    |   <x-ui.file-viewer name="cover.jpg" src="/images/cover.jpg" type="JPEG image" dimensions="2400 × 1600" />
    */
    $typeIcons = [
        'image' => 'image', 'video' => 'file-video', 'audio' => 'file-audio',
        'pdf' => 'file-pdf', 'archive' => 'file-zip', 'spreadsheet' => 'file-xls',
        'document' => 'file-doc', 'code' => 'file-code', 'text' => 'file-text',
    ];

    $extension = \Illuminate\Support\Str::of($name)->afterLast('.')->lower()->value();
    $guessed = match (true) {
        $src !== null, in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'], true) => 'image',
        $extension === 'pdf' => 'pdf',
        in_array($extension, ['zip', 'rar', '7z', 'tar', 'gz'], true) => 'archive',
        in_array($extension, ['mp4', 'mov', 'webm', 'avi'], true) => 'video',
        in_array($extension, ['mp3', 'wav', 'ogg', 'flac'], true) => 'audio',
        in_array($extension, ['xls', 'xlsx', 'csv'], true) => 'spreadsheet',
        in_array($extension, ['doc', 'docx'], true) => 'document',
        in_array($extension, ['js', 'ts', 'json', 'php', 'css', 'html', 'vue', 'py'], true) => 'code',
        in_array($extension, ['txt', 'md'], true) => 'text',
        default => 'file',
    };

    $resolvedIcon = $icon ?? ($typeIcons[$guessed] ?? 'file');

    $details = collect([
        'Type' => $type,
        'Size' => $size,
        'Dimensions' => $dimensions,
        'Location' => $location,
        'Created' => $created,
        'Modified' => $modified,
    ])->merge($meta)->filter(fn ($value) => filled($value))->all();
@endphp

<aside {{ $attributes->merge(['class' => 'flex flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900']) }}>
    {{-- Header --}}
    <div class="flex items-start justify-between gap-3 border-b border-gray-200 px-5 py-4 dark:border-gray-800">
        <div class="flex min-w-0 items-center gap-3">
            <span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-300">
                <x-ui.icon :name="$resolvedIcon" weight="fill" size="xl" />
            </span>
            <div class="min-w-0">
                <h3 class="truncate text-sm font-semibold text-gray-900 dark:text-white" title="{{ $name }}">{{ $name }}</h3>
                @if ($type)
                    <p class="truncate text-xs text-gray-500 dark:text-gray-400">{{ $type }}</p>
                @endif
            </div>
        </div>

        @if ($closeHref)
            <x-ui.icon-button :href="$closeHref" icon="x" label="Close" size="sm" />
        @endif
    </div>

    {{-- Preview --}}
    <div class="flex min-h-44 items-center justify-center border-b border-gray-200 bg-gray-50 p-5 dark:border-gray-800 dark:bg-gray-950/40">
        @isset($preview)
            {{ $preview }}
        @elseif ($src)
            <img src="{{ $src }}" alt="{{ $name }}" class="max-h-72 w-auto rounded-lg object-contain shadow-sm">
        @else
            <div class="flex flex-col items-center gap-2 text-gray-400 dark:text-gray-500">
                <x-ui.icon :name="$resolvedIcon" weight="duotone" size="5xl" />
                <span class="text-xs font-medium uppercase tracking-wide">{{ $type ?? 'No preview' }}</span>
            </div>
        @endif
    </div>

    {{-- Actions --}}
    <div class="flex flex-wrap items-center gap-2 border-b border-gray-200 px-5 py-3 dark:border-gray-800">
        @isset($actions)
            {{ $actions }}
        @else
            <x-ui.button :href="$downloadHref" size="sm" icon="download-simple">Download</x-ui.button>
            <x-ui.button type="button" variant="outline" size="sm" icon="share-network">Share</x-ui.button>
            <x-ui.dropdown align="right" width="48">
                <x-slot:trigger>
                    <x-ui.icon-button icon="dots-three" label="More actions" size="sm" variant="outline" />
                </x-slot:trigger>
                <x-ui.dropdown.item icon="pencil-simple">Rename</x-ui.dropdown.item>
                <x-ui.dropdown.item icon="copy">Make a copy</x-ui.dropdown.item>
                <x-ui.dropdown.item icon="folder-open">Move to</x-ui.dropdown.item>
                <x-ui.dropdown.item icon="trash" variant="danger">Delete</x-ui.dropdown.item>
            </x-ui.dropdown>
        @endisset
    </div>

    {{-- Details --}}
    <div class="flex-1 px-5 py-4">
        @if ($owner || $ownerAvatar)
            <div class="mb-4 flex items-center gap-3 border-b border-gray-100 pb-4 dark:border-gray-800">
                <x-ui.avatar :name="$owner" :src="$ownerAvatar" size="sm" />
                <div class="min-w-0">
                    <p class="text-xs text-gray-400 dark:text-gray-500">Owner</p>
                    <p class="truncate text-sm font-medium text-gray-800 dark:text-gray-100">{{ $owner ?? 'Unknown' }}</p>
                </div>
            </div>
        @endif

        @if (! empty($details))
            <p class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500">Details</p>
            <x-ui.description-list compact :items="$details" />
        @endif

        @if (! empty($tags))
            <div class="mt-4">
                <p class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500">Tags</p>
                <div class="flex flex-wrap gap-1.5">
                    @foreach ($tags as $tag)
                        <x-ui.badge variant="gray" size="sm">{{ $tag }}</x-ui.badge>
                    @endforeach
                </div>
            </div>
        @endif

        {{ $slot }}
    </div>

    @isset($footer)
        <div class="border-t border-gray-200 px-5 py-4 dark:border-gray-800">
            {{ $footer }}
        </div>
    @endisset
</aside>