Common provides reusable Filament fields, table filters and columns, range matching, and Laravel model and page utilities.
- ItemPicker form fields with inline or custom dropdown layouts, grouped trees, server search, scroll loading, multiple selection, badges and images.
- Inclusive, strict and open-ended range matching.
- Range, active and combined table filters.
- Standard active, creator, created, updated and default table columns.
- Record creators, form slugs, relationship badges and page refresh actions.
- PHP 8.4 or later
- Filament 4 or 5
composer require phpinnacle/common
php artisan filament:assetsPublish Filament assets again after package updates. No panel plugin or application frontend build is required.
use PHPinnacle\Common\Tables\CreatedColumn;
CreatedColumn::make();use PHPinnacle\Common\Enums\ItemPickerMode;
use PHPinnacle\Common\Forms\Item;
use PHPinnacle\Common\Forms\ItemBadge;
use PHPinnacle\Common\Forms\ItemGroup;
use PHPinnacle\Common\Forms\ItemPicker;
ItemPicker::make('assignees')
->mode(ItemPickerMode::Select)
->multiple()
->compact()
->dense()
->circular()
->branches(false)
->items([
ItemGroup::make('team')->children([
Item::make('anna')
->label('Anna')
->description('Administrator')
->image('/avatars/anna.jpg')
->badges([ItemBadge::make('Available')->color('success')]),
]),
]);compact() puts item and group descriptions on the label line. dense() independently reduces spacing and visual sizes. circular() rounds images and icon backgrounds. Images take precedence over icons. These options, multiple() and branches() accept boolean conditions or closures. Multiple dropdowns show complete selected chips and summarize overflow as +N. branches(false) allows leaf choices while keeping parent branches expandable. Disabled choices cannot be selected.
Item and group names get title-case labels unless an explicit label is supplied. Single selection stores a string or null; multiple selection stores a list of strings. The field validates availability, disabled items and branch selection. Both inline and dropdown modes support keyboard navigation. Groups are labels rather than selectable values.
Enable server search with searchable(). Its boolean or closure controls whether the search input is available. The items() callback receives a nullable $search and the current $state, alongside the usual Filament utility injections. Initial loading and clearing the search pass null. The callback owns filtering, ordering and access restrictions; the field does not filter search results automatically.
Set limit(50) to enable loading additional portions when scrolling. The default is null, with one unbounded result. When a limit is configured, the callback also receives $offset (starting at 0) and $limit. Apply these to the source query; the field does not truncate callback results. Each successful portion advances the offset by the limit, and [] ends loading. Changing the search starts again at offset 0. Static arrays are paginated by their top-level items.
Use resolve() to retrieve selected items outside the current search portion:
ItemPicker::make('assignees')
->mode(ItemPickerMode::Select)
->multiple()
->searchable()
->limit(50)
->items(fn (?string $search, int $offset, int $limit) => $directory->searchItems($search, $offset, $limit))
->resolve(fn (?array $state, array $items) => $directory->selectedItems($state, $items));The resolver runs after items() and receives the current $state and the returned $items. It returns list<Item> in both single and multiple modes, using already loaded items or looking up missing selections. Its results display the selection and determine valid values without being appended to the search list or affecting offsets. Inline selections already loaded in the list are highlighted there. Missing selections are added as selectable cards before the results. These cards remain in the current list after deselection or loading further portions, so choosing an item does not move other cards; duplicate results are hidden. When a branch loads, its full tree row replaces the resolved card so its children remain accessible. Changing the search rebuilds the list using the current selection. This applies to each selected value in multiple mode. Return [] for an empty selection and omit unavailable values. Disabled items and forbidden branches remain invalid. Apply the same access restrictions to search and resolution. Validation uses the initial portion with $search = null and the current state. Static arrays resolve selections automatically; callbacks without a resolver use their current portion.
Callbacks returning trees must retain the parent context needed to display matches and identify branches. Search results expand their branches to reveal matching descendants.
ActiveFilter filters the is_active column. HasFilter filters whether an
Eloquent relationship has records. RangeFilter selects an inclusive numeric
interval:
use PHPinnacle\Common\Filters\ActiveFilter;
use PHPinnacle\Common\Filters\HasFilter;
use PHPinnacle\Common\Filters\RangeFilter;
ActiveFilter::make();
HasFilter::make('orders');
RangeFilter::make('quantity')->minValue(0)->maxValue(100)->step(5);CombinedFilter groups select filters. Each selection filters its named column
unless an applyUsing() callback supplies a custom query. depends() disables
a selection until its prerequisites have values, and clears dependent choices
when a prerequisite changes:
use PHPinnacle\Common\Filters\CombinedFilter;
use PHPinnacle\Common\Filters\CombinedFilter\Filter;
CombinedFilter::make()->filters(
Filter::make('status')->label('Status')->options([
'draft' => 'Draft',
'published' => 'Published',
]),
);CreatedColumn, UpdatedColumn, CreatorColumn, ActiveColumn and
DefaultColumn provide standard labels and display for their corresponding
record fields. Applications supply actions when a column should change a record.
use PHPinnacle\Common\Models\Range;
$range = Range::make(10, 20);
$range->match(10); // true: boundaries are included
$range->match(10, strict: true); // false: boundaries are excluded
Range::make(null, 20)->match(5); // true: no lower boundarymatch() also accepts arrays and Countable values, comparing their item count.
A null boundary leaves that side of the range open.
Use HasCreator on an Eloquent model with a nullable created_by column. New
records use the authenticated user's ID unless a creator was explicitly assigned.
The creator relationship uses the configured authentication user model.
FormSlug::make() can be passed to a name field's afterStateUpdated() callback.
It updates the slug field while its value still matches the previous name's
slug, preserving manual edits. By default it runs only during creation.
use Filament\Forms\Components\TextInput;
use PHPinnacle\Common\Concerns\FormSlug;
TextInput::make('name')->live(onBlur: true)->afterStateUpdated(FormSlug::make());
TextInput::make('slug');PageBadge shows the count of a configured relationship beside a resource page's
navigation item. CanBeRefreshed refreshes a Livewire page on refresh-page.
Use Refresh::page($livewire) to request that update, or Refresh::nav($livewire)
to refresh sidebar and topbar navigation.
Tests cover valid and invalid selections, unavailable choices, search and loading, keyboard navigation, safe rendering, range matching, record creators and page refreshes.
composer test
npm testSee CHANGELOG. Released under the MIT License.