Skip to content

Commit d1ca855

Browse files
committed
feat: add lazyLoad, showFileSize, showFileCount, and loadingSkeleton features
- Add lazyLoad() method for lazy loading images (loading=lazy attribute) - Add showFileSize() method to display file sizes (B, KB, MB, GB, TB) - Add showFileCount() method to display file count badge above grid - Add loadingSkeleton() method to show skeleton placeholders while loading - Add formatFileSize() and getFileSize() helper methods - Add tests for all new features - Update README.md with documentation and examples
1 parent e014dc2 commit d1ca855

File tree

4 files changed

+235
-13
lines changed

4 files changed

+235
-13
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ FileViewEntry::make('documents')
9696
| `withModalEye(bool $enabled)` | Add eye button for modal preview when asModal(false) |
9797
| `showAsLink(bool $enabled)` | Show as compact list (default: false) |
9898
| `contained(bool $enabled)` | Show background/border (default: true) |
99+
| `lazyLoad(bool $enabled)` | Lazy load images (default: false) |
100+
| `showFileSize(bool $enabled)` | Show file size (default: false) |
101+
| `showFileCount(bool $enabled)` | Show file count badge (default: false) |
102+
| `loadingSkeleton(bool $enabled)` | Show loading skeleton (default: false) |
99103
| `disk(string $disk)` | Storage disk name |
100104
| `downloadable(bool $enabled)` | Show download button |
101105
| `previewHeight(int\|string $height)` | Modal height (default: 300px) |
@@ -199,6 +203,44 @@ FileViewEntry::make('attachments')
199203

200204
This is useful when you want a cleaner look or when nesting inside other containers.
201205

206+
### Additional Features
207+
208+
#### Lazy Loading Images
209+
Improve page performance by lazy loading images:
210+
211+
```php
212+
FileViewEntry::make('attachments')
213+
->lazyLoad(true) // Enable lazy loading for images
214+
->grid(4);
215+
```
216+
217+
#### Show File Size
218+
Display file size next to the filename:
219+
220+
```php
221+
FileViewEntry::make('attachments')
222+
->showFileSize(true) // Show file size (e.g., "2.5 MB")
223+
->grid(4);
224+
```
225+
226+
#### File Count Badge
227+
Show a badge with the total number of files:
228+
229+
```php
230+
FileViewEntry::make('attachments')
231+
->showFileCount(true) // Show "3 files" badge
232+
->grid(4);
233+
```
234+
235+
#### Loading Skeleton
236+
Show skeleton placeholders while files are loading:
237+
238+
```php
239+
FileViewEntry::make('attachments')
240+
->loadingSkeleton(true) // Show loading skeleton
241+
->grid(4);
242+
```
243+
202244
## Customization
203245

204246
### Publish Views

resources/views/infolists/components/file-view-entry.blade.php

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
$asModal = $shouldShowAsModal();
99
$withModalEye = $shouldShowWithModalEye();
1010
$contained = $isContained();
11+
$lazyLoad = $shouldLazyLoad();
12+
$showFileSize = $shouldShowFileSize();
13+
$showFileCount = $shouldShowFileCount();
14+
$loadingSkeleton = $shouldShowLoadingSkeleton();
1115
$downloadable = $isDownloadable();
1216
$height = $getPreviewHeight();
1317
$gridColumns = $getGridColumns();
@@ -64,10 +68,33 @@
6468
}
6569
return $default;
6670
};
71+
72+
// Helper to get file size
73+
$getFileSize = function($path) use ($entry) {
74+
return $entry->getFileSize($path);
75+
};
76+
};
6777
@endphp
6878

6979
<div {{ $getExtraAttributeBag()->class(['fi-in-file-view w-full']) }}>
70-
@if($fileCount > 0)
80+
{{-- File count badge --}}
81+
@if($showFileCount && $fileCount > 0)
82+
<div class="mb-2 text-sm text-gray-500 dark:text-gray-400">
83+
{{ $fileCount }} {{ $fileCount === 1 ? __('file') : __('files') }}
84+
</div>
85+
@endif
86+
87+
{{-- Loading skeleton --}}
88+
@if($loadingSkeleton && empty($files))
89+
<div class="grid {{ $gridClass }} gap-4 w-full">
90+
@for($i = 0; $i < 4; $i++)
91+
<div class="w-full aspect-square p-4 rounded-2xl bg-gray-100 dark:bg-gray-800 animate-pulse">
92+
<div class="w-12 h-12 mx-auto mb-4 bg-gray-200 dark:bg-gray-700 rounded"></div>
93+
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4 mx-auto"></div>
94+
</div>
95+
@endfor
96+
</div>
97+
@elseif($fileCount > 0)
7198
<div class="grid {{ $gridClass }} gap-4 w-full">
7299
@foreach($files as $index => $file)
73100
@php
@@ -86,6 +113,7 @@
86113
$fileType = $filePath ? $getFileType($filePath) : 'other';
87114
$fileIcon = $getFileIcon($fileType);
88115
$canPreview = $filePath ? $canPreviewInBrowser($filePath) : false;
116+
$fileSize = ($showFileSize && $filePath) ? $getFileSize($filePath) : null;
89117
$modalId = 'file-preview-modal-' . $entry->getName() . '-' . $index;
90118
91119
// Format date (only if dateKey is set)
@@ -121,9 +149,11 @@ class="group cursor-pointer"
121149
{{-- Filename --}}
122150
<span class="text-sm font-medium text-gray-900 dark:text-white truncate flex-1">{{ $fileTitle }}</span>
123151

