Skip to content

Commit 050b0ed

Browse files
committed
Merge branch '4.x' into 5.x
2 parents 7d3cd6e + 029f358 commit 050b0ed

File tree

39 files changed

+1013
-27
lines changed

39 files changed

+1013
-27
lines changed

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
composer.lock
55
docs-assets
66
packages/panels/resources/boost
7+
tests/src/Panels/Commands/Fixture
78
tests/src/Upgrade/Fixture

docs/08-styling/01-overview.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,17 @@ By default, this command will use NPM to install dependencies. If you want to us
128128
php artisan make:filament-theme --pm=bun
129129
````
130130

131-
This command generates a CSS file in the `resources/css/filament` directory.
131+
This command will:
132+
133+
1. Install the required Tailwind CSS dependencies
134+
2. Generate a CSS file in `resources/css/filament/{panel}/theme.css`
135+
3. Attempt to automatically add the theme to your `vite.config.js` input array
136+
4. Attempt to automatically register `->viteTheme()` in your panel provider
137+
5. Offer to compile the theme with Vite
138+
139+
If the command cannot automatically configure your files (due to non-standard formatting), it will display manual instructions instead. In that case, follow these steps:
140+
141+
### Manual configuration
132142
133143
Add the theme's CSS file to the Laravel plugin's `input` array in `vite.config.js`:
134144
@@ -139,7 +149,7 @@ input: [
139149
]
140150
```
141151

142-
Now, register the Vite-compiled theme CSS file in the panel's provider:
152+
Register the Vite-compiled theme CSS file in the panel's provider:
143153

144154
```php
145155
use Filament\Panel;
@@ -152,14 +162,14 @@ public function panel(Panel $panel): Panel
152162
}
153163
```
154164

155-
Finally, compile the theme with Vite:
165+
Then compile the theme with Vite:
156166

157167
```bash
158168
npm run build
159169
```
160170

161171
<Aside variant="info">
162-
Check the command output for the exact file path (e.g., `app/theme.css`), as it may vary depending on your panel's ID.
172+
Check the command output for the exact file path (e.g., `admin/theme.css`), as it may vary depending on your panel's ID.
163173
</Aside>
164174

165175
You can now customize the theme by editing the CSS file in `resources/css/filament`.

packages/actions/src/Action.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ public function getContext(): array
473473
(! $table)
474474
|| (! $record instanceof Model)
475475
|| blank($table->getModel())
476-
|| ($record::class === $table->getModel())
476+
|| is_a($record::class, $table->getModel(), true)
477477
) && filled($recordKey = $this->resolveRecordKey($record))) {
478478
$context['recordKey'] = $recordKey;
479479
}

packages/panels/src/Commands/MakeThemeCommand.php

Lines changed: 167 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
use Filament\Support\Commands\Exceptions\FailureCommandOutput;
88
use Filament\Support\Commands\Exceptions\SuccessCommandOutput;
99
use Illuminate\Console\Command;
10+
use Illuminate\Filesystem\Filesystem;
1011
use Illuminate\Support\Arr;
1112
use Illuminate\Support\Str;
1213
use Symfony\Component\Console\Attribute\AsCommand;
1314
use Symfony\Component\Console\Input\InputArgument;
1415
use Symfony\Component\Console\Input\InputOption;
1516

17+
use function Laravel\Prompts\confirm;
18+
1619
#[AsCommand(name: 'make:filament-theme', aliases: [
1720
'filament:make-theme',
1821
'filament:theme',
@@ -28,6 +31,10 @@ class MakeThemeCommand extends Command
2831

2932
protected string $pm;
3033

34+
protected Filesystem $filesystem;
35+
36+
protected string $themePath;
37+
3138
/**
3239
* @var array<string>
3340
*/
@@ -76,12 +83,16 @@ protected function getOptions(): array
7683
];
7784
}
7885

79-
public function handle(): int
86+
public function handle(Filesystem $filesystem): int
8087
{
88+
$this->filesystem = $filesystem;
89+
8190
try {
8291
$this->configurePanel(question: 'Which panel would you like to create this theme for?');
8392
$this->configurePackageManager();
8493

94+
$this->themePath = "resources/css/filament/{$this->panel->getId()}/theme.css";
95+
8596
$this->installDependencies();
8697
$this->createThemeSourceFiles();
8798

@@ -92,12 +103,32 @@ public function handle(): int
92103
return static::SUCCESS;
93104
}
94105

95-
$this->components->warn('Action is required to complete the theme setup:');
96-
$this->components->bulletList([
97-
"First, add a new item to the Laravel plugin's `input` array in `vite.config.js`: `resources/css/filament/{$this->panel->getId()}/theme.css`.",
98-
"Next, register the theme in the {$this->panel->getId()} panel provider using `->viteTheme('resources/css/filament/{$this->panel->getId()}/theme.css')`",
99-
"Finally, run `{$this->pm} run build` to compile the theme.",
100-
]);
106+
$pendingActions = [];
107+
108+
// Try to register in `vite.config.js`
109+
if (! $this->registerInViteConfig()) {
110+
$pendingActions[] = "Add a new item to the Laravel plugin's `input` array in `vite.config.js`: `{$this->themePath}`.";
111+
}
112+
113+
// Try to register in panel provider
114+
if (! $this->registerInPanelProvider()) {
115+
$pendingActions[] = "Register the theme in the {$this->panel->getId()} panel provider using `->viteTheme('{$this->themePath}')`";
116+
}
117+
118+
// Show pending manual actions if any
119+
if (count($pendingActions) > 0) {
120+
$this->components->warn('Action is required to complete the theme setup:');
121+
$this->components->bulletList($pendingActions);
122+
$this->newLine();
123+
}
124+
125+
// Offer to compile the theme
126+
if (confirm('Would you like to compile the theme now?', default: true)) {
127+
$this->components->info('Compiling theme...');
128+
passthru("{$this->pm} run build");
129+
} else {
130+
$this->components->info("Run `{$this->pm} run build` to compile the theme.");
131+
}
101132

102133
return static::SUCCESS;
103134
}
@@ -163,14 +194,140 @@ protected function abortIfNotVite(): void
163194
return;
164195
}
165196

197+
$panelId = $this->panel->getId();
198+
$publicPath = "public/css/filament/{$panelId}/theme.css";
199+
166200
$this->components->warn('Action is required to complete the theme setup:');
167201
$this->components->bulletList([
168-
"It looks like you don't have Vite installed. Please use your asset bundling system of choice to compile `resources/css/filament/{$this->panel->getId()}/theme.css` into `public/css/filament/{$this->panel->getId()}/theme.css`.",
202+
"It looks like you don't have Vite installed. Please use your asset bundling system of choice to compile `{$this->themePath}` into `{$publicPath}`.",
169203
"If you're not currently using a bundler, we recommend using Vite. Alternatively, you can use the Tailwind CLI with the following command:",
170-
"npx @tailwindcss/cli --input ./resources/css/filament/{$this->panel->getId()}/theme.css --output ./public/css/filament/{$this->panel->getId()}/theme.css --config ./resources/css/filament/{$this->panel->getId()}/tailwind.config.js --minify",
171-
"Make sure to register the theme in the {$this->panel->getId()} panel provider using `->theme(asset('css/filament/{$this->panel->getId()}/theme.css'))`",
204+
"npx @tailwindcss/cli --input ./{$this->themePath} --output ./{$publicPath} --minify",
205+
"Make sure to register the theme in the {$panelId} panel provider using `->theme(asset('css/filament/{$panelId}/theme.css'))`",
172206
]);
173207

174208
throw new SuccessCommandOutput;
175209
}
210+
211+
protected function registerInViteConfig(): bool
212+
{
213+
$viteConfigPath = base_path('vite.config.js');
214+
215+
if (! $this->filesystem->exists($viteConfigPath)) {
216+
return false;
217+
}
218+
219+
$contents = $this->filesystem->get($viteConfigPath);
220+
221+
// Check if already registered
222+
if (str_contains($contents, $this->themePath)) {
223+
$this->components->info('Theme already registered in vite.config.js.');
224+
225+
return true;
226+
}
227+
228+
// Look for the laravel plugin input array pattern
229+
// Match: input: ['...', '...'] or input: ["...", "..."]
230+
$pattern = '/(\binput\s*:\s*\[)([^\]]*?)(\])/s';
231+
232+
if (! preg_match($pattern, $contents, $matches)) {
233+
return false;
234+
}
235+
236+
$inputArrayContents = $matches[2];
237+
238+
// Verify the array contains recognizable Laravel paths (resources/css or resources/js)
239+
if (! preg_match('/[\'"]resources\/(css|js)\//', $inputArrayContents)) {
240+
return false;
241+
}
242+
243+
// Detect quote style from existing entries
244+
$quoteStyle = str_contains($inputArrayContents, "'") ? "'" : '"';
245+
246+
// Find the last quoted string in the array (with optional trailing comma and whitespace)
247+
if (! preg_match('/^(.*[\'"][^\'"]+[\'"]),?(\s*)$/s', $inputArrayContents, $lastEntryMatch)) {
248+
return false;
249+
}
250+
251+
$beforeTrailing = $lastEntryMatch[1];
252+
$trailingWhitespace = $lastEntryMatch[2];
253+
254+
// Build new input array contents - add comma after existing entry, then new entry
255+
$newEntry = "{$quoteStyle}{$this->themePath}{$quoteStyle}";
256+
257+
// If multiline (has newlines), preserve the formatting
258+
if (str_contains($trailingWhitespace, "\n")) {
259+
// Extract the indentation from existing array entries (look for newline followed by spaces and a quote)
260+
preg_match('/\n(\s+)[\'"]/', $inputArrayContents, $indentMatch);
261+
$indent = $indentMatch[1] ?? ' ';
262+
$newInputArrayContents = $beforeTrailing . ",\n{$indent}{$newEntry}," . $trailingWhitespace;
263+
} else {
264+
// Single line - just append with comma
265+
$newInputArrayContents = $beforeTrailing . ", {$newEntry}" . $trailingWhitespace;
266+
}
267+
268+
$newContents = preg_replace(
269+
$pattern,
270+
'$1' . str_replace(['\\', '$'], ['\\\\', '\\$'], $newInputArrayContents) . '$3',
271+
$contents,
272+
1
273+
);
274+
275+
if ($newContents === null || $newContents === $contents) {
276+
return false;
277+
}
278+
279+
$this->filesystem->put($viteConfigPath, $newContents);
280+
$this->components->info('Added theme to vite.config.js input array.');
281+
282+
return true;
283+
}
284+
285+
protected function registerInPanelProvider(): bool
286+
{
287+
$panelId = $this->panel->getId();
288+
289+
// Find the panel provider file
290+
$providerPath = app_path('Providers/Filament/' . Str::studly($panelId) . 'PanelProvider.php');
291+
292+
if (! $this->filesystem->exists($providerPath)) {
293+
return false;
294+
}
295+
296+
$contents = $this->filesystem->get($providerPath);
297+
298+
// Check if already registered
299+
if (str_contains($contents, 'viteTheme(')) {
300+
$this->components->info('viteTheme() already configured in panel provider.');
301+
302+
return true;
303+
}
304+
305+
// Look for ->id('panelId') to confirm we're in the right file
306+
$idPattern = '/(->id\s*\(\s*[\'"]' . preg_quote($panelId, '/') . '[\'"]\s*\))(\s*\n)/';
307+
if (! preg_match($idPattern, $contents)) {
308+
return false;
309+
}
310+
311+
// Try to insert after ->path() first, then fall back to ->id()
312+
$pathPattern = '/(->path\s*\(\s*[\'"][^\'"]*[\'"]\s*\))(\s*\n)/';
313+
314+
if (preg_match($pathPattern, $contents)) {
315+
$pattern = $pathPattern;
316+
} else {
317+
// No ->path() found, insert after ->id() instead
318+
$pattern = $idPattern;
319+
}
320+
321+
$replacement = '$1' . "\n ->viteTheme('{$this->themePath}')" . '$2';
322+
$newContents = preg_replace($pattern, $replacement, $contents, 1);
323+
324+
if ($newContents === null || $newContents === $contents) {
325+
return false;
326+
}
327+
328+
$this->filesystem->put($providerPath, $newContents);
329+
$this->components->info('Added viteTheme() to panel provider.');
330+
331+
return true;
332+
}
176333
}

packages/panels/src/Panel/Concerns/HasTheme.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ public function viteTheme(string | array $theme, ?string $buildDirectory = null)
3232
return $this;
3333
}
3434

35+
/**
36+
* @return string | array<string> | null
37+
*/
38+
public function getViteTheme(): string | array | null
39+
{
40+
return $this->viteTheme;
41+
}
42+
3543
public function theme(string | Htmlable | Theme $theme): static
3644
{
3745
$this->theme = $theme;
@@ -41,8 +49,8 @@ public function theme(string | Htmlable | Theme $theme): static
4149

4250
public function getTheme(): Theme
4351
{
44-
if (filled($this->viteTheme)) {
45-
$this->theme = app(Vite::class)($this->viteTheme, $this->viteThemeBuildDirectory);
52+
if (filled($viteTheme = $this->getViteTheme())) {
53+
$this->theme = app(Vite::class)($viteTheme, $this->viteThemeBuildDirectory);
4654
}
4755

4856
if (blank($this->theme)) {

packages/spatie-laravel-media-library-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ The media library file upload supports all the customization options of the [ori
3838

3939
### Passing a collection
4040

41-
Optionally, you may pass a [`collection()`](https://spatie.be/docs/laravel-medialibrary/working-with-media-collections/simple-media-collections) allows you to group files into categories:
41+
Optionally, you may pass a [`collection()`](https://spatie.be/docs/laravel-medialibrary/working-with-media-collections/simple-media-collections) that allows you to group files into categories:
4242

4343
```php
4444
use Filament\Forms\Components\SpatieMediaLibraryFileUpload;

