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 <git@danharrin.com>
This commit is contained in:
Kenneth Sese
2025-09-24 10:28:12 -06:00
committed by GitHub
parent d91c365104
commit 5d509911b2
5 changed files with 94 additions and 56 deletions
+1
View File
@@ -133,6 +133,7 @@ use Filament\Tables\View\TablesRenderHook;
```
- `TablesRenderHook::FILTER_INDICATORS` - Replace the existing filter indicators, receives `filterIndicators` data as `array<Filament\Tables\Filters\Indicator>`
- `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
@@ -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<string> | null $scopes = null)
* @method static Htmlable renderHook(string $name, string | array<string> | null $scopes = null, array<string, mixed> $data = [])
*
* @see ViewManager
+24
View File
@@ -39,6 +39,30 @@ class ViewManager
}
}
/**
* @param string | array<string> | 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<string> | null $scopes
* @param array<string, mixed> $data
+66 -56
View File
@@ -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
<th
@if ($isColumnActivelySorted)
aria-sort="{{ $sortDirection === 'asc' ? 'ascending' : 'descending' }}"
@endif
{{
$column->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)
<span
aria-label="{{ trim(strip_tags($columnLabel)) }}"
role="button"
tabindex="0"
wire:click="sortTable('{{ $columnName }}')"
x-on:keydown.enter.prevent.stop="$wire.sortTable('{{ $columnName }}')"
x-on:keydown.space.prevent.stop="$wire.sortTable('{{ $columnName }}')"
wire:loading.attr="disabled"
class="fi-ta-header-cell-sort-btn"
>
<th
@if ($isColumnActivelySorted)
aria-sort="{{ $sortDirection === 'asc' ? 'ascending' : 'descending' }}"
@endif
{{
$column->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)
<span
aria-label="{{ trim(strip_tags($columnLabel)) }}"
role="button"
tabindex="0"
wire:click="sortTable('{{ $columnName }}')"
x-on:keydown.enter.prevent.stop="$wire.sortTable('{{ $columnName }}')"
x-on:keydown.space.prevent.stop="$wire.sortTable('{{ $columnName }}')"
wire:loading.attr="disabled"
class="fi-ta-header-cell-sort-btn"
>
{{ $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,
})
}}
</span>
@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,
})
}}
</span>
@else
{{ $columnLabel }}
@endif
</th>
@endif
</th>
@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 }}"
@@ -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.
*/