This commit is contained in:
Dan Harrin
2023-07-18 14:31:18 +01:00
parent f57167c886
commit 507267e271
14 changed files with 736 additions and 201 deletions
@@ -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([
@@ -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();
+1
View File
@@ -42,6 +42,7 @@ class User extends Authenticatable implements FilamentUser
* @var array<string, string>
*/
protected $casts = [
'colleagues' => 'array',
'email_verified_at' => 'datetime',
];
Binary file not shown.
@@ -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();
});
+72
View File
@@ -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',
@@ -30,6 +30,9 @@ class CheckboxList extends Field implements Contracts\HasNestedRecursiveValidati
protected string | Closure | null $relationship = null;
/**
* @var array<string> | 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<string> | 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<string>
*/
public function getDescriptions(): array
{
$descriptions = $this->evaluate($this->descriptions);
+136 -26
View File
@@ -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()
```
<AutoScreenshot name="infolists/entries/image/square" alt="Square image entry" version="3.x" />
## 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()
```
<AutoScreenshot name="infolists/entries/image/circular" alt="Circular image entry" version="3.x" />
## 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()
```
<AutoScreenshot name="infolists/entries/image/square" alt="Square image entry" version="3.x" />
## 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()
```
<AutoScreenshot name="infolists/entries/image/circular" alt="Circular image entry" version="3.x" />
## 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()
```
<AutoScreenshot name="infolists/entries/image/stacked" alt="Stacked image entry" version="3.x" />
### 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)
```
<AutoScreenshot name="infolists/entries/image/limited" alt="Limited image entry" version="3.x" />
### 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()
```
<AutoScreenshot name="infolists/entries/image/limited-remaining-text" alt="Limited image entry with remaining text" version="3.x" />
#### 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)
```
<AutoScreenshot name="infolists/entries/image/limited-remaining-text-separately" alt="Limited image entry with remaining text separately" version="3.x" />
#### 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()`:
@@ -1,54 +1,105 @@
<x-dynamic-component :component="$getEntryWrapperView()" :entry="$entry">
@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
<div
{{
$attributes
->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))
<div class="flex items-center gap-2">
<div
@class([
'flex',
match ($overlap) {
0 => '',
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',
},
])
}}
>
<div
@class([
'inline-block',
'overflow-hidden' => $isCircular || $isSquare,
'rounded-full' => $isCircular,
])
@style([
"height: {$height}" => $height,
"width: {$width}" => $width,
])
>
@if ($path)
<img
src="{{ $path }}"
{{
$getExtraImgAttributeBag()
->class([
'object-cover object-center' => $isCircular || $isSquare,
])
->style([
"height: {$height}" => $height,
"width: {$width}" => $width,
])
}}
/>
>
@foreach ($state as $stateItem)
<img
src="{{ filled($stateItem) ? $getImageUrl($stateItem) : $defaultImageUrl }}"
style="
@if ($height) height: {{ $height }}; @endif
@if ($width) width: {{ $width }}; @endif
"
{{ $getExtraImgAttributeBag()->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)
<div
style="
@if ($height) height: {{ $height }}; @endif
@if ($width) width: {{ $width }}; @endif
"
@class([
'flex items-center justify-center bg-gray-100 text-gray-500 ring-white dark:bg-gray-800 dark:text-gray-300 dark:ring-gray-900',
'rounded-full' => $isCircular,
$limitedRemainingTextSizeClasses,
$ringClasses,
])
>
<span class="-ml-1">
+{{ count($state) - $loop->iteration }}
</span>
</div>
@endif
</div>
@if ($hasLimitedRemainingText && ($loop->iteration < count($state)) && ($isLimitedRemainingTextSeparate || (! $isCircular)))
<div
@class([
'text-gray-600 dark:text-gray-300',
$limitedRemainingTextSizeClasses,
])
>
+{{ count($state) - $loop->iteration }}
</div>
@endif
</div>
</div>
@endif
</x-dynamic-component>
@@ -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);
}
}
@@ -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<string>
*/
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();
}
}
@@ -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<string>
*/
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();
}
}
+114 -98
View File
@@ -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')
<AutoScreenshot name="tables/columns/image/circular" alt="Circular image column" version="3.x" />
## 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()
```
<AutoScreenshot name="tables/columns/image/stacked" alt="Stacked image column" version="3.x" />
### 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)
```
<AutoScreenshot name="tables/columns/image/limited" alt="Limited image column" version="3.x" />
### 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()
```
<AutoScreenshot name="tables/columns/image/limited-remaining-text" alt="Limited image column with remaining text" version="3.x" />
#### 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)
```
<AutoScreenshot name="tables/columns/image/limited-remaining-text-separately" alt="Limited image column with remaining text separately" version="3.x" />
#### 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)
```
@@ -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
</div>
@if ($hasLimitedRemainingText && ($loop->iteration < count($state)) && ($isLimitedRemainingTextSeparate || $isCircular))
@if ($hasLimitedRemainingText && ($loop->iteration < count($state)) && ($isLimitedRemainingTextSeparate || (! $isCircular)))
<div
@class([
'text-gray-600 dark:text-gray-300',