packages/spatie-laravel-tags-plugin/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ You must also [prepare your Eloquent model](https://spatie.be/docs/laravel-tags/
2626
2727
## Form component
2828

29-
This guide assumes that you've already set up your model attach tags as per [Spatie's documentation](https://spatie.be/docs/laravel-tags/basic-usage/using-tags).
29+
This guide assumes that you've already set up your model to attach tags as per [Spatie's documentation](https://spatie.be/docs/laravel-tags/basic-usage/using-tags).
3030

3131
You may use the field in the same way as the [original tags input](https://filamentphp.com/docs/forms/tags-input) field:
3232

@@ -36,7 +36,7 @@ use Filament\Forms\Components\SpatieTagsInput;
3636
SpatieTagsInput::make('tags')
3737
```
3838

39-
Optionally, you may pass a [`type()`](https://spatie.be/docs/laravel-tags/advanced-usage/using-types) allows you to group tags into collections:
39+
Optionally, you may pass a [`type()`](https://spatie.be/docs/laravel-tags/advanced-usage/using-types) that allows you to group tags into collections:
4040

4141
```php
4242
use Filament\Forms\Components\SpatieTagsInput;

packages/tables/src/Concerns/CanSortRecords.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ protected function applySortingToTableQuery(Builder $query): Builder
8585
return $query->orderBy($this->getTable()->getReorderColumn(), $this->getTable()->getReorderDirection());
8686
}
8787

88+
$tableSortColumn = $this->getTableSortColumn();
89+
8890
if (
89-
$this->getTableSortColumn() &&
90-
$column = $this->getTable()->getSortableVisibleColumn($this->getTableSortColumn())
91+
$tableSortColumn &&
92+
$column = $this->getTable()->getSortableVisibleColumn($tableSortColumn)
9193
) {
9294
$sortDirection = $this->getTableSortDirection() === 'desc' ? 'desc' : 'asc';
9395

@@ -99,11 +101,14 @@ protected function applySortingToTableQuery(Builder $query): Builder
99101

100102
if (
101103
is_string($defaultSort) &&
102-
($defaultSort !== $this->getTableSortColumn()) &&
104+
($defaultSort !== $tableSortColumn) &&
103105
($sortColumn = $this->getTable()->getSortableVisibleColumn($defaultSort))
104106
) {
105107
$sortColumn->applySort($query, $sortDirection);
106-
} elseif (is_string($defaultSort)) {
108+
} elseif (
109+
is_string($defaultSort) &&
110+
$defaultSort !== $tableSortColumn
111+
) {
107112
$query->orderBy($defaultSort, $sortDirection);
108113
}
109114

packages/widgets/docs/03-charts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ window.filamentChartJsPlugins.push(ChartDataLabels)
338338
339339
This is equivalent to including the plugins "inline" via `new Chart(..., { plugins: [...] })` when instantiating a Chart.js chart.
340340
341-
It's important to initialise the array if it has not been already, before pushing onto it. This ensures that mutliple JavaScript files (especially those from Filament plugins) that register Chart.js plugins do not overwrite each other, regardless of the order they are booted in.
341+
It's important to initialise the array if it has not been already, before pushing onto it. This ensures that multiple JavaScript files (especially those from Filament plugins) that register Chart.js plugins do not overwrite each other, regardless of the order they are booted in.
342342
343343
You can push as many plugins to the array as you would like to install, you do not need a separate file to import each plugin.
344344

pint-strict-imports.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
"types_spaces": {
1515
"space": "single"
1616
}
17-
}
17+
},
18+
"exclude": ["tests/src/Panels/Commands/Fixture"]
1819
}

0 commit comments

Comments
 (0)