Scroll a long file list on the download page on its own

A share with hundreds of files made the download page as tall as its
list, so the recipient scrolled far past the buttons. The list now sits
in .share-file-list, capped at min(32rem, 60dvh) with its own scroll, as
the upload page's selected-files list already is; the download buttons
stay on the first screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-10-08 22:40:34 +02:00
co-authored by Claude Opus 5.5
parent 63bae2d1a6
commit 6c3202a0cf
3 changed files with 47 additions and 19 deletions
+10
View File
@@ -243,6 +243,16 @@
block-size: 100%; block-size: 100%;
} }
/*
* resources/views/livewire/share-download.blade.php: a long file list scrolls on its own, as the
* upload page's does, so the download buttons stay near the top of the page. It is the page's main
* content, so it may take more of the window.
*/
.share-file-list {
max-block-size: min(32rem, 60dvh);
overflow-y: auto;
}
/* /*
* resources/views/livewire/share-download.blade.php: the private text once the recipient shows it, * resources/views/livewire/share-download.blade.php: the private text once the recipient shows it,
* as it was typed — its line breaks kept and a long word or key wrapped rather than overflowing. * as it was typed — its line breaks kept and a long word or key wrapped rather than overflowing.
@@ -94,25 +94,27 @@
})" })"
x-on:beforeunload.window="warnBeforeLeaving($event)" x-on:beforeunload.window="warnBeforeLeaving($event)"
> >
<x-list :label="__('Shared Files')"> <div class="share-file-list" data-test="share-file-list">
@foreach ($files as $file) <x-list :label="__('Shared Files')">
<x-list-item @foreach ($files as $file)
:title="$file->relative_path ?: $file->original_name" <x-list-item
icon="description" :title="$file->relative_path ?: $file->original_name"
wire:key="file-{{ $file->id }}" icon="description"
> wire:key="file-{{ $file->id }}"
<x-slot:description><span class="md-tabular">{{ Number::fileSize($file->file_size) }}</span></x-slot:description> >
<x-slot:end> <x-slot:description><span class="md-tabular">{{ Number::fileSize($file->file_size) }}</span></x-slot:description>
<x-row gap="space100"> <x-slot:end>
<span x-show="isDone({{ $file->id }})" x-cloak data-test="file-downloaded"> <x-row gap="space100">
<x-icon name="download_done" class="md-ink-success" :label="__('Downloaded')" /> <span x-show="isDone({{ $file->id }})" x-cloak data-test="file-downloaded">
</span> <x-icon name="download_done" class="md-ink-success" :label="__('Downloaded')" />
<x-button icon="download" :link="route('share.download.file', [$share, $file])" no-wire-navigate :aria-label="__('Download :name', ['name' => $file->original_name])" x-on:click="downloaded = true; markDone({{ $file->id }})" /> </span>
</x-row> <x-button icon="download" :link="route('share.download.file', [$share, $file])" no-wire-navigate :aria-label="__('Download :name', ['name' => $file->original_name])" x-on:click="downloaded = true; markDone({{ $file->id }})" />
</x-slot:end> </x-row>
</x-list-item> </x-slot:end>
@endforeach </x-list-item>
</x-list> @endforeach
</x-list>
</div>
<div x-show="running || finished" x-cloak data-test="folder-download"> <div x-show="running || finished" x-cloak data-test="folder-download">
<x-stack gap="space100"> <x-stack gap="space100">
+16
View File
@@ -297,3 +297,19 @@ test('Browse Folder picks a whole folder and keeps each file\'s path in it', fun
$page->assertSee('holiday/docs/notes.txt') $page->assertSee('holiday/docs/notes.txt')
->assertNoJavaScriptErrors(); ->assertNoJavaScriptErrors();
}); });
test('a long file list scrolls on its own and keeps the download buttons on the first screen', function () {
$share = app(ShareService::class)->createShare(collect(range(1, 40))->map(fn (int $number): array => [
'file' => UploadedFile::fake()->createWithContent("file-{$number}.txt", "file {$number}"),
'relativePath' => null,
])->all());
$page = ready(visit(route('share.download', $share, false))->resize(1280, 800));
$page->assertScript("(() => {
const list = document.querySelector('[data-test=share-file-list]');
return list.scrollHeight > list.clientHeight && getComputedStyle(list).overflowY === 'auto';
})()")
->assertScript("[...document.querySelectorAll('[data-md-button]')].filter((button) => button.offsetParent !== null && button.textContent.trim().toLowerCase().startsWith('download all')).every((button) => button.getBoundingClientRect().bottom <= window.innerHeight)")
->assertNoJavaScriptErrors();
});