From 5d509911b2e7f7cf0e5fb930e1b6aa4b88e787bb Mon Sep 17 00:00:00 2001 From: Kenneth Sese Date: Wed, 24 Sep 2025 10:28:12 -0600 Subject: [PATCH] Add table header cell render hook (#17895) * renderhooks * indenting * add hasRenderHook method * remove table cell renderhook * remove table cell renderhook view * implement hasRenderHook check * rename & document hook --------- Co-authored-by: Dan Harrin --- docs/09-advanced/01-render-hooks.md | 1 + packages/support/src/Facades/FilamentView.php | 1 + packages/support/src/View/ViewManager.php | 24 ++++ .../tables/resources/views/index.blade.php | 122 ++++++++++-------- packages/tables/src/View/TablesRenderHook.php | 2 + 5 files changed, 94 insertions(+), 56 deletions(-) diff --git a/docs/09-advanced/01-render-hooks.md b/docs/09-advanced/01-render-hooks.md index fd19839d3d..ca641d6192 100644 --- a/docs/09-advanced/01-render-hooks.md +++ b/docs/09-advanced/01-render-hooks.md @@ -133,6 +133,7 @@ use Filament\Tables\View\TablesRenderHook; ``` - `TablesRenderHook::FILTER_INDICATORS` - Replace the existing filter indicators, receives `filterIndicators` data as `array` +- `TablesRenderHook::HEADER_CELL` - Replace the existing header cells, receives the `Filament\Tables\Columns\Column` object as `column` and `isReordering` in the data. - `TablesRenderHook::SELECTION_INDICATOR_ACTIONS_AFTER` - After the "select all" and "deselect all" action buttons in the selection indicator bar - `TablesRenderHook::SELECTION_INDICATOR_ACTIONS_BEFORE` - Before the "select all" and "deselect all" action buttons in the selection indicator bar - `TablesRenderHook::HEADER_AFTER` - After the header container diff --git a/packages/support/src/Facades/FilamentView.php b/packages/support/src/Facades/FilamentView.php index 23984ab03e..78c5dcbdbd 100644 --- a/packages/support/src/Facades/FilamentView.php +++ b/packages/support/src/Facades/FilamentView.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Facade; /** * @method static bool hasSpaMode(?string $url = null) * @method static bool hasSpaPrefetching() + * @method static bool hasRenderHook(string $name, string | array | null $scopes = null) * @method static Htmlable renderHook(string $name, string | array | null $scopes = null, array $data = []) * * @see ViewManager diff --git a/packages/support/src/View/ViewManager.php b/packages/support/src/View/ViewManager.php index c4a32d045b..06c8621da7 100644 --- a/packages/support/src/View/ViewManager.php +++ b/packages/support/src/View/ViewManager.php @@ -39,6 +39,30 @@ class ViewManager } } + /** + * @param string | array | null $scopes + */ + public function hasRenderHook(string $name, string | array | null $scopes = null): bool + { + if (! isset($this->renderHooks[$name])) { + return false; + } + + if (isset($this->renderHooks[$name][null]) && count($this->renderHooks[$name][null])) { + return true; + } + + $scopes = Arr::wrap($scopes); + + foreach ($scopes as $scopeName) { + if (isset($this->renderHooks[$name][$scopeName]) && count($this->renderHooks[$name][$scopeName])) { + return true; + } + } + + return false; + } + /** * @param string | array | null $scopes * @param array $data diff --git a/packages/tables/resources/views/index.blade.php b/packages/tables/resources/views/index.blade.php index f7ffb49c6b..a0af9d037e 100644 --- a/packages/tables/resources/views/index.blade.php +++ b/packages/tables/resources/views/index.blade.php @@ -1347,63 +1347,74 @@ @endif @endif + @php + $hasHeaderCellRenderHook = FilamentView::hasRenderHook(TablesRenderHook::HEADER_CELL, scopes: static::class); + @endphp + @foreach ($columns as $column) - @php - $columnName = $column->getName(); - $columnLabel = $column->getLabel(); - $columnAlignment = $column->getAlignment(); - $columnWidth = $column->getWidth(); - $isColumnActivelySorted = $getSortColumn() === $column->getName(); - $isColumnSortable = $column->isSortable() && (! $isReordering); - @endphp + @if ($hasHeaderCellRenderHook && filled($headerCellView = FilamentView::renderHook(TablesRenderHook::HEADER_CELL, scopes: static::class, data: [ + 'column' => $column, + 'isReordering' => $isReordering, + ]))) + {{ $headerCellView }} + @else + @php + $columnName = $column->getName(); + $columnLabel = $column->getLabel(); + $columnAlignment = $column->getAlignment(); + $columnWidth = $column->getWidth(); + $isColumnActivelySorted = $getSortColumn() === $column->getName(); + $isColumnSortable = $column->isSortable() && (! $isReordering); + @endphp - getExtraHeaderAttributeBag() - ->class([ - 'fi-ta-header-cell', - 'fi-ta-header-cell-' . str($columnName)->camel()->kebab(), - 'fi-growable' => blank($columnWidth) && $column->canGrow(default: false), - 'fi-grouped' => $column->getGroup(), - 'fi-wrapped' => $column->canHeaderWrap(), - 'fi-ta-header-cell-sorted' => $isColumnActivelySorted, - ((($columnAlignment = $column->getAlignment()) instanceof \Filament\Support\Enums\Alignment) ? "fi-align-{$columnAlignment->value}" : (is_string($columnAlignment) ? $columnAlignment : '')), - (filled($columnHiddenFrom = $column->getHiddenFrom()) ? "{$columnHiddenFrom}:fi-hidden" : ''), - (filled($columnVisibleFrom = $column->getVisibleFrom()) ? "{$columnVisibleFrom}:fi-visible" : ''), - ]) - ->style([ - ('width: ' . $columnWidth) => filled($columnWidth), - ]) - }} - > - @if ($isColumnSortable) - + getExtraHeaderAttributeBag() + ->class([ + 'fi-ta-header-cell', + 'fi-ta-header-cell-' . str($columnName)->camel()->kebab(), + 'fi-growable' => blank($columnWidth) && $column->canGrow(default: false), + 'fi-grouped' => $column->getGroup(), + 'fi-wrapped' => $column->canHeaderWrap(), + 'fi-ta-header-cell-sorted' => $isColumnActivelySorted, + ((($columnAlignment = $column->getAlignment()) instanceof \Filament\Support\Enums\Alignment) ? "fi-align-{$columnAlignment->value}" : (is_string($columnAlignment) ? $columnAlignment : '')), + (filled($columnHiddenFrom = $column->getHiddenFrom()) ? "{$columnHiddenFrom}:fi-hidden" : ''), + (filled($columnVisibleFrom = $column->getVisibleFrom()) ? "{$columnVisibleFrom}:fi-visible" : ''), + ]) + ->style([ + ('width: ' . $columnWidth) => filled($columnWidth), + ]) + }} + > + @if ($isColumnSortable) + + {{ $columnLabel }} + + {{ + \Filament\Support\generate_icon_html(($isColumnActivelySorted && $sortDirection === 'asc') ? \Filament\Support\Icons\Heroicon::ChevronUp : \Filament\Support\Icons\Heroicon::ChevronDown, alias: match (true) { + $isColumnActivelySorted && ($sortDirection === 'asc') => \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_ASC_BUTTON, + $isColumnActivelySorted && ($sortDirection === 'desc') => \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_DESC_BUTTON, + default => \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_BUTTON, + }) + }} + + @else {{ $columnLabel }} - - {{ - \Filament\Support\generate_icon_html(($isColumnActivelySorted && $sortDirection === 'asc') ? \Filament\Support\Icons\Heroicon::ChevronUp : \Filament\Support\Icons\Heroicon::ChevronDown, alias: match (true) { - $isColumnActivelySorted && ($sortDirection === 'asc') => \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_ASC_BUTTON, - $isColumnActivelySorted && ($sortDirection === 'desc') => \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_DESC_BUTTON, - default => \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_BUTTON, - }) - }} - - @else - {{ $columnLabel }} - @endif - + @endif + + @endif @endforeach @if ((! $isReordering) && count($records)) @@ -1913,8 +1924,7 @@ @if ($columnWrapperTag === 'a') {{ \Filament\Support\generate_href_html($columnUrl ?: $recordUrl, $columnUrl ? $column->shouldOpenUrlInNewTab() : $openRecordUrlInNewTab, hasNestedClickEventHandler: true) }} @elseif ($columnWrapperTag === 'button') - type - ="button" + type="button" wire:click.prevent.stop="{{ $columnWireClickAction }}" wire:loading.attr="disabled" wire:target="{{ $columnWireClickAction }}" diff --git a/packages/tables/src/View/TablesRenderHook.php b/packages/tables/src/View/TablesRenderHook.php index 281e6f5cb8..e86c9d1001 100644 --- a/packages/tables/src/View/TablesRenderHook.php +++ b/packages/tables/src/View/TablesRenderHook.php @@ -38,6 +38,8 @@ class TablesRenderHook const FILTER_INDICATORS = 'tables::filter.indicators'; + const HEADER_CELL = 'tables::header.cell'; + /** * @deprecated Use `TOOLBAR_COLUMN_MANAGER_TRIGGER_AFTER` instead. */