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>