Move the navigation into a floating toolbar at the bottom
The top app bar repeated the site's name, which already heads the upload and download pages, and spread its actions to the window's far edge. A floating toolbar centred above the bottom edge, as wide as its buttons, now holds them: Upload and the admin pages with the current one filled, and the account menu; guests get Upload, the theme toggle and Log in, without tooltips. On a phone the sign-in card no longer stretches to the full height, and the admin dashboard's empty state sits outside the scrolling table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
This commit is contained in:
co-authored by
Claude Opus 5
parent
2a363defd1
commit
accbc17c1c
@@ -3,13 +3,13 @@
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="min-h-dvh bg-surface font-sans text-on-surface antialiased">
|
||||
@include('partials.app-bar')
|
||||
|
||||
<main class="mx-auto w-full max-w-5xl px-4 pt-4 pb-16 sm:px-6">
|
||||
<body class="min-h-dvh bg-surface font-sans text-on-surface antialiased [--material-bottom-bar:calc(5rem+env(safe-area-inset-bottom))]">
|
||||
<main class="mx-auto w-full max-w-5xl px-4 pt-8 pb-32 sm:px-6 sm:pt-12">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
@include('partials.toolbar')
|
||||
|
||||
<x-toast />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="flex min-h-dvh flex-col bg-surface font-sans text-on-surface antialiased">
|
||||
@include('partials.app-bar')
|
||||
|
||||
<main class="flex flex-1 justify-center px-4 pt-6 pb-16 sm:items-center sm:pt-0">
|
||||
<body class="flex min-h-dvh flex-col bg-surface font-sans text-on-surface antialiased [--material-bottom-bar:calc(5rem+env(safe-area-inset-bottom))]">
|
||||
<main class="flex flex-1 items-start justify-center px-4 pt-8 pb-32 sm:items-center">
|
||||
<div class="w-full max-w-md rounded-corner-xl bg-surface-container-low p-6 sm:p-8">
|
||||
<div class="flex flex-col gap-6">
|
||||
{{ $slot }}
|
||||
@@ -14,6 +12,8 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@include('partials.toolbar')
|
||||
|
||||
<x-toast />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,51 +11,50 @@
|
||||
</div>
|
||||
|
||||
<x-card :title="__('All Shares')" variant="outlined">
|
||||
<div class="-mx-4 overflow-x-auto">
|
||||
<x-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<x-sort-header column="token" :sort-by="$sortBy">{{ __('Token') }}</x-sort-header>
|
||||
<x-sort-header column="files_count" :sort-by="$sortBy" class="text-end">{{ __('Files') }}</x-sort-header>
|
||||
<x-sort-header column="total_size" :sort-by="$sortBy" class="text-end">{{ __('Size') }}</x-sort-header>
|
||||
<x-sort-header column="download_count" :sort-by="$sortBy" class="text-end">{{ __('Downloads') }}</x-sort-header>
|
||||
<x-sort-header column="expires_at" :sort-by="$sortBy">{{ __('Expires') }}</x-sort-header>
|
||||
<x-sort-header column="created_at" :sort-by="$sortBy">{{ __('Created') }}</x-sort-header>
|
||||
<th><span class="sr-only">{{ __('Actions') }}</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($shares as $share)
|
||||
<tr wire:key="share-{{ $share->id }}">
|
||||
<td class="font-mono">{{ $share->token }}</td>
|
||||
<td class="text-end tabular-nums">{{ $share->files_count }}</td>
|
||||
<td class="text-end tabular-nums whitespace-nowrap">{{ Number::fileSize($share->total_size) }}</td>
|
||||
<td class="text-end tabular-nums">{{ $share->download_count }}</td>
|
||||
<td class="whitespace-nowrap">
|
||||
@if ($share->expires_at)
|
||||
<span @class(['text-error' => $share->isExpired()])>{{ $share->expires_at->diffForHumans() }}</span>
|
||||
@else
|
||||
<span class="text-on-surface-variant">{{ __('Never') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="whitespace-nowrap">{{ $share->created_at->diffForHumans() }}</td>
|
||||
<td class="text-end whitespace-nowrap">
|
||||
<x-button icon="open_in_new" :tooltip="__('Open')" :link="route('share.download', $share)" external />
|
||||
<x-button icon="delete" :tooltip="__('Delete')" color="error" wire:click="$set('deletingShareId', {{ $share->id }})" data-test="delete-share-{{ $share->id }}" />
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
{{-- Outside the table, so it stays centred on a phone instead of scrolling with the columns. --}}
|
||||
@if ($shares->total() === 0)
|
||||
<x-empty-state icon="link_off" :title="__('No shares yet')" :description="__('Shares appear here once someone uploads files.')" />
|
||||
@else
|
||||
<div class="-mx-4 overflow-x-auto">
|
||||
<x-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<x-empty-state icon="link_off" :title="__('No shares yet')" :description="__('Shares appear here once someone uploads files.')" />
|
||||
</td>
|
||||
<x-sort-header column="token" :sort-by="$sortBy">{{ __('Token') }}</x-sort-header>
|
||||
<x-sort-header column="files_count" :sort-by="$sortBy" class="text-end">{{ __('Files') }}</x-sort-header>
|
||||
<x-sort-header column="total_size" :sort-by="$sortBy" class="text-end">{{ __('Size') }}</x-sort-header>
|
||||
<x-sort-header column="download_count" :sort-by="$sortBy" class="text-end">{{ __('Downloads') }}</x-sort-header>
|
||||
<x-sort-header column="expires_at" :sort-by="$sortBy">{{ __('Expires') }}</x-sort-header>
|
||||
<x-sort-header column="created_at" :sort-by="$sortBy">{{ __('Created') }}</x-sort-header>
|
||||
<th><span class="sr-only">{{ __('Actions') }}</span></th>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</x-table>
|
||||
</div>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($shares as $share)
|
||||
<tr wire:key="share-{{ $share->id }}">
|
||||
<td class="font-mono">{{ $share->token }}</td>
|
||||
<td class="text-end tabular-nums">{{ $share->files_count }}</td>
|
||||
<td class="text-end tabular-nums whitespace-nowrap">{{ Number::fileSize($share->total_size) }}</td>
|
||||
<td class="text-end tabular-nums">{{ $share->download_count }}</td>
|
||||
<td class="whitespace-nowrap">
|
||||
@if ($share->expires_at)
|
||||
<span @class(['text-error' => $share->isExpired()])>{{ $share->expires_at->diffForHumans() }}</span>
|
||||
@else
|
||||
<span class="text-on-surface-variant">{{ __('Never') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="whitespace-nowrap">{{ $share->created_at->diffForHumans() }}</td>
|
||||
<td class="text-end whitespace-nowrap">
|
||||
<x-button icon="open_in_new" :tooltip="__('Open')" :link="route('share.download', $share)" external />
|
||||
<x-button icon="delete" :tooltip="__('Delete')" color="error" wire:click="$set('deletingShareId', {{ $share->id }})" data-test="delete-share-{{ $share->id }}" />
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</x-table>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">{{ $shares->links() }}</div>
|
||||
<div class="mt-4">{{ $shares->links() }}</div>
|
||||
@endif
|
||||
</x-card>
|
||||
|
||||
<x-modal wire:model="deletingShareId" :title="__('Delete this share?')" icon="delete">
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{{-- The one top app bar on every page: the site's logo and title, then the account menu for a
|
||||
signed-in user, or the theme toggle and a way to sign in for everyone else. --}}
|
||||
|
||||
@php
|
||||
$siteTitle = \App\Models\Setting::get('site_title') ?: config('app.name', 'SealShare');
|
||||
$siteLogo = \App\Models\Setting::get('site_logo');
|
||||
@endphp
|
||||
|
||||
<x-app-bar>
|
||||
<x-slot:navigation>
|
||||
<a href="{{ route('home') }}" class="focus-ring flex min-w-0 items-center gap-3 rounded-corner-full py-2 ps-3 pe-4" data-test="app-bar-home">
|
||||
@if ($siteLogo)
|
||||
<img src="{{ Storage::disk('public')->url($siteLogo) }}" alt="" class="h-8 w-auto" />
|
||||
@else
|
||||
<x-app-logo-icon class="size-8 shrink-0 text-primary" />
|
||||
@endif
|
||||
<span class="truncate type-title-lg text-on-surface">{{ $siteTitle }}</span>
|
||||
</a>
|
||||
</x-slot:navigation>
|
||||
|
||||
<x-slot:actions>
|
||||
@auth
|
||||
<span class="me-2 inline-flex">
|
||||
<x-account-menu :name="auth()->user()->name" :email="auth()->user()->email">
|
||||
<x-menu-item :label="__('Upload')" icon="upload" :link="route('upload')" />
|
||||
@if (auth()->user()->is_admin)
|
||||
<x-menu-item :label="__('Admin dashboard')" icon="dashboard" :link="route('admin.dashboard')" />
|
||||
<x-menu-item :label="__('Admin settings')" icon="admin_panel_settings" :link="route('admin.settings')" />
|
||||
@endif
|
||||
<x-menu-item :label="__('Settings')" icon="settings" :link="route('profile.edit')" />
|
||||
|
||||
<x-slot:footer>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<x-menu-item :label="__('Log out')" icon="logout" type="submit" data-test="logout-button" />
|
||||
</form>
|
||||
</x-slot:footer>
|
||||
</x-account-menu>
|
||||
</span>
|
||||
@else
|
||||
<x-theme-toggle />
|
||||
@if (Route::has('login') && ! request()->routeIs('login'))
|
||||
<span class="me-1 inline-flex"><x-button :label="__('Log in')" :link="route('login')" /></span>
|
||||
@endif
|
||||
@endauth
|
||||
</x-slot:actions>
|
||||
</x-app-bar>
|
||||
@@ -0,0 +1,44 @@
|
||||
{{-- The one navigation on every page: a floating toolbar centred above the bottom edge, as wide as
|
||||
its buttons. The site's name and logo head the upload and download pages instead of a bar.
|
||||
A signed-in user gets the pages they can reach and the account menu; everyone else gets the
|
||||
upload page, the theme toggle and a way to sign in — without tooltips, so the download page
|
||||
stays free of anchored components. The current page's button is filled. --}}
|
||||
|
||||
@php
|
||||
$onUpload = request()->routeIs('upload', 'share.created');
|
||||
$onDashboard = request()->routeIs('admin.dashboard');
|
||||
$onAdminSettings = request()->routeIs('admin.settings');
|
||||
@endphp
|
||||
|
||||
<nav aria-label="{{ __('Main') }}">
|
||||
<x-toolbar :label="__('Main')" place="bottom" data-test="app-toolbar">
|
||||
@auth
|
||||
<x-button icon="upload" :tooltip="__('Upload')" :link="route('upload')" :variant="$onUpload ? 'filled' : 'text'" :aria-current="$onUpload ? 'page' : null" />
|
||||
|
||||
@if (auth()->user()->is_admin)
|
||||
<x-button icon="dashboard" :tooltip="__('Admin dashboard')" :link="route('admin.dashboard')" :variant="$onDashboard ? 'filled' : 'text'" :aria-current="$onDashboard ? 'page' : null" />
|
||||
<x-button icon="admin_panel_settings" :tooltip="__('Admin settings')" :link="route('admin.settings')" :variant="$onAdminSettings ? 'filled' : 'text'" :aria-current="$onAdminSettings ? 'page' : null" />
|
||||
@endif
|
||||
|
||||
<span class="ms-1 inline-flex">
|
||||
<x-account-menu :name="auth()->user()->name" :email="auth()->user()->email" position="top-end">
|
||||
<x-menu-item :label="__('Settings')" icon="settings" :link="route('profile.edit')" />
|
||||
|
||||
<x-slot:footer>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<x-menu-item :label="__('Log out')" icon="logout" type="submit" data-test="logout-button" />
|
||||
</form>
|
||||
</x-slot:footer>
|
||||
</x-account-menu>
|
||||
</span>
|
||||
@else
|
||||
<x-button icon="upload" :aria-label="__('Upload')" :link="route('upload')" :variant="$onUpload ? 'filled' : 'text'" :aria-current="$onUpload ? 'page' : null" />
|
||||
<x-theme-toggle />
|
||||
|
||||
@if (Route::has('login') && ! request()->routeIs('login'))
|
||||
<x-button :label="__('Log in')" :link="route('login')" />
|
||||
@endif
|
||||
@endauth
|
||||
</x-toolbar>
|
||||
</nav>
|
||||
Reference in New Issue
Block a user