252 lines
12 KiB
PHP
Executable File
252 lines
12 KiB
PHP
Executable File
## Filament
|
|
|
|
- Filament is a Laravel UI framework built on Livewire, Alpine.js, and Tailwind CSS. UIs are defined in PHP via fluent, chainable components. Follow existing conventions in this app.
|
|
- Use the ___SINGLE_BACKTICK___search-docs___SINGLE_BACKTICK___ tool for official documentation on Artisan commands, code examples, testing, relationships, and idiomatic practices. If ___SINGLE_BACKTICK___search-docs___SINGLE_BACKTICK___ is unavailable, refer to https://filamentphp.com/docs.
|
|
|
|
### Artisan
|
|
|
|
- Always use Filament-specific Artisan commands to create files. Find available commands with the ___SINGLE_BACKTICK___list-artisan-commands___SINGLE_BACKTICK___ tool, or run ___SINGLE_BACKTICK___php artisan --help___SINGLE_BACKTICK___.
|
|
- Inspect required options before running, and always pass ___SINGLE_BACKTICK___--no-interaction___SINGLE_BACKTICK___.
|
|
|
|
### Patterns
|
|
|
|
Always use static ___SINGLE_BACKTICK___make()___SINGLE_BACKTICK___ methods to initialize components. Most configuration methods accept a ___SINGLE_BACKTICK___Closure___SINGLE_BACKTICK___ for dynamic values.
|
|
|
|
Use ___SINGLE_BACKTICK___Get $get___SINGLE_BACKTICK___ to read other form field values for conditional logic:
|
|
|
|
|
|
<code-snippet name="Conditional form field visibility" lang="php">
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Schemas\Components\Utilities\Get;
|
|
|
|
Select::make('type')
|
|
->options(CompanyType::class)
|
|
->required()
|
|
->live(),
|
|
|
|
TextInput::make('company_name')
|
|
->required()
|
|
->visible(fn (Get $get): bool => $get('type') === 'business'),
|
|
|
|
</code-snippet>
|
|
|
|
|
|
Use ___SINGLE_BACKTICK___Set $set___SINGLE_BACKTICK___ inside ___SINGLE_BACKTICK___->afterStateUpdated()___SINGLE_BACKTICK___ on a ___SINGLE_BACKTICK___->live()___SINGLE_BACKTICK___ field to mutate another field reactively. Prefer ___SINGLE_BACKTICK___->live(onBlur: true)___SINGLE_BACKTICK___ on text inputs to avoid per-keystroke updates:
|
|
|
|
|
|
<code-snippet name="Reactive field update" lang="php">
|
|
use Filament\Schemas\Components\Utilities\Set;
|
|
use Illuminate\Support\Str;
|
|
|
|
TextInput::make('title')
|
|
->required()
|
|
->live(onBlur: true)
|
|
->afterStateUpdated(fn (Set $set, ?string $state) => $set(
|
|
'slug',
|
|
Str::slug($state ?? ''),
|
|
)),
|
|
|
|
TextInput::make('slug')
|
|
->required(),
|
|
|
|
</code-snippet>
|
|
|
|
|
|
Compose layout by nesting ___SINGLE_BACKTICK___Section___SINGLE_BACKTICK___ and ___SINGLE_BACKTICK___Grid___SINGLE_BACKTICK___. Children need explicit ___SINGLE_BACKTICK___->columnSpan()___SINGLE_BACKTICK___ or ___SINGLE_BACKTICK___->columnSpanFull()___SINGLE_BACKTICK___:
|
|
|
|
|
|
<code-snippet name="Section and Grid layout" lang="php">
|
|
use Filament\Schemas\Components\Grid;
|
|
use Filament\Schemas\Components\Section;
|
|
|
|
Section::make('Details')
|
|
->schema([
|
|
Grid::make(2)->schema([
|
|
TextInput::make('first_name')
|
|
->columnSpan(1),
|
|
TextInput::make('last_name')
|
|
->columnSpan(1),
|
|
TextInput::make('bio')
|
|
->columnSpanFull(),
|
|
]),
|
|
]),
|
|
|
|
</code-snippet>
|
|
|
|
|
|
Use ___SINGLE_BACKTICK___Repeater___SINGLE_BACKTICK___ for inline ___SINGLE_BACKTICK___HasMany___SINGLE_BACKTICK___ management. ___SINGLE_BACKTICK___->relationship()___SINGLE_BACKTICK___ with no args binds to the relationship matching the field name:
|
|
|
|
|
|
<code-snippet name="Repeater for HasMany" lang="php">
|
|
use Filament\Forms\Components\Repeater;
|
|
|
|
Repeater::make('qualifications')
|
|
->relationship()
|
|
->schema([
|
|
TextInput::make('institution')
|
|
->required(),
|
|
TextInput::make('qualification')
|
|
->required(),
|
|
])
|
|
->columns(2),
|
|
|
|
</code-snippet>
|
|
|
|
|
|
Use ___SINGLE_BACKTICK___state()___SINGLE_BACKTICK___ with a ___SINGLE_BACKTICK___Closure___SINGLE_BACKTICK___ to compute derived column values:
|
|
|
|
|
|
<code-snippet name="Computed table column value" lang="php">
|
|
use Filament\Tables\Columns\TextColumn;
|
|
|
|
TextColumn::make('full_name')
|
|
->state(fn (User $record): string => "{$record->first_name} {$record->last_name}"),
|
|
|
|
</code-snippet>
|
|
|
|
|
|
Use ___SINGLE_BACKTICK___SelectFilter___SINGLE_BACKTICK___ for enum or relationship filters, and ___SINGLE_BACKTICK___Filter___SINGLE_BACKTICK___ with a ___SINGLE_BACKTICK___->query()___SINGLE_BACKTICK___ closure for custom logic:
|
|
|
|
|
|
<code-snippet name="Table filters" lang="php">
|
|
use Filament\Tables\Filters\Filter;
|
|
use Filament\Tables\Filters\SelectFilter;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
SelectFilter::make('status')
|
|
->options(UserStatus::class),
|
|
|
|
SelectFilter::make('author')
|
|
->relationship('author', 'name'),
|
|
|
|
Filter::make('verified')
|
|
->query(fn (Builder $query) => $query->whereNotNull('email_verified_at')),
|
|
|
|
</code-snippet>
|
|
|
|
|
|
Actions are buttons that encapsulate optional modal forms and behavior:
|
|
|
|
|
|
<code-snippet name="Action with modal form" lang="php">
|
|
use Filament\Actions\Action;
|
|
|
|
Action::make('updateEmail')
|
|
->schema([
|
|
TextInput::make('email')
|
|
->email()
|
|
->required(),
|
|
])
|
|
->action(fn (array $data, User $record) => $record->update($data)),
|
|
|
|
</code-snippet>
|
|
|
|
|
|
### Testing
|
|
|
|
Testing setup (requires ___SINGLE_BACKTICK___pestphp/pest-plugin-livewire___SINGLE_BACKTICK___ in ___SINGLE_BACKTICK___composer.json___SINGLE_BACKTICK___):
|
|
|
|
- Always call ___SINGLE_BACKTICK___$this->actingAs(User::factory()->create())___SINGLE_BACKTICK___ before testing panel functionality.
|
|
- For edit pages, pass ___SINGLE_BACKTICK___['record' => $user->id]___SINGLE_BACKTICK___, use ___SINGLE_BACKTICK___->call('save')___SINGLE_BACKTICK___ (not ___SINGLE_BACKTICK___->call('create')___SINGLE_BACKTICK___), and do not assert ___SINGLE_BACKTICK___->assertRedirect()___SINGLE_BACKTICK___ (edit pages do not redirect after save).
|
|
|
|
|
|
<code-snippet name="Table test" lang="php">
|
|
use function Pest\Livewire\livewire;
|
|
|
|
livewire(ListUsers::class)
|
|
->assertCanSeeTableRecords($users)
|
|
->searchTable($users->first()->name)
|
|
->assertCanSeeTableRecords($users->take(1))
|
|
->assertCanNotSeeTableRecords($users->skip(1));
|
|
|
|
</code-snippet>
|
|
|
|
<code-snippet name="Create resource test" lang="php">
|
|
use function Pest\Laravel\assertDatabaseHas;
|
|
|
|
livewire(CreateUser::class)
|
|
->fillForm([
|
|
'name' => 'Test',
|
|
'email' => 'test@example.com',
|
|
])
|
|
->call('create')
|
|
->assertNotified()
|
|
->assertHasNoFormErrors()
|
|
->assertRedirect();
|
|
|
|
assertDatabaseHas(User::class, [
|
|
'name' => 'Test',
|
|
'email' => 'test@example.com',
|
|
]);
|
|
|
|
</code-snippet>
|
|
|
|
<code-snippet name="Edit resource test" lang="php">
|
|
livewire(EditUser::class, ['record' => $user->id])
|
|
->fillForm(['name' => 'Updated'])
|
|
->call('save')
|
|
->assertNotified()
|
|
->assertHasNoFormErrors();
|
|
|
|
assertDatabaseHas(User::class, [
|
|
'id' => $user->id,
|
|
'name' => 'Updated',
|
|
]);
|
|
|
|
</code-snippet>
|
|
|
|
<code-snippet name="Testing validation" lang="php">
|
|
livewire(CreateUser::class)
|
|
->fillForm([
|
|
'name' => null,
|
|
'email' => 'invalid-email',
|
|
])
|
|
->call('create')
|
|
->assertHasFormErrors([
|
|
'name' => 'required',
|
|
'email' => 'email',
|
|
])
|
|
->assertNotNotified();
|
|
|
|
</code-snippet>
|
|
|
|
|
|
Use ___SINGLE_BACKTICK___->callAction(DeleteAction::class)___SINGLE_BACKTICK___ for page actions, or ___SINGLE_BACKTICK___->callAction(TestAction::make('name')->table($record))___SINGLE_BACKTICK___ for table actions:
|
|
|
|
|
|
<code-snippet name="Calling actions" lang="php">
|
|
use Filament\Actions\Testing\TestAction;
|
|
|
|
livewire(ListUsers::class)
|
|
->callAction(TestAction::make('promote')->table($user), [
|
|
'role' => 'admin',
|
|
])
|
|
->assertNotified();
|
|
|
|
</code-snippet>
|
|
|
|
|
|
### Correct Namespaces
|
|
|
|
- Form fields (___SINGLE_BACKTICK___TextInput___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Select___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Repeater___SINGLE_BACKTICK___, etc.): ___SINGLE_BACKTICK___Filament\Forms\Components\___SINGLE_BACKTICK___
|
|
- Infolist entries (___SINGLE_BACKTICK___TextEntry___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___IconEntry___SINGLE_BACKTICK___, etc.): ___SINGLE_BACKTICK___Filament\Infolists\Components\___SINGLE_BACKTICK___
|
|
- Layout components (___SINGLE_BACKTICK___Grid___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Section___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Fieldset___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Tabs___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Wizard___SINGLE_BACKTICK___, etc.): ___SINGLE_BACKTICK___Filament\Schemas\Components\___SINGLE_BACKTICK___
|
|
- Schema utilities (___SINGLE_BACKTICK___Get___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Set___SINGLE_BACKTICK___, etc.): ___SINGLE_BACKTICK___Filament\Schemas\Components\Utilities\___SINGLE_BACKTICK___
|
|
- Table columns (___SINGLE_BACKTICK___TextColumn___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___IconColumn___SINGLE_BACKTICK___, etc.): ___SINGLE_BACKTICK___Filament\Tables\Columns\___SINGLE_BACKTICK___
|
|
- Table filters (___SINGLE_BACKTICK___SelectFilter___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Filter___SINGLE_BACKTICK___, etc.): ___SINGLE_BACKTICK___Filament\Tables\Filters\___SINGLE_BACKTICK___
|
|
- Actions (___SINGLE_BACKTICK___DeleteAction___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___CreateAction___SINGLE_BACKTICK___, etc.): ___SINGLE_BACKTICK___Filament\Actions\___SINGLE_BACKTICK___. Never use ___SINGLE_BACKTICK___Filament\Tables\Actions\___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Filament\Forms\Actions\___SINGLE_BACKTICK___, or any other sub-namespace for actions.
|
|
- Icons: ___SINGLE_BACKTICK___Filament\Support\Icons\Heroicon___SINGLE_BACKTICK___ enum (e.g., ___SINGLE_BACKTICK___Heroicon::PencilSquare___SINGLE_BACKTICK___)
|
|
|
|
### Common Mistakes
|
|
|
|
- **Never assume public file visibility.** File visibility is ___SINGLE_BACKTICK___private___SINGLE_BACKTICK___ by default. Always use ___SINGLE_BACKTICK___->visibility('public')___SINGLE_BACKTICK___ when public access is needed.
|
|
- **Never assume full-width layout.** ___SINGLE_BACKTICK___Grid___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Section___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Fieldset___SINGLE_BACKTICK___, and ___SINGLE_BACKTICK___Repeater___SINGLE_BACKTICK___ do not span all columns by default.
|
|
- **Use ___SINGLE_BACKTICK___Select::make('author_id')->relationship('author', 'name')___SINGLE_BACKTICK___ for BelongsTo fields.** ___SINGLE_BACKTICK___BelongsToSelect___SINGLE_BACKTICK___ does not exist in v4.
|
|
- **___SINGLE_BACKTICK___Repeater___SINGLE_BACKTICK___ uses ___SINGLE_BACKTICK___->schema()___SINGLE_BACKTICK___, not ___SINGLE_BACKTICK___->fields()___SINGLE_BACKTICK___.**
|
|
- **Never add ___SINGLE_BACKTICK___->dehydrated(false)___SINGLE_BACKTICK___ to fields that need to be saved.** It strips the value from form state before ___SINGLE_BACKTICK___->action()___SINGLE_BACKTICK___ or the save handler runs. Only use it for helper/UI-only fields.
|
|
- **Use correct property types when overriding ___SINGLE_BACKTICK___Page___SINGLE_BACKTICK___, ___SINGLE_BACKTICK___Resource___SINGLE_BACKTICK___, and ___SINGLE_BACKTICK___Widget___SINGLE_BACKTICK___ properties.** These properties have union types or changed modifiers that must be preserved:
|
|
- ___SINGLE_BACKTICK___$navigationIcon___SINGLE_BACKTICK___: ___SINGLE_BACKTICK___protected static string | BackedEnum | null___SINGLE_BACKTICK___ (not ___SINGLE_BACKTICK___?string___SINGLE_BACKTICK___)
|
|
- ___SINGLE_BACKTICK___$navigationGroup___SINGLE_BACKTICK___: ___SINGLE_BACKTICK___protected static string | UnitEnum | null___SINGLE_BACKTICK___ (not ___SINGLE_BACKTICK___?string___SINGLE_BACKTICK___)
|
|
- ___SINGLE_BACKTICK___$view___SINGLE_BACKTICK___: ___SINGLE_BACKTICK___protected string___SINGLE_BACKTICK___ (not ___SINGLE_BACKTICK___protected static string___SINGLE_BACKTICK___) on ___SINGLE_BACKTICK___Page___SINGLE_BACKTICK___ and ___SINGLE_BACKTICK___Widget___SINGLE_BACKTICK___ classes
|
|
<?php /**PATH /Users/mag/Sites/WMS/Concepts/WMS_Core/storage/framework/views/db800410232a09db40ffd9b85b3884fa.blade.php ENDPATH**/ ?>
|