Implement image column

This commit is contained in:
Dan Harrin
2024-09-29 13:54:02 -04:00
parent 5277b6d830
commit 1de9502df5
15 changed files with 246 additions and 226 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

-18
View File
@@ -1518,15 +1518,6 @@ export default {
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',
@@ -2045,15 +2036,6 @@ export default {
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',
+6
View File
@@ -201,6 +201,12 @@ public function getStats(): array
### Low-impact changes
<Disclosure x-show="packages.includes('tables') || packages.includes('infolists')">
<span slot="summary">The `isSeparate` parameter of `ImageColumn::limitedRemainingText()` and `ImageEntry::limitedRemainingText()` has been removed</span>
Previously, users were able to display the number of limited images separately to an image stack using the `isSeparate` parameter. Now the parameter has been removed, and if a stack exists, the text will always be stacked on top and not separate. If the images are not stacked, the text will be separate.
</Disclosure>
<Disclosure x-show="packages.includes('forms')">
<span slot="summary">Overriding the `Field::make()`, `MorphToSelect::make()`, `Placeholder::make()`, or `Builder\Block::make()` methods</span>
@@ -171,23 +171,6 @@ ImageEntry::make('colleagues.avatar')
<AutoScreenshot name="infolists/entries/image/limited-remaining-text" alt="Limited image entry with remaining text" version="4.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="4.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:
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,121 @@
.fi-ta-image {
@apply flex w-full items-center gap-1.5;
& img {
@apply max-w-none object-cover object-center;
}
&.fi-circular {
& img {
@apply rounded-full;
}
}
&.fi-ta-image-ring {
& img,
& .fi-ta-image-limited-remaining-text {
@apply ring ring-white dark:ring-gray-900;
}
&.fi-ta-image-ring-1 {
& img,
& .fi-ta-image-limited-remaining-text {
@apply ring-1;
}
}
&.fi-ta-image-ring-2 {
& img,
& .fi-ta-image-limited-remaining-text {
@apply ring-2;
}
}
&.fi-ta-image-ring-4 {
& img,
& .fi-ta-image-limited-remaining-text {
@apply ring-4;
}
}
}
&.fi-ta-image-overlap-1 {
@apply gap-x-0 -space-x-1;
}
&.fi-ta-image-overlap-2 {
@apply gap-x-0 -space-x-2;
}
&.fi-ta-image-overlap-3 {
@apply gap-x-0 -space-x-3;
}
&.fi-ta-image-overlap-4 {
@apply gap-x-0 -space-x-4;
}
&.fi-ta-image-overlap-5 {
@apply gap-x-0 -space-x-5;
}
&.fi-ta-image-overlap-6 {
@apply gap-x-0 -space-x-6;
}
&.fi-ta-image-overlap-7 {
@apply gap-x-0 -space-x-7;
}
&.fi-ta-image-overlap-8 {
@apply gap-x-0 -space-x-8;
}
&.fi-wrapped {
@apply flex-wrap;
}
&:not(.fi-inline) {
@apply px-3 py-4;
}
&.fi-align-start,
&.fi-align-left {
@apply justify-start;
}
&.fi-align-center {
@apply justify-center;
}
&.fi-align-end,
&.fi-align-right {
@apply justify-end;
}
&.fi-align-justify,
&.fi-align-between {
@apply justify-between;
}
& .fi-ta-image-limited-remaining-text {
@apply flex items-center justify-center text-sm font-medium text-gray-500 dark:text-gray-400;
&:is(.fi-stacked .fi-ta-image-limited-remaining-text) {
@apply rounded-full bg-gray-100 dark:bg-gray-800;
}
&.fi-size-xs {
@apply text-xs;
}
&.fi-size-base,
&.fi-size-md {
@apply text-base;
}
&.fi-size-lg {
@apply text-lg;
}
}
}
+1
View File
@@ -15,6 +15,7 @@ import '../css/components/tables/actions.css'
import '../css/components/tables/cell.css'
import '../css/components/tables/columns/color.css'
import '../css/components/tables/columns/icon.css'
import '../css/components/tables/columns/image.css'
import '../css/components/tables/columns/text.css'
import '../css/components/tables/row.css'
@@ -175,22 +175,6 @@ ImageColumn::make('colleagues.avatar')
<AutoScreenshot name="tables/columns/image/limited-remaining-text" alt="Limited image column with remaining text" version="4.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="4.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:
@@ -1,143 +0,0 @@
@php
$state = $getState();
if ($state instanceof \Illuminate\Support\Collection) {
$state = $state->all();
}
$state = \Illuminate\Support\Arr::wrap($state);
$limit = $getLimit();
$limitedState = array_slice($state, 0, $limit);
$isCircular = $isCircular();
$isSquare = $isSquare();
$isStacked = $isStacked();
$overlap = $isStacked ? ($getOverlap() ?? 2) : null;
$ring = $isStacked ? ($getRing() ?? 2) : null;
$height = $getHeight() ?? ($isStacked ? '2rem' : '2.5rem');
$width = $getWidth() ?? (($isCircular || $isSquare) ? $height : null);
$stateCount = count($state);
$limitedStateCount = count($limitedState);
$defaultImageUrl = $getDefaultImageUrl();
if ((! $limitedStateCount) && filled($defaultImageUrl)) {
$limitedState = [null];
$limitedStateCount = 1;
}
$ringClasses = \Illuminate\Support\Arr::toCssClasses([
'ring-white dark:ring-gray-900',
match ($ring) {
0 => null,
1 => 'ring-1',
2 => 'ring-2',
3 => 'ring',
4 => 'ring-4',
default => $ring,
},
]);
$hasLimitedRemainingText = $hasLimitedRemainingText() && ($limitedStateCount < $stateCount);
$isLimitedRemainingTextSeparate = $isLimitedRemainingTextSeparate();
$limitedRemainingTextSizeClasses = match ($getLimitedRemainingTextSize()) {
'xs' => 'text-xs',
'sm', null => 'text-sm',
'base', 'md' => 'text-base',
'lg' => 'text-lg',
default => $size,
};
@endphp
<div
{{
$attributes
->merge($getExtraAttributes(), escape: false)
->class([
'fi-ta-image',
'px-3 py-4' => ! $isInline(),
])
}}
>
@if ($limitedStateCount)
<div class="flex items-center gap-x-2.5">
<div
@class([
'flex',
'flex-wrap' => $canWrap(),
match ($overlap) {
0 => null,
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 => 'gap-1.5',
},
])
>
@foreach ($limitedState as $stateItem)
<img
src="{{ filled($stateItem) ? $getImageUrl($stateItem) : $defaultImageUrl }}"
{{
$getExtraImgAttributeBag()
->class([
'max-w-none object-cover object-center',
'rounded-full' => $isCircular,
$ringClasses,
])
->style([
"height: {$height}" => $height,
"width: {$width}" => $width,
])
}}
/>
@endforeach
@if ($hasLimitedRemainingText && (! $isLimitedRemainingTextSeparate) && $isCircular)
<div
style="
@if ($height) height: {{ $height }}; @endif
@if ($width) width: {{ $width }}; @endif
"
@class([
'flex items-center justify-center bg-gray-100 font-medium text-gray-500 dark:bg-gray-800 dark:text-gray-400',
'rounded-full' => $isCircular,
$limitedRemainingTextSizeClasses,
$ringClasses,
])
@style([
"height: {$height}" => $height,
"width: {$width}" => $width,
])
>
<span class="-ms-0.5">
+{{ $stateCount - $limitedStateCount }}
</span>
</div>
@endif
</div>
@if ($hasLimitedRemainingText && ($isLimitedRemainingTextSeparate || (! $isCircular)))
<div
@class([
'font-medium text-gray-500 dark:text-gray-400',
$limitedRemainingTextSizeClasses,
])
>
+{{ $stateCount - $limitedStateCount }}
</div>
@endif
</div>
@elseif (($placeholder = $getPlaceholder()) !== null)
<x-filament-tables::columns.placeholder>
{{ $placeholder }}
</x-filament-tables::columns.placeholder>
@endif
</div>
@@ -279,6 +279,7 @@ class IconColumn extends Column implements HasEmbeddedView
]);
ob_start(); ?>
<div <?= $attributes->toHtml() ?>>
<?php foreach ($state as $stateItem) { ?>
<?php
@@ -304,6 +305,7 @@ class IconColumn extends Column implements HasEmbeddedView
->toHtml() ?>
<?php } ?>
</div>
<?php return ob_get_clean();
}
}
+106 -22
View File
@@ -3,22 +3,21 @@
namespace Filament\Tables\Columns;
use Closure;
use Filament\Support\Components\Contracts\HasEmbeddedView;
use Filament\Support\Enums\Alignment;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\ComponentAttributeBag;
use League\Flysystem\UnableToCheckFileExistence;
use Throwable;
class ImageColumn extends Column
class ImageColumn extends Column implements HasEmbeddedView
{
use Concerns\CanWrap;
/**
* @var view-string
*/
protected string $view = 'filament-tables::columns.image-column';
protected string | Closure | null $disk = null;
protected int | string | Closure | null $height = null;
@@ -48,8 +47,6 @@ class ImageColumn extends Column
protected bool | Closure $hasLimitedRemainingText = false;
protected bool | Closure $isLimitedRemainingTextSeparate = false;
protected string | Closure | null $limitedRemainingTextSize = null;
protected bool | Closure $shouldCheckFileExistence = true;
@@ -301,32 +298,19 @@ class ImageColumn extends Column
return $this->evaluate($this->limit);
}
public function limitedRemainingText(bool | Closure $condition = true, bool | Closure $isSeparate = false, string | Closure | null $size = null): static
public function limitedRemainingText(bool | Closure $condition = true, 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;
@@ -350,4 +334,104 @@ class ImageColumn extends Column
{
return (bool) $this->evaluate($this->shouldCheckFileExistence);
}
public function toEmbeddedHtml(): string
{
$state = $this->getState();
if ($state instanceof Collection) {
$state = $state->all();
}
$attributes = $this->getExtraAttributeBag()
->class([
'fi-ta-image',
'fi-inline' => $this->isInline(),
]);
if (empty($state)) {
$placeholder = $this->getPlaceholder();
ob_start(); ?>
<div <?= $attributes->toHtml() ?>>
<?php if (filled($placeholder !== null)) { ?>
<p class="fi-ta-placeholder">
<?= e($placeholder) ?>
</p>
<?php } ?>
</div>
<?php return ob_get_clean();
}
$state = Arr::wrap($state);
$stateCount = count($state);
$limit = $this->getLimit() ?? $stateCount;
$stateOverLimitCount = ($limit && ($stateCount > $limit))
? ($stateCount - $limit)
: 0;
if ($stateOverLimitCount) {
$state = array_slice($state, 0, $limit);
}
$alignment = $this->getAlignment();
$isCircular = $this->isCircular();
$isSquare = $this->isSquare();
$isStacked = $this->isStacked();
$hasLimitedRemainingText = $stateOverLimitCount && $this->hasLimitedRemainingText();
$limitedRemainingTextSize = $this->getLimitedRemainingTextSize();
$height = $this->getHeight() ?? ($isStacked ? '2rem' : '2.5rem');
$width = $this->getWidth() ?? (($isCircular || $isSquare) ? $height : null);
$defaultImageUrl = $this->getDefaultImageUrl();
$attributes = $attributes
->class([
'fi-circular' => $isCircular,
'fi-wrapped' => $this->canWrap(),
'fi-stacked' => $isStacked,
($isStacked && is_int($ring = $this->getRing())) ? "fi-ta-image-ring fi-ta-image-ring-{$ring}" : '',
($isStacked && ($overlap = ($this->getOverlap() ?? 2))) ? "fi-ta-image-overlap-{$overlap}" : '',
($alignment instanceof Alignment) ? "fi-align-{$alignment->value}" : (is_string($alignment) ? $alignment : ''),
]);
ob_start(); ?>
<div <?= $attributes->toHtml() ?>>
<?php foreach ($state as $stateItem) { ?>
<img
<?= $this->getExtraImgAttributeBag()
->merge([
'src' => filled($stateItem) ? $this->getImageUrl($stateItem) : $defaultImageUrl,
])
->style([
"height: {$height}" => $height,
"width: {$width}" => $width,
])
->toHtml() ?>
/>
<?php } ?>
<?php if ($hasLimitedRemainingText && $stateOverLimitCount) { ?>
<div <?= (new ComponentAttributeBag)
->class([
'fi-ta-image-limited-remaining-text',
"fi-size-{$limitedRemainingTextSize}" => $limitedRemainingTextSize,
])
->style([
"height: {$height}" => $height,
"width: {$width}" => $width,
])
->toHtml() ?>>
+<?= $stateOverLimitCount ?>
</div>
<?php } ?>
</div>
<?php return ob_get_clean();
}
}
+9 -9
View File
@@ -183,10 +183,10 @@ class TextColumn extends Column implements HasEmbeddedView
$stateCount = count($state);
$listLimit = $this->getListLimit() ?? $stateCount;
$stateOverLimitCount = 0;
$stateOverListLimitCount = 0;
if ($listLimit && ($stateCount > $listLimit)) {
$stateOverLimitCount = $stateCount - $listLimit;
$stateOverListLimitCount = $stateCount - $listLimit;
if (
(! $isListWithLineBreaks) ||
@@ -400,16 +400,16 @@ class TextColumn extends Column implements HasEmbeddedView
'fi-ta-text-has-line-breaks' => $isListWithLineBreaks,
]);
if ($hasDescriptions || $stateOverLimitCount) {
if ($hasDescriptions || $stateOverListLimitCount) {
$attributes = $attributes
->merge([
'x-data' => ($stateOverLimitCount && $isLimitedListExpandable)
'x-data' => ($stateOverListLimitCount && $isLimitedListExpandable)
? '{ isLimited: true }'
: null,
])
->class([
'fi-ta-text-has-descriptions' => $hasDescriptions,
'fi-ta-text-list-limited' => $stateOverLimitCount,
'fi-ta-text-list-limited' => $stateOverListLimitCount,
]);
ob_start(); ?>
@@ -483,7 +483,7 @@ class TextColumn extends Column implements HasEmbeddedView
</ul>
<?php } ?>
<?php if ($stateOverLimitCount) { ?>
<?php if ($stateOverListLimitCount) { ?>
<p class="fi-ta-text-list-limited-message">
<?php if ($isLimitedListExpandable) { ?>
<button
@@ -492,7 +492,7 @@ class TextColumn extends Column implements HasEmbeddedView
x-show="isLimited"
class="fi-link fi-size-xs"
>
<?= trans_choice('filament-tables::table.columns.text.actions.expand_list', $stateOverLimitCount) ?>
<?= trans_choice('filament-tables::table.columns.text.actions.expand_list', $stateOverListLimitCount) ?>
</button>
<button
@@ -502,10 +502,10 @@ class TextColumn extends Column implements HasEmbeddedView
x-show="! isLimited"
class="fi-link fi-size-xs"
>
<?= trans_choice('filament-tables::table.columns.text.actions.collapse_list', $stateOverLimitCount) ?>
<?= trans_choice('filament-tables::table.columns.text.actions.collapse_list', $stateOverListLimitCount) ?>
</button>
<?php } else { ?>
<?= trans_choice('filament-tables::table.columns.text.more_list_items', $stateOverLimitCount) ?>
<?= trans_choice('filament-tables::table.columns.text.more_list_items', $stateOverListLimitCount) ?>
<?php } ?>
</p>
<?php } ?>