124-
@if($displayDate)
125-
{{-- Date --}}
126-
<span class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">{{ $displayDate }}</span>
152+
@if($displayDate || $fileSize)
153+
{{-- Date and/or Size --}}
154+
<span class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">
155+
{{ $displayDate }}{{ $displayDate && $fileSize ? ' · ' : '' }}{{ $fileSize }}
156+
</span>
127157
@endif
128158
</div>
129159

@@ -185,6 +215,7 @@ class="text-gray-400 hover:text-gray-500 focus:outline-none flex-shrink-0"
185215
src="{{ $fileUrl }}"
186216
alt="{{ $fileTitle }}"
187217
class="w-full h-auto object-contain bg-gray-100 dark:bg-gray-800"
218+
@if($lazyLoad) loading="lazy" @endif
188219
/>
189220
@break
190221

@@ -294,9 +325,11 @@ class="mt-3 inline-flex w-full justify-center rounded-md bg-white dark:bg-gray-8
294325
{{-- Filename --}}
295326
<span class="text-sm font-medium text-gray-900 dark:text-white truncate flex-1">{{ $fileTitle }}</span>
296327

297-
@if($displayDate)
298-
{{-- Date --}}
299-
<span class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">{{ $displayDate }}</span>
328+
@if($displayDate || $fileSize)
329+
{{-- Date and/or Size --}}
330+
<span class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">
331+
{{ $displayDate }}{{ $displayDate && $fileSize ? ' · ' : '' }}{{ $fileSize }}
332+
</span>
300333
@endif
301334
</a>
302335
@elseif($asModal)
@@ -322,9 +355,11 @@ class="group cursor-pointer"
322355
{{-- Filename --}}
323356
<span class="text-sm font-medium text-gray-900 dark:text-white truncate w-full mt-2">{{ $fileTitle }}</span>
324357

325-
@if($displayDate)
326-
{{-- Date --}}
327-
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ $displayDate }}</span>
358+
@if($displayDate || $fileSize)
359+
{{-- Date and/or Size --}}
360+
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1">
361+
{{ $displayDate }}{{ $displayDate && $fileSize ? ' · ' : '' }}{{ $fileSize }}
362+
</span>
328363
@endif
329364
</div>
330365

@@ -522,8 +557,10 @@ class="text-gray-400 hover:text-primary-500"
522557
@endif
523558
</div>
524559
</div>
525-
@if($displayDate)
526-
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1 block">{{ $displayDate }}</span>
560+
@if($displayDate || $fileSize)
561+
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1 block">
562+
{{ $displayDate }}{{ $displayDate && $fileSize ? ' · ' : '' }}{{ $fileSize }}
563+
</span>
527564
@endif
528565
</div>
529566

@@ -535,6 +572,7 @@ class="text-gray-400 hover:text-primary-500"
535572
src="{{ $fileUrl }}"
536573
alt="{{ $fileTitle }}"
537574
class="w-full h-auto object-contain bg-gray-100 dark:bg-gray-800"
575+
@if($lazyLoad) loading="lazy" @endif
538576
/>
539577
@break
540578

@@ -658,6 +696,7 @@ class="text-gray-400 hover:text-gray-500 focus:outline-none flex-shrink-0"
658696
src="{{ $fileUrl }}"
659697
alt="{{ $fileTitle }}"
660698
class="w-full h-auto object-contain bg-gray-100 dark:bg-gray-800"
699+
@if($lazyLoad) loading="lazy" @endif
661700
/>
662701
@break
663702

@@ -752,7 +791,7 @@ class="mt-3 inline-flex w-full justify-center rounded-md bg-white dark:bg-gray-8
752791
@endif
753792
@endforeach
754793
</div>
755-
@else
794+
@elseif(!$loadingSkeleton)
756795
<div class="fi-in-file-placeholder text-gray-500 dark:text-gray-400 text-sm">
757796
{{ $getPlaceholder() ?? __('No file') }}
758797
</div>

src/Infolists/Components/FileViewEntry.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ class FileViewEntry extends Entry
1818

1919
protected bool|Closure $contained = true;
2020

21+
protected bool|Closure $lazyLoad = false;
22+
23+
protected bool|Closure $showFileSize = false;
24+
25+
protected bool|Closure $showFileCount = false;
26+
27+
protected bool|Closure $loadingSkeleton = false;
28+
2129
protected bool|Closure $downloadable = false;
2230

