diff --git a/docs-assets/app/app/Livewire/Infolists/EntriesDemo.php b/docs-assets/app/app/Livewire/Infolists/EntriesDemo.php index 7fa45b35d8..02534fb702 100644 --- a/docs-assets/app/app/Livewire/Infolists/EntriesDemo.php +++ b/docs-assets/app/app/Livewire/Infolists/EntriesDemo.php @@ -328,6 +328,7 @@ class EntriesDemo extends Component implements HasInfolists ]) ->schema([ ImageEntry::make('author.avatar') + ->height(40) ->state('https://picsum.photos/id/177/1200/800') ->square(), ]), @@ -338,9 +339,87 @@ class EntriesDemo extends Component implements HasInfolists ]) ->schema([ ImageEntry::make('author.avatar') + ->height(40) ->state('https://picsum.photos/id/433/1200/800') ->circular(), ]), + Group::make() + ->id('imageStacked') + ->extraAttributes([ + 'class' => 'p-16 max-w-3xl', + ]) + ->schema([ + ImageEntry::make('colleagues') + ->height(40) + ->state([ + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + 'https://avatars.githubusercontent.com/u/881938?v=4', + ]) + ->circular() + ->stacked(), + ]), + Group::make() + ->id('imageLimited') + ->extraAttributes([ + 'class' => 'p-16 max-w-3xl', + ]) + ->schema([ + ImageEntry::make('colleagues') + ->height(40) + ->state([ + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + 'https://avatars.githubusercontent.com/u/881938?v=4', + ]) + ->circular() + ->stacked() + ->limit(3), + ]), + Group::make() + ->id('imageLimitedRemainingText') + ->extraAttributes([ + 'class' => 'p-16 max-w-3xl', + ]) + ->schema([ + ImageEntry::make('colleagues') + ->height(40) + ->state([ + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + 'https://avatars.githubusercontent.com/u/881938?v=4', + ]) + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(), + ]), + Group::make() + ->id('imageLimitedRemainingTextSeparately') + ->extraAttributes([ + 'class' => 'p-16 max-w-3xl', + ]) + ->schema([ + ImageEntry::make('colleagues') + ->height(40) + ->state([ + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + 'https://avatars.githubusercontent.com/u/881938?v=4', + ]) + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(isSeparate: true), + ]), Group::make() ->id('color') ->extraAttributes([ diff --git a/docs-assets/app/app/Livewire/TablesDemo.php b/docs-assets/app/app/Livewire/TablesDemo.php index ea0ce24e52..05eeb6da30 100644 --- a/docs-assets/app/app/Livewire/TablesDemo.php +++ b/docs-assets/app/app/Livewire/TablesDemo.php @@ -498,6 +498,55 @@ class TablesDemo extends Component implements HasForms, HasTable ]); } + public function imageColumnStacked(Table $table): Table + { + return $this->usersTable($table) + ->columns([ + TextColumn::make('name'), + ImageColumn::make('colleagues') + ->circular() + ->stacked(), + ]); + } + + public function imageColumnLimited(Table $table): Table + { + return $this->usersTable($table) + ->columns([ + TextColumn::make('name'), + ImageColumn::make('avatars') + ->circular() + ->stacked() + ->limit(3), + ]); + } + + public function imageColumnLimitedRemainingText(Table $table): Table + { + return $this->usersTable($table) + ->columns([ + TextColumn::make('name'), + ImageColumn::make('avatars') + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(), + ]); + } + + public function imageColumnLimitedRemainingTextSeparately(Table $table): Table + { + return $this->usersTable($table) + ->columns([ + TextColumn::make('name'), + ImageColumn::make('avatars') + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(isSeparate: true), + ]); + } + public function colorColumn(Table $table): Table { return $this->postsTable($table) @@ -1338,6 +1387,13 @@ class TablesDemo extends Component implements HasForms, HasTable 'avatar' => 'https://avatars.githubusercontent.com/u/41773797?v=4', 'phone' => '+1 (555) 555-5555', 'job' => 'Developer', + 'colleagues' => json_encode([ + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + 'https://avatars.githubusercontent.com/u/881938?v=4', + ]), ], [ 'name' => 'Ryan Chandler', @@ -1347,6 +1403,13 @@ class TablesDemo extends Component implements HasForms, HasTable 'avatar' => 'https://avatars.githubusercontent.com/u/41837763?v=4', 'phone' => '+1 (555) 555-5555', 'job' => 'Developer', + 'colleagues' => json_encode([ + 'https://avatars.githubusercontent.com/u/41773797?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + 'https://avatars.githubusercontent.com/u/881938?v=4', + ]), ], [ 'name' => 'Zep Fietje', @@ -1356,6 +1419,12 @@ class TablesDemo extends Component implements HasForms, HasTable 'avatar' => 'https://avatars.githubusercontent.com/u/44533235?v=4', 'phone' => '+1 (555) 555-5555', 'job' => 'Developer', + 'colleagues' => json_encode([ + 'https://avatars.githubusercontent.com/u/41773797?v=4', + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + ]), ], [ 'name' => 'Dennis Koch', @@ -1365,6 +1434,12 @@ class TablesDemo extends Component implements HasForms, HasTable 'avatar' => 'https://avatars.githubusercontent.com/u/22632550?v=4', 'phone' => '+1 (555) 555-5555', 'job' => 'Developer', + 'colleagues' => json_encode([ + 'https://avatars.githubusercontent.com/u/41773797?v=4', + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/3596800?v=4', + ]), ], [ 'name' => 'Adam Weston', @@ -1374,6 +1449,12 @@ class TablesDemo extends Component implements HasForms, HasTable 'avatar' => 'https://avatars.githubusercontent.com/u/3596800?v=4', 'phone' => '+1 (555) 555-5555', 'job' => 'Developer', + 'colleagues' => json_encode([ + 'https://avatars.githubusercontent.com/u/41773797?v=4', + 'https://avatars.githubusercontent.com/u/41837763?v=4', + 'https://avatars.githubusercontent.com/u/44533235?v=4', + 'https://avatars.githubusercontent.com/u/22632550?v=4', + ]), ], [ 'name' => 'Ryan Scherler', @@ -1383,6 +1464,10 @@ class TablesDemo extends Component implements HasForms, HasTable 'avatar' => 'https://avatars.githubusercontent.com/u/881938?v=4', 'phone' => '+1 (555) 555-5555', 'job' => 'Developer', + 'colleagues' => json_encode([ + 'https://avatars.githubusercontent.com/u/41773797?v=4', + 'https://avatars.githubusercontent.com/u/41837763?v=4', + ]), ], ]); User::factory()->count(45)->create(); diff --git a/docs-assets/app/app/Models/User.php b/docs-assets/app/app/Models/User.php index fbd1b9dc2e..e1d34a33bb 100644 --- a/docs-assets/app/app/Models/User.php +++ b/docs-assets/app/app/Models/User.php @@ -42,6 +42,7 @@ class User extends Authenticatable implements FilamentUser * @var array */ protected $casts = [ + 'colleagues' => 'array', 'email_verified_at' => 'datetime', ]; diff --git a/docs-assets/app/database/database.sqlite b/docs-assets/app/database/database.sqlite index a348e87ba0..6710be4d47 100644 Binary files a/docs-assets/app/database/database.sqlite and b/docs-assets/app/database/database.sqlite differ diff --git a/docs-assets/app/database/migrations/2014_10_12_000000_create_users_table.php b/docs-assets/app/database/migrations/2014_10_12_000000_create_users_table.php index 77e824aae4..17fa6b6d6c 100644 --- a/docs-assets/app/database/migrations/2014_10_12_000000_create_users_table.php +++ b/docs-assets/app/database/migrations/2014_10_12_000000_create_users_table.php @@ -20,6 +20,7 @@ return new class extends Migration $table->string('avatar')->nullable(); $table->string('phone')->nullable(); $table->string('job')->nullable(); + $table->json('colleagues')->nullable(); $table->rememberToken(); $table->timestamps(); }); diff --git a/docs-assets/screenshots/schema.js b/docs-assets/screenshots/schema.js index c9b4bfc5d5..6ec1df66ab 100644 --- a/docs-assets/screenshots/schema.js +++ b/docs-assets/screenshots/schema.js @@ -1336,6 +1336,42 @@ export default { deviceScaleFactor: 3, }, }, + 'infolists/entries/image/stacked': { + url: 'infolists/entries', + selector: '#imageStacked', + viewport: { + width: 1920, + height: 640, + deviceScaleFactor: 3, + }, + }, + 'infolists/entries/image/limited': { + url: 'infolists/entries', + selector: '#imageLimited', + viewport: { + width: 1920, + height: 640, + deviceScaleFactor: 3, + }, + }, + 'infolists/entries/image/limited-remaining-text': { + url: 'infolists/entries', + selector: '#imageLimitedRemainingText', + viewport: { + width: 1920, + height: 640, + deviceScaleFactor: 3, + }, + }, + 'infolists/entries/image/limited-remaining-text-separately': { + url: 'infolists/entries', + selector: '#imageLimitedRemainingTextSeparately', + viewport: { + width: 1920, + height: 640, + deviceScaleFactor: 3, + }, + }, 'infolists/entries/color/simple': { url: 'infolists/entries', selector: '#color', @@ -1881,6 +1917,42 @@ export default { deviceScaleFactor: 3, }, }, + 'tables/columns/image/stacked': { + url: 'tables?table=imageColumnStacked', + selector: 'body', + viewport: { + width: 1080, + height: 640, + deviceScaleFactor: 3, + }, + }, + 'tables/columns/image/limited': { + url: 'tables?table=imageColumnLimited', + selector: 'body', + viewport: { + width: 1080, + height: 640, + deviceScaleFactor: 3, + }, + }, + 'tables/columns/image/limited-remaining-text': { + url: 'tables?table=imageColumnLimitedRemainingText', + selector: 'body', + viewport: { + width: 1080, + height: 640, + deviceScaleFactor: 3, + }, + }, + 'tables/columns/image/limited-remaining-text-separately': { + url: 'tables?table=imageColumnLimitedRemainingTextSeparately', + selector: 'body', + viewport: { + width: 1080, + height: 640, + deviceScaleFactor: 3, + }, + }, 'tables/columns/color/simple': { url: 'tables?table=colorColumn', selector: 'body', diff --git a/packages/forms/src/Components/CheckboxList.php b/packages/forms/src/Components/CheckboxList.php index 23d0dfc277..558ef6584e 100644 --- a/packages/forms/src/Components/CheckboxList.php +++ b/packages/forms/src/Components/CheckboxList.php @@ -30,6 +30,9 @@ class CheckboxList extends Field implements Contracts\HasNestedRecursiveValidati protected string | Closure | null $relationship = null; + /** + * @var array | Arrayable | Closure + */ protected array | Arrayable | Closure $descriptions = []; protected bool | Closure $isBulkToggleable = false; @@ -277,6 +280,9 @@ class CheckboxList extends Field implements Contracts\HasNestedRecursiveValidati return (bool) $this->evaluate($this->isBulkToggleable); } + /** + * @param array | Arrayable | Closure $descriptions + */ public function descriptions(array | Arrayable | Closure $descriptions): static { $this->descriptions = $descriptions; @@ -284,16 +290,25 @@ class CheckboxList extends Field implements Contracts\HasNestedRecursiveValidati return $this; } + /** + * @param array-key $value + */ public function hasDescription($value): bool { return array_key_exists($value, $this->getDescriptions()); } + /** + * @param array-key $value + */ public function getDescription($value): ?string { return $this->getDescriptions()[$value] ?? null; } + /** + * @return array + */ public function getDescriptions(): array { $descriptions = $this->evaluate($this->descriptions); diff --git a/packages/infolists/docs/03-entries/04-image.md b/packages/infolists/docs/03-entries/04-image.md index 20c99b2c30..042164028a 100644 --- a/packages/infolists/docs/03-entries/04-image.md +++ b/packages/infolists/docs/03-entries/04-image.md @@ -39,32 +39,6 @@ ImageEntry::make('header_image') ->visibility('private') ``` -## Square image - -You may display the image using a 1:1 aspect ratio: - -```php -use Filament\Infolists\Components\ImageEntry; - -ImageEntry::make('author.avatar') - ->square() -``` - - - -## Circular image - -You may make the image fully rounded, which is useful for rendering avatars: - -```php -use Filament\Infolists\Components\ImageEntry; - -ImageEntry::make('author.avatar') - ->circular() -``` - - - ## Customizing the size You may customize the image size by passing a `width()` and `height()`, or both with `size()`: @@ -82,6 +56,34 @@ ImageEntry::make('author.avatar') ->size(40) ``` +## Square image + +You may display the image using a 1:1 aspect ratio: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('author.avatar') + ->height(40) + ->square() +``` + + + +## Circular image + +You may make the image fully rounded, which is useful for rendering avatars: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('author.avatar') + ->height(40) + ->circular() +``` + + + ## Adding a default image URL You can display a placeholder image if one doesn't exist yet, by passing a URL to the `defaultImageUrl()` method: @@ -93,6 +95,114 @@ ImageEntry::make('avatar') ->defaultImageUrl(url('/images/placeholder.png')) ``` +## Stacking images + +You may display multiple images as a stack of overlapping images by using `stacked()`: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('colleagues.avatar') + ->height(40) + ->circular() + ->stacked() +``` + + + +### Customizing the stacked ring width + +The default ring width is `3`, but you may customize it to be from `0` to `8`: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('colleagues.avatar') + ->height(40) + ->circular() + ->stacked() + ->ring(5) +``` + +### Customizing the stacked overlap + +The default overlap is `4`, but you may customize it to be from `0` to `8`: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('colleagues.avatar') + ->height(40) + ->circular() + ->stacked() + ->overlap(2) +``` + +## Setting a limit + +You may set a limit of the maximum number of images you want to display by passing `limit()`: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('colleagues.avatar') + ->height(40) + ->circular() + ->stacked() + ->limit(3) +``` + + + +### Showing the remaining images count + +When you set a limit you may also display the count of remaining images by passing `limitedRemainingText()`. + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('colleagues.avatar') + ->height(40) + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText() +``` + + + +#### Showing the limited remaining text separately + +By default, `limitedRemainingText()` will display the count of remaining images as a number stacked on the other images. If you prefer to show the count as a number after the images you may use the `isSeparate: true` parameter: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('colleagues.avatar') + ->height(40) + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(isSeparate: true) +``` + + + +#### Customizing the limited remaining text size + +By default, the size of the remaining text is `sm`. You can customize this to be `xs`, `md` or `lg` using the `size` parameter: + +```php +use Filament\Infolists\Components\ImageEntry; + +ImageEntry::make('colleagues.avatar') + ->height(40) + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(size: 'lg') +``` + ## Custom attributes You may customize the extra HTML attributes of the image using `extraImgAttributes()`: diff --git a/packages/infolists/resources/views/components/image-entry.blade.php b/packages/infolists/resources/views/components/image-entry.blade.php index b21e4e229e..5b4c9ee937 100644 --- a/packages/infolists/resources/views/components/image-entry.blade.php +++ b/packages/infolists/resources/views/components/image-entry.blade.php @@ -1,54 +1,105 @@ @php - $height = $getHeight(); + $limit = $getLimit(); + $state = collect($getState())->take($limit)->all(); $isCircular = $isCircular(); $isSquare = $isSquare(); - $path = $getImageUrl(); - $width = $getWidth() ?? (($isCircular || $isSquare) ? $height : null); + $height = $getHeight(); + $width = $getWidth() ?? ($isCircular || $isSquare ? $height : null); + $overlap = $isStacked() ? ($getOverlap() ?? 4) : null; + + $defaultImageUrl = $getDefaultImageUrl(); + if ((! count($state)) && filled($defaultImageUrl)) { + $state = [null]; + } + + $ringClasses = match ($getRing()) { + 0 => '', + 1 => 'ring-1', + 2 => 'ring-2', + 3 => 'ring-3', + 4 => 'ring-4', + 5 => 'ring-5', + 6 => 'ring-6', + 7 => 'ring-7', + 8 => 'ring-8', + default => 'ring', + }; + + $hasLimitedRemainingText = $hasLimitedRemainingText(); + $isLimitedRemainingTextSeparate = $isLimitedRemainingTextSeparate(); + $limitedRemainingTextSizeClasses = match ($getLimitedRemainingTextSize()) { + 'xs' => 'text-xs', + 'base', 'md' => 'text-base', + 'lg' => 'text-lg', + default => 'text-sm', + }; @endphp -
merge($getExtraAttributes(), escape: false) - ->class([ - 'fi-in-image flex', - match ($getAlignment()) { - 'center' => 'justify-center', - 'end' => 'justify-end', - 'left' => 'justify-left', - 'right' => 'justify-right', - 'start', null => 'justify-start', + @if (count($state)) +
+
'', + 1 => '-space-x-1', + 2 => '-space-x-2', + 3 => '-space-x-3', + 4 => '-space-x-4', + 5 => '-space-x-5', + 6 => '-space-x-6', + 7 => '-space-x-7', + 8 => '-space-x-8', + default => 'space-x-1', }, ]) - }} - > -
$isCircular || $isSquare, - 'rounded-full' => $isCircular, - ]) - @style([ - "height: {$height}" => $height, - "width: {$width}" => $width, - ]) - > - @if ($path) - class([ - 'object-cover object-center' => $isCircular || $isSquare, - ]) - ->style([ - "height: {$height}" => $height, - "width: {$width}" => $width, - ]) - }} - /> + > + @foreach ($state as $stateItem) + class([ + 'max-w-none ring-white object-cover object-center dark:ring-gray-900', + 'rounded-full' => $isCircular, + $ringClasses, + ]) }} + > + @endforeach + + @if ($hasLimitedRemainingText && ($loop->iteration < count($state)) && (! $isLimitedRemainingTextSeparate) && $isCircular) +
$isCircular, + $limitedRemainingTextSizeClasses, + $ringClasses, + ]) + > + + +{{ count($state) - $loop->iteration }} + +
+ @endif +
+ + @if ($hasLimitedRemainingText && ($loop->iteration < count($state)) && ($isLimitedRemainingTextSeparate || (! $isCircular))) +
+ +{{ count($state) - $loop->iteration }} +
@endif
-
+ @endif diff --git a/packages/infolists/src/Components/ImageEntry.php b/packages/infolists/src/Components/ImageEntry.php index 83dae0c53c..afaa063fbb 100644 --- a/packages/infolists/src/Components/ImageEntry.php +++ b/packages/infolists/src/Components/ImageEntry.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; use Illuminate\View\ComponentAttributeBag; +use League\Flysystem\UnableToCheckFileExistence; use Throwable; class ImageEntry extends Entry @@ -35,6 +36,20 @@ class ImageEntry extends Entry protected string | Closure | null $defaultImageUrl = null; + protected bool | Closure $isStacked = false; + + protected int | Closure | null $overlap = null; + + protected int | Closure | null $ring = null; + + protected int | Closure | null $limit = null; + + protected bool | Closure $hasLimitedRemainingText = false; + + protected bool | Closure $isLimitedRemainingTextSeparate = false; + + protected string | Closure | null $limitedRemainingTextSize = null; + public function disk(string | Closure | null $disk): static { $this->disk = $disk; @@ -117,14 +132,8 @@ class ImageEntry extends Entry return $this; } - public function getImageUrl(): ?string + public function getImageUrl(?string $state = null): ?string { - $state = $this->getState(); - - if (! $state) { - return $this->getDefaultImageUrl(); - } - if (filter_var($state, FILTER_VALIDATE_URL) !== false) { return $state; } @@ -132,7 +141,11 @@ class ImageEntry extends Entry /** @var FilesystemAdapter $storage */ $storage = $this->getDisk(); - if (! $storage->exists($state)) { + try { + if (! $storage->exists($state)) { + return null; + } + } catch (UnableToCheckFileExistence $exception) { return null; } @@ -207,4 +220,90 @@ class ImageEntry extends Entry { return new ComponentAttributeBag($this->getExtraImgAttributes()); } + + public function stacked(bool | Closure $condition = true): static + { + $this->isStacked = $condition; + + return $this; + } + + public function isStacked(): bool + { + return (bool) $this->evaluate($this->isStacked); + } + + public function overlap(int | Closure | null $overlap): static + { + $this->overlap = $overlap; + + return $this; + } + + public function getOverlap(): ?int + { + return $this->evaluate($this->overlap); + } + + public function ring(string | Closure | null $ring): static + { + $this->ring = $ring; + + return $this; + } + + public function getRing(): ?int + { + return $this->evaluate($this->ring); + } + + public function limit(int | Closure | null $limit = 3): static + { + $this->limit = $limit; + + return $this; + } + + public function getLimit(): ?int + { + return $this->evaluate($this->limit); + } + + public function limitedRemainingText(bool | Closure $condition = true, bool | Closure $isSeparate = false, string | Closure | null $size = null): static + { + $this->hasLimitedRemainingText = $condition; + $this->limitedRemainingTextSeparate($isSeparate); + $this->limitedRemainingTextSize($size); + + return $this; + } + + public function limitedRemainingTextSeparate(bool | Closure $condition = true): static + { + $this->isLimitedRemainingTextSeparate = $condition; + + return $this; + } + + public function hasLimitedRemainingText(): bool + { + return (bool) $this->evaluate($this->hasLimitedRemainingText); + } + + public function isLimitedRemainingTextSeparate(): bool + { + return (bool) $this->evaluate($this->isLimitedRemainingTextSeparate); + } + + public function limitedRemainingTextSize(string | Closure | null $size): static + { + $this->limitedRemainingTextSize = $size; + + return $this; + } + + public function getLimitedRemainingTextSize(): ?string + { + return $this->evaluate($this->limitedRemainingTextSize); + } } diff --git a/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php b/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php index abaca26fa6..fc71e54046 100644 --- a/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php +++ b/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php @@ -3,6 +3,7 @@ namespace Filament\Infolists\Components; use Illuminate\Support\Collection; +use Spatie\MediaLibrary\MediaCollections\Models\Media; use Throwable; class SpatieMediaLibraryImageEntry extends ImageEntry @@ -35,14 +36,8 @@ class SpatieMediaLibraryImageEntry extends ImageEntry return $this->conversion ?? ''; } - public function getImageUrl(): ?string + public function getImageUrl(?string $state = null): ?string { - $state = $this->getState(); - - if ($state && (! $state instanceof Collection)) { - return $state; - } - $record = $this->getRecord(); if (! $record) { @@ -55,11 +50,17 @@ class SpatieMediaLibraryImageEntry extends ImageEntry $record = $record->getRelationValue($relationshipName); } - if ($this->getVisibility() === 'private' && method_exists($record, 'getFirstTemporaryUrl')) { + /** @var ?Media $media */ + $media = $record->media->first(fn (Media $media): bool => $media->uuid === $state); + + if (! $media) { + return null; + } + + if ($this->getVisibility() === 'private') { try { - return $record->getFirstTemporaryUrl( + return $media->getTemporaryUrl( now()->addMinutes(5), - $this->getCollection(), $this->getConversion(), ); } catch (Throwable $exception) { @@ -67,12 +68,19 @@ class SpatieMediaLibraryImageEntry extends ImageEntry } } - if (! method_exists($record, 'getFirstMediaUrl')) { - return $state; - } + return $media->getUrl($this->getConversion()); + } - $firstMediaUrl = $record->getFirstMediaUrl($this->getCollection(), $this->getConversion()); + /** + * @return array + */ + public function getState(): array + { + $collection = $this->getCollection(); - return filled($firstMediaUrl) ? $firstMediaUrl : $this->getDefaultImageUrl(); + return $this->getRecord()->getRelationValue('media') + ->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection)) + ->map(fn (Media $media): string => $media->uuid) + ->all(); } } diff --git a/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php b/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php index 01e2dbf60e..c3fcd7b702 100644 --- a/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php +++ b/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php @@ -48,8 +48,8 @@ class SpatieMediaLibraryImageColumn extends ImageColumn $record = $record->getRelationValue($this->getRelationshipName()); } - /** @var Media $media */ - $media = $record->media->first(fn (Media $media) => $media->uuid === $state); + /** @var ?Media $media */ + $media = $record->media->first(fn (Media $media): bool => $media->uuid === $state); if (! $media) { return null; @@ -69,12 +69,15 @@ class SpatieMediaLibraryImageColumn extends ImageColumn return $media->getUrl($this->getConversion()); } + /** + * @return array + */ public function getState(): array { $collection = $this->getCollection(); - return $this->getRecord()->media - ->filter(fn (Media $media): bool => blank($collection) || ($media->collection_name === $collection)) + return $this->getRecord()->getRelationValue('media') + ->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection)) ->map(fn (Media $media): string => $media->uuid) ->all(); } @@ -98,9 +101,4 @@ class SpatieMediaLibraryImageColumn extends ImageColumn return $query->with(['media']); } - - public function getRecord(): Model&HasMedia - { - return parent::getRecord(); - } } diff --git a/packages/tables/docs/03-columns/04-image.md b/packages/tables/docs/03-columns/04-image.md index 6f7bafbc53..60da3e407c 100644 --- a/packages/tables/docs/03-columns/04-image.md +++ b/packages/tables/docs/03-columns/04-image.md @@ -39,6 +39,23 @@ ImageColumn::make('header_image') ->visibility('private') ``` +## Customizing the size + +You may customize the image size by passing a `width()` and `height()`, or both with `size()`: + +```php +use Filament\Tables\Columns\ImageColumn; + +ImageColumn::make('header_image') + ->width(200) + +ImageColumn::make('header_image') + ->height(50) + +ImageColumn::make('author.avatar') + ->size(40) +``` + ## Square image You may display the image using a 1:1 aspect ratio: @@ -65,23 +82,6 @@ ImageColumn::make('avatar') -## Customizing the size - -You may customize the image size by passing a `width()` and `height()`, or both with `size()`: - -```php -use Filament\Tables\Columns\ImageColumn; - -ImageColumn::make('header_image') - ->width(200) - -ImageColumn::make('header_image') - ->height(50) - -ImageColumn::make('author.avatar') - ->size(40) -``` - ## Adding a default image URL You can display a placeholder image if one doesn't exist yet, by passing a URL to the `defaultImageUrl()` method: @@ -93,6 +93,103 @@ ImageColumn::make('avatar') ->defaultImageUrl(url('/images/placeholder.png')) ``` +## Stacking images + +You may display multiple images as a stack of overlapping images by using `stacked()`: + +```php +use Filament\Tables\Columns\ImageColumn; + +ImageColumn::make('colleagues.avatar') + ->circular() + ->stacked() +``` + + + +### Customizing the stacked ring width + +The default ring width is `3`, but you may customize it to be from `0` to `8`: + +```php +ImageColumn::make('colleagues.avatar') + ->circular() + ->stacked() + ->ring(5) +``` + +### Customizing the stacked overlap + +The default overlap is `4`, but you may customize it to be from `0` to `8`: + +```php +ImageColumn::make('colleagues.avatar') + ->circular() + ->stacked() + ->overlap(2) +``` + +## Setting a limit + +You may set a limit of the maximum number of images you want to display by passing `limit()`: + +```php +use Filament\Tables\Columns\ImageColumn; + +ImageColumn::make('colleagues.avatar') + ->circular() + ->stacked() + ->limit(3) +``` + + + +### Showing the remaining images count + +When you set a limit you may also display the count of remaining images by passing `limitedRemainingText()`. + +```php +use Filament\Tables\Columns\ImageColumn; + +ImageColumn::make('colleagues.avatar') + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText() +``` + + + +#### Showing the limited remaining text separately + +By default, `limitedRemainingText()` will display the count of remaining images as a number stacked on the other images. If you prefer to show the count as a number after the images you may use the `isSeparate: true` parameter: + +```php +use Filament\Tables\Columns\ImageColumn; + +ImageColumn::make('colleagues.avatar') + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(isSeparate: true) +``` + + + +#### Customizing the limited remaining text size + +By default, the size of the remaining text is `sm`. You can customize this to be `xs`, `md` or `lg` using the `size` parameter: + +```php +use Filament\Tables\Columns\ImageColumn; + +ImageColumn::make('colleagues.avatar') + ->circular() + ->stacked() + ->limit(3) + ->limitedRemainingText(size: 'lg') +``` + ## Custom attributes You may customize the extra HTML attributes of the image using `extraImgAttributes()`: @@ -114,84 +211,3 @@ ImageColumn::make('logo') 'alt' => "{$record->name} logo", ]), ``` - -## Multiple images - -You may display multiple images from an array: - -```php -ImageColumn::make('images') - ->circular() -``` - -Be sure to add an `array` [cast](https://laravel.com/docs/eloquent-mutators#array-and-json-casting) to the model property: - -```php -use Illuminate\Database\Eloquent\Model; - -class Product extends Model -{ - protected $casts = [ - 'images' => 'array', - ]; - - // ... -} -``` - -## Stacking images - -You may display multiple images as a stack of overlapping images by using `stacked()`: - -```php -ImageColumn::make('images') - ->circular() - ->stacked() -``` - -### Setting a limit - -You may set a limit to the number of images you want to display by passing `limit()`: - -```php -ImageColumn::make('images') - ->circular() - ->stacked() - ->limit(3) -``` - -### Showing the remaining images count - -When you set a limit you may also display the count of remaining images by passing `showRemaining()`. - -```php -ImageColumn::make('images') - ->circular() - ->stacked() - ->limit(3) - ->showRemaining() -``` - -By default, `showRemaining()` will display the count of remaining images as a number stacked on the other images. If you prefer to show the count as a number after the images you may use `showRemainingAfterStack()`. You may also set the text size by using `remainingTextSize('xs')`; - -### Customizing the ring width - -The default ring width is `ring-3` but you may customize the ring width to be either `0`, `1`, `2`, or `4` which correspond to tailwinds `ring-widths`: `ring-0`, `ring-1`, `ring-2`, and `ring-4` respectively. - -```php -ImageColumn::make('users.avatar') - ->circular() - ->stacked() - ->ring(3) -``` - -### Customizing the overlap - -The default overlap is `-space-x-1` but you may customize the overlap to be either `0`, `1`, `2`, `3`, or `4` which correspond to tailwinds `space-x` options: `space-x-0`, `-space-x-1`, `-space-x-2`, `-space-x-3`, and `-space-x-4` respectively. - -```php -ImageColumn::make('users.avatar') - ->circular() - ->stacked() - ->overlap(3) -``` \ No newline at end of file diff --git a/packages/tables/resources/views/columns/image-column.blade.php b/packages/tables/resources/views/columns/image-column.blade.php index 0324665117..2208bf3f2a 100644 --- a/packages/tables/resources/views/columns/image-column.blade.php +++ b/packages/tables/resources/views/columns/image-column.blade.php @@ -39,7 +39,7 @@ $isLimitedRemainingTextSeparate = $isLimitedRemainingTextSeparate(); $limitedRemainingTextSizeClasses = match ($getLimitedRemainingTextSize()) { 'xs' => 'text-xs', - 'md' => 'text-md', + 'base', 'md' => 'text-base', 'lg' => 'text-lg', default => 'text-sm', }; @@ -99,7 +99,7 @@ @endif
- @if ($hasLimitedRemainingText && ($loop->iteration < count($state)) && ($isLimitedRemainingTextSeparate || $isCircular)) + @if ($hasLimitedRemainingText && ($loop->iteration < count($state)) && ($isLimitedRemainingTextSeparate || (! $isCircular)))