2331
protected int|string|Closure|null $previewHeight = null;
@@ -60,6 +68,34 @@ public function contained(bool|Closure $condition = true): static
6068
return $this;
6169
}
6270

71+
public function lazyLoad(bool|Closure $condition = true): static
72+
{
73+
$this->lazyLoad = $condition;
74+
75+
return $this;
76+
}
77+
78+
public function showFileSize(bool|Closure $condition = true): static
79+
{
80+
$this->showFileSize = $condition;
81+
82+
return $this;
83+
}
84+
85+
public function showFileCount(bool|Closure $condition = true): static
86+
{
87+
$this->showFileCount = $condition;
88+
89+
return $this;
90+
}
91+
92+
public function loadingSkeleton(bool|Closure $condition = true): static
93+
{
94+
$this->loadingSkeleton = $condition;
95+
96+
return $this;
97+
}
98+
6399
public function downloadable(bool|Closure $condition = true): static
64100
{
65101
$this->downloadable = $condition;
@@ -129,6 +165,26 @@ public function isContained(): bool
129165
return (bool) $this->evaluate($this->contained);
130166
}
131167

168+
public function shouldLazyLoad(): bool
169+
{
170+
return (bool) $this->evaluate($this->lazyLoad);
171+
}
172+
173+
public function shouldShowFileSize(): bool
174+
{
175+
return (bool) $this->evaluate($this->showFileSize);
176+
}
177+
178+
public function shouldShowFileCount(): bool
179+
{
180+
return (bool) $this->evaluate($this->showFileCount);
181+
}
182+
183+
public function shouldShowLoadingSkeleton(): bool
184+
{
185+
return (bool) $this->evaluate($this->loadingSkeleton);
186+
}
187+
132188
public function isDownloadable(): bool
133189
{
134190
return (bool) $this->evaluate($this->downloadable);
@@ -237,4 +293,28 @@ public function getFileIcon(string $fileType): string
237293
default => 'heroicon-o-document',
238294
};
239295
}
296+
297+
public function getFileSize(string $path): ?string
298+
{
299+
try {
300+
$size = Storage::disk($this->getDiskName())->size($path);
301+
302+
return $this->formatFileSize($size);
303+
} catch (\Exception $e) {
304+
return null;
305+
}
306+
}
307+
308+
public function formatFileSize(int $bytes): string
309+
{
310+
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
311+
$unitIndex = 0;
312+
313+
while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
314+
$bytes /= 1024;
315+
$unitIndex++;
316+
}
317+
318+
return round($bytes, 2) . ' ' . $units[$unitIndex];
319+
}
240320
}

tests/Unit/FileViewEntryTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,64 @@
191191

192192
expect($entry->isContained())->toBeTrue();
193193
});
194+
195+
it('has default lazyLoad set to false', function () {
196+
$entry = FileViewEntry::make('files');
197+
198+
expect($entry->shouldLazyLoad())->toBeFalse();
199+
});
200+
201+
it('can enable lazyLoad', function () {
202+
$entry = FileViewEntry::make('files')
203+
->lazyLoad(true);
204+
205+
expect($entry->shouldLazyLoad())->toBeTrue();
206+
});
207+
208+
it('has default showFileSize set to false', function () {
209+
$entry = FileViewEntry::make('files');
210+
211+
expect($entry->shouldShowFileSize())->toBeFalse();
212+
});
213+
214+
it('can enable showFileSize', function () {
215+
$entry = FileViewEntry::make('files')
216+
->showFileSize(true);
217+
218+
expect($entry->shouldShowFileSize())->toBeTrue();
219+
});
220+
221+
it('has default showFileCount set to false', function () {
222+
$entry = FileViewEntry::make('files');
223+
224+
expect($entry->shouldShowFileCount())->toBeFalse();
225+
});
226+
227+
it('can enable showFileCount', function () {
228+
$entry = FileViewEntry::make('files')
229+
->showFileCount(true);
230+
231+
expect($entry->shouldShowFileCount())->toBeTrue();
232+
});
233+
234+
it('has default loadingSkeleton set to false', function () {
235+
$entry = FileViewEntry::make('files');
236+
237+
expect($entry->shouldShowLoadingSkeleton())->toBeFalse();
238+
});
239+
240+
it('can enable loadingSkeleton', function () {
241+
$entry = FileViewEntry::make('files')
242+
->loadingSkeleton(true);
243+
244+
expect($entry->shouldShowLoadingSkeleton())->toBeTrue();
245+
});
246+
247+
it('can format file size', function () {
248+
$entry = FileViewEntry::make('files');
249+
250+
expect($entry->formatFileSize(0))->toBe('0 B')
251+
->and($entry->formatFileSize(1024))->toBe('1 KB')
252+
->and($entry->formatFileSize(1024 * 1024))->toBe('1 MB')
253+
->and($entry->formatFileSize(1024 * 1024 * 1024))->toBe('1 GB');
254+
});

0 commit comments

Comments
 (0)