From 8e348c2803d5859f8bb9e8c2a8514334bfd3dcfc Mon Sep 17 00:00:00 2001 From: Bezhan Salleh Date: Sun, 8 Feb 2026 07:02:34 +0100 Subject: [PATCH 1/2] feat:v3.x Enhances Panel access, adds dark icons support and removes deprecated APIs --- README.md | 218 ++++--------- composer.json | 2 +- .../views/components/card-grid.blade.php | 56 ---- .../views/components/panel-icon.blade.php | 15 + .../views/components/panel-list.blade.php | 113 +++++++ .../views/components/panel-trigger.blade.php | 85 +++++ .../components/slide-over-list.blade.php | 75 ----- resources/views/panel-switch-menu.blade.php | 298 +++++++----------- src/PanelSwitch.php | 143 ++++----- 9 files changed, 456 insertions(+), 549 deletions(-) delete mode 100644 resources/views/components/card-grid.blade.php create mode 100644 resources/views/components/panel-icon.blade.php create mode 100644 resources/views/components/panel-list.blade.php create mode 100644 resources/views/components/panel-trigger.blade.php delete mode 100644 resources/views/components/slide-over-list.blade.php diff --git a/README.md b/README.md index b687666..e8c95b1 100644 --- a/README.md +++ b/README.md @@ -41,35 +41,25 @@ The Panel Switch Plugin for Filament offers a robust and customizable component Configuration @@ -132,168 +122,116 @@ PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { // Custom configurations go here }); ``` -### Design or Style -By default, the Plugin uses Filament's [Modal Blade component](https://filamentphp.com/docs/3.x/support/blade-components/modal) as the modern design for the panel switch menu. But you can change it to the simple design by using the `simple()` method. - -- #### Modern - - ##### Modal Heading - Set a custom Modal Heading for the Panel Switcher. By default, the modal heading is set to `Switch Panels`. - ```php - use BezhanSalleh\PanelSwitch\PanelSwitch; - - PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->modalHeading('Available Panels'); - }); - ``` - - ##### Modal Width - By default, the modal width is set to `screen` but you can use the options avaialbel for [Modal Blade component](https://filamentphp.com/docs/3.x/support/blade-components/modal#changing-the-modal-width). - ```php - use BezhanSalleh\PanelSwitch\PanelSwitch; - - PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->modalWidth('sm'); - }); - ``` - - ##### Slide-Over - You can use the `slideOver()` method to open a `slide-over` dialog instead of the modal. - ```php - use BezhanSalleh\PanelSwitch\PanelSwitch; - - PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->slideOver(); - }); - ``` -- #### Simple - The `simple()` method transforms the panel switch menu to a dropdown list, allowing users to switch between panels directly from the list. - ```php - use BezhanSalleh\PanelSwitch\PanelSwitch; - - PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->simple(); - }); - ``` -### Labels -By using `labels()` method you can provide textual representation for each panel. The keys of the array should be valid panel IDs, and the values can be either regular strings or Laravel's translatable strings: +### Panel Access & Navigation +The panel switch is automatically visible when the authenticated user has access to two or more panels. Panel access is determined via the `canAccessPanel` method on your User model — no additional configuration needed. Panels the user cannot access are automatically hidden from the switch. -```php -use BezhanSalleh\PanelSwitch\PanelSwitch; +To further limit which panels appear in the switch, use the `panels()` method: +```php PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch - ->labels([ - 'admin' => 'Custom Admin Label', - 'general_manager' => __('General Manager') - ]); + $panelSwitch->panels([ + 'admin', + 'dev', + 'app' + ]); }); ``` +The `panels()` method also accepts a closure that returns an array of panel ids. This is useful when you want to dynamically determine the panels that will be listed. The plugin will also validate the panels to ensure that they are valid filament panels. If any of the panels provided are invalid, the plugin will throw an `InvalidArgumentException`. -### Icons/Images -Define icons/images for available panels using the `icons()` method which accepts an array. The keys of the array should be valid panel IDs. If using images instead of icons, set the `$asImage` parameter to `true` and the value of the array should be the path to the image meaning a valid url: +### Design or Style +The plugin supports two design styles: -- **Icons** -```php -use BezhanSalleh\PanelSwitch\PanelSwitch; +- **Modern** (default) — Uses Filament's [Modal Blade component](https://filamentphp.com/docs/3.x/support/blade-components/modal). You can customize the modal heading, [width](https://filamentphp.com/docs/3.x/support/blade-components/modal#changing-the-modal-width), or use a slide-over: -PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->icons([ - 'validPanelId1' => 'heroicon-o-square-2-stack', - 'validPanelId2' => 'heroicon-o-star', - ], $asImage = false); -}); -``` + ```php + PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { + $panelSwitch + ->modalHeading('Available Panels') + ->modalWidth('sm') + ->slideOver(); + }); + ``` -- **Images** -```php -use BezhanSalleh\PanelSwitch\PanelSwitch; +- **Simple** — A dropdown list for switching panels directly: -PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->icons([ - 'validPanelId1' => 'https://raw.githubusercontent.com/bezhanSalleh/filament-panel-switch/3.x/art/banner.jpg', - 'validPanelId2' => 'https://raw.githubusercontent.com/bezhanSalleh/filament-panel-switch/3.x/art/banner.jpg', - ], $asImage = true); -}); + ```php + PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { + $panelSwitch->simple(); + }); + ``` + +For full control over the look and feel, you can publish the views and make your own adjustments: + +```bash +php artisan vendor:publish --tag="filament-panel-switch-views" ``` -### Icon/Image Size -Use the `iconSize()` method to set the size of the icons/images. The default size is `128px`. The value provided will be multiplied by 4 and then used as the size of the icon/image. +### Labels +Provide custom labels for each panel. The keys should be valid panel IDs, and values can be strings or translatable strings: ```php -use BezhanSalleh\PanelSwitch\PanelSwitch; - -PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - // This would result in an icon/image size of 128 pixels. - $panelSwitch->iconSize(32); +PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { + $panelSwitch + ->labels([ + 'admin' => 'Custom Admin Label', + 'general_manager' => __('General Manager') + ]); }); ``` -### Visibility -By default, the package checks whether the user can access the panel if so the switch will be visible. You can further customize whether the panel switch should be shown. +### Icons +Define icons for each panel using the `icons()` method. The keys should be valid panel IDs: ```php -use BezhanSalleh\PanelSwitch\PanelSwitch; - -PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch - ->visible(fn (): bool => auth()->user()?->hasAnyRole([ - 'admin', - 'general_manager', - 'super_admin', - ])); +PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { + $panelSwitch->icons([ + 'admin' => 'heroicon-o-square-2-stack', + 'app' => 'heroicon-o-star', + ]); }); ``` -### Who Can Switch Panels? -You might want an option in a situation where you want a group of your users to see the panel but not be able to switch panels. You can do that by using the `canSwitchPanels()` method. +For images instead of icons, set the `asImage` parameter to `true`: ```php -use BezhanSalleh\PanelSwitch\PanelSwitch; - -PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch - ->canSwitchPanels(fn (): bool => auth()->user()?->can('switch_panels')); +PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { + $panelSwitch->icons([ + 'admin' => 'https://example.com/admin.jpg', + 'app' => 'https://example.com/app.jpg', + ], asImage: true); }); ``` -### Panels -By default all the panels available will be listed in the panel switch menu. But by providing an array of panel ids to the `panels()` method you can limit the panels that will be listed. +Use `iconSize()` to customize the size (default `128px`). The value is multiplied by 4: ```php PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->panels([ - 'admin', - 'dev', - 'app' - ]); + $panelSwitch->iconSize(32); // 128px }); ``` -Then `panels()` method also accepts a closure that returns an array of panel ids. This is useful when you want to dynamically determine the panels that will be listed. The plugin will also validate the panels to ensure that they are valid filament panels. If any of the panels provided are invalid, the plugin will throw an `InvalidArgumentException`. ### Sort Order -By default the panels will be listed in the order they were registered in `config/app.php`'s `providers` array or in the order they are provided through the `panels()` method. But you can opt-in to sort the panels either in `asc` or `desc` order via `sort()` method. +By default panels are listed in registration order, or in the order provided via `panels()`. Use `sort()` to sort alphabetically: + ```php PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { $panelSwitch - ... - ->panels(['admin', 'dev', 'app']) // default order if `sort()` method not used - ->sort() // ['admin', 'app', 'dev'] - // ->sort(order: 'desc') // ['dev', 'app', 'admin'] - ... - ; + ->panels(['admin', 'dev', 'app']) + ->sort(); // ascending: ['admin', 'app', 'dev'] + // ->sort('desc') // descending: ['dev', 'app', 'admin'] }); ``` ### Placement -You can choose where the panel switch menu should be placed. By default panel switch menu is rendered via 'panels::global-search.before' `Hook`. But you can change it to anyone of the other available Filament [Render Hooks](https://filamentphp.com/docs/3.x/support/render-hooks#available-render-hooks). -```php -use BezhanSalleh\PanelSwitch\PanelSwitch; +By default the panel switch is rendered via the `panels::global-search.before` render hook. You can change this to any available Filament [Render Hook](https://filamentphp.com/docs/3.x/support/render-hooks#available-render-hooks): +```php PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { $panelSwitch->renderHook('panels::global-search.after'); }); ``` -### Usage -The `Panel Switch Plugin` has a fluent api so you can chain the methods together and configure everything in one go. +### Full Example ```php use BezhanSalleh\PanelSwitch\PanelSwitch; @@ -317,22 +255,6 @@ PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { }); ``` -### Panel Exclusion -**`@deprecated`** use **`panels()`** method instead. -By default all the panels available will be listed in the panel switch menu. But you can exclude some of them by using the excludes() method. -```php -PanelSwitch::configureUsing(function (PanelSwitch $panelSwitch) { - $panelSwitch->excludes([ - 'saas' - ]); -}); -``` - -Optionally, you can publish the views using - -```bash -php artisan vendor:publish --tag="filament-panel-switch-views" -``` ### Testing ```bash @@ -378,4 +300,4 @@ Please review [our security policy](../../security/policy) on how to report secu ## License -The MIT License (MIT). Please see [License File](LICENSE.md) for more information. +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. \ No newline at end of file diff --git a/composer.json b/composer.json index 7ea3886..8c3719a 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ } }, "scripts": { - "format": "vendor/bin/pint" + "lint": "pint" }, "config": { "sort-packages": true diff --git a/resources/views/components/card-grid.blade.php b/resources/views/components/card-grid.blade.php deleted file mode 100644 index 2cbf8b7..0000000 --- a/resources/views/components/card-grid.blade.php +++ /dev/null @@ -1,56 +0,0 @@ -@props([ - 'panels', - 'currentPanel', - 'labels' => [], - 'icons' => [], - 'iconSize' => 32, - 'renderIconAsImage' => false, -]) - -@php - $defaultIcon = 'heroicon-s-square-2-stack'; - $defaultImage = 'https://raw.githubusercontent.com/bezhanSalleh/filament-panel-switch/3.x/art/banner.jpg'; -@endphp - -
- @foreach ($panels as $id => $url) - @php - $isCurrentPanel = $id === $currentPanel->getId(); - $panelLabel = $labels[$id] ?? str($id)->ucfirst(); - $panelIcon = $icons[$id] ?? $defaultIcon; - @endphp - - -
! $isCurrentPanel, - "ring-2 ring-primary-600" => $isCurrentPanel, - ]) - > - @if ($renderIconAsImage) - {{ $panelLabel }} - @else - @svg($panelIcon, 'text-primary-600 panel-switch-card-icon', ['style' => 'width: ' . ($iconSize * 4) . 'px; height: ' . ($iconSize * 4). 'px;']) - @endif -
- ! $isCurrentPanel, - "text-primary-600 dark:text-primary-400" => $isCurrentPanel, - ]) - > - {{ $panelLabel }} - -
- @endforeach -
diff --git a/resources/views/components/panel-icon.blade.php b/resources/views/components/panel-icon.blade.php new file mode 100644 index 0000000..8a4ab35 --- /dev/null +++ b/resources/views/components/panel-icon.blade.php @@ -0,0 +1,15 @@ +@props([ + 'icon', + 'darkIcon' => null, + 'renderAsImage' => false, +]) + + + @if ($renderAsImage) + + + @else + + + @endif + diff --git a/resources/views/components/panel-list.blade.php b/resources/views/components/panel-list.blade.php new file mode 100644 index 0000000..8b5fa47 --- /dev/null +++ b/resources/views/components/panel-list.blade.php @@ -0,0 +1,113 @@ +@props([ + 'panels', + 'currentPanel', + 'labels' => [], + 'icons' => [], + 'darkIcons' => [], + 'iconSize' => 32, + 'renderIconAsImage' => false, + 'slideOver' => false, +]) + +@php + $defaultIcon = $slideOver ? 'heroicon-o-square-2-stack' : 'heroicon-s-square-2-stack'; + $defaultImage = 'https://raw.githubusercontent.com/bezhanSalleh/filament-panel-switch/3.x/art/banner.jpg'; + $tag = $slideOver ? 'ul' : 'div'; + $tagClasses = match ($tag) { + 'ul' => 'space-y-2', + default => 'flex flex-wrap items-center justify-center gap-4 md:gap-6' + }; +@endphp + +<{{ $tag }} class="{{ $tagClasses }}"> + @foreach ($panels as $id => $url) + @php + $isCurrentPanel = $id === $currentPanel->getId(); + $panelLabel = $labels[$id] ?? str($id)->ucfirst(); + $panelIcon = $icons[$id] ?? ($renderIconAsImage ? $defaultImage : $defaultIcon); + $panelDarkIcon = $darkIcons[$id] ?? null; + @endphp + + @if ($slideOver) +
  • + ! $isCurrentPanel, + "bg-primary-50 dark:bg-primary-400/10 pointer-events-none" => $isCurrentPanel, + ]) + > + $renderIconAsImage, + 'h-6 w-6 shrink-0' => ! $renderIconAsImage, + 'text-gray-400 dark:text-gray-500' => ! $renderIconAsImage && ! $isCurrentPanel, + 'text-primary-600 dark:text-primary-400' => ! $renderIconAsImage && $isCurrentPanel, + ]) + :alt="$panelLabel" + /> + + ! $isCurrentPanel, + "text-primary-600 dark:text-primary-400" => $isCurrentPanel, + ]) + > + {{ $panelLabel }} + + + @if ($isCurrentPanel) + + @else + + @endif + +
  • + @else + +
    ! $isCurrentPanel, + "ring-2 ring-primary-600" => $isCurrentPanel, + ]) + > + ! $renderIconAsImage, + 'rounded-lg panel-switch-card-image' => $renderIconAsImage, + ]) + :style="'width: ' . ($iconSize * 4) . 'px; height: ' . ($iconSize * 4) . 'px;'" + :alt="$panelLabel" + /> +
    + ! $isCurrentPanel, + "text-primary-600 dark:text-primary-400" => $isCurrentPanel, + ]) + > + {{ $panelLabel }} + +
    + @endif + @endforeach + diff --git a/resources/views/components/panel-trigger.blade.php b/resources/views/components/panel-trigger.blade.php new file mode 100644 index 0000000..df01094 --- /dev/null +++ b/resources/views/components/panel-trigger.blade.php @@ -0,0 +1,85 @@ +@props([ + 'icon', + 'darkIcon' => null, + 'renderAsImage' => false, + 'label', + 'topbar' => false, + 'collapsibleSidebar' => false, + 'showChevron' => false, + 'modalId' => null, +]) + +@if ($topbar) + + + + +@else + +@endif diff --git a/resources/views/components/slide-over-list.blade.php b/resources/views/components/slide-over-list.blade.php deleted file mode 100644 index 4a3e118..0000000 --- a/resources/views/components/slide-over-list.blade.php +++ /dev/null @@ -1,75 +0,0 @@ -@props([ - 'panels', - 'currentPanel', - 'labels' => [], - 'icons' => [], - 'renderIconAsImage' => false, -]) - -@php - $defaultIcon = 'heroicon-o-square-2-stack'; - $defaultImage = 'https://raw.githubusercontent.com/bezhanSalleh/filament-panel-switch/3.x/art/banner.jpg'; -@endphp - - diff --git a/resources/views/panel-switch-menu.blade.php b/resources/views/panel-switch-menu.blade.php index 22f97ef..60e99c0 100644 --- a/resources/views/panel-switch-menu.blade.php +++ b/resources/views/panel-switch-menu.blade.php @@ -3,8 +3,8 @@ $isSidebarCollapsibleOnDesktop = filament()->isSidebarCollapsibleOnDesktop(); $currentLabel = $labels[$currentPanel->getId()] ?? str($currentPanel->getId())->ucfirst(); $currentIcon = $icons[$currentPanel->getId()] ?? 'heroicon-o-square-2-stack'; + $currentDarkIcon = $darkIcons[$currentPanel->getId()] ?? $currentIcon; $defaultIcon = 'heroicon-o-square-2-stack'; - $defaultImage = 'https://raw.githubusercontent.com/bezhanSalleh/filament-panel-switch/3.x/art/banner.jpg'; @endphp @if ($isSimple) @@ -14,135 +14,96 @@ :placement="$hasTopbar ? 'bottom-end' : 'top-start'" > - @if ($hasTopbar) - {{-- Topbar trigger --}} - - @else - {{-- Sidebar trigger --}} - - @endif + - {{-- Dropdown list (shared for both topbar and sidebar) --}} + {{-- Dropdown list --}} @foreach ($panels as $id => $url) @php $isCurrentPanel = $id === $currentPanel->getId(); $panelLabel = $labels[$id] ?? str($id)->ucfirst(); $panelIcon = $icons[$id] ?? $defaultIcon; + $panelDarkIcon = $darkIcons[$id] ?? null; @endphp - @if ($isCurrentPanel) - - - {{ $panelLabel }} - - - - @else - - {{ $panelLabel }} - - @endif + + @if ($isCurrentPanel) + + + {{ $panelLabel }} + + + + + + {{ $panelLabel }} + + + + @else + + {{ $panelLabel }} + + + {{ $panelLabel }} + + @endif + @endforeach @else - {{-- MODAL MODE --}} - - {{-- CSS for centered modal (non-slideover) --}} - @unless ($isSlideOver) - - @endunless - @if ($hasTopbar) - {{-- Topbar: Icon button trigger with separate modal --}}
    - - @if ($isSlideOver) - - @else - - @endif +
    @else - {{-- Sidebar: Modal with inline trigger --}} - + {{ $heading }} - @if ($isSlideOver) - - @else - - @endif + @endif @endif + +@once + @unless($isSlideOver) + + @endunless +@endonce \ No newline at end of file diff --git a/src/PanelSwitch.php b/src/PanelSwitch.php index 5a066a5..69ab77a 100644 --- a/src/PanelSwitch.php +++ b/src/PanelSwitch.php @@ -15,12 +15,6 @@ class PanelSwitch extends Component { use Concerns\HasPanelValidator; - protected array | Closure $excludes = []; - - protected bool | Closure | null $visible = null; - - protected bool | Closure | null $canSwitchPanel = true; - protected bool | Closure $isModalSlideOver = false; protected string | Closure | null $modalWidth = null; @@ -29,6 +23,8 @@ class PanelSwitch extends Component protected array | Closure $icons = []; + protected array | Closure $darkIcons = []; + protected int | Closure | null $iconSize = null; protected array | Closure $labels = []; @@ -47,24 +43,8 @@ public static function make(): static { $static = app(static::class); - $static->visible(function () use ($static) { - if (($user = auth()->user()) === null) { - return false; - } - - if (method_exists($user, 'canAccessPanel')) { - return $user->canAccessPanel($static->getCurrentPanel()); - } - - return true; - }); - $static->configure(); - if (count($static->getPanels()) < 2) { - $static->visible(false); - } - return $static; } @@ -84,6 +64,7 @@ public static function boot(): void return view('filament-panel-switch::panel-switch-menu', [ 'currentPanel' => $currentPanel, + 'darkIcons' => $static->getDarkIcons(), 'hasTopbar' => $currentPanel->hasTopbar(), 'heading' => $static->getModalHeading(), 'icons' => $static->getIcons(), @@ -99,23 +80,6 @@ public static function boot(): void ); } - public function canSwitchPanels(bool | Closure $condition): static - { - $this->canSwitchPanel = $condition; - - return $this; - } - - /** - * @deprecated Use `panels()` instead. - */ - public function excludes(array | Closure $panelIds): static - { - $this->excludes = $panelIds; - - return $this; - } - public function modalHeading(string | Closure $modalHeading): static { $this->modalHeading = $modalHeading; @@ -125,21 +89,19 @@ public function modalHeading(string | Closure $modalHeading): static public function icons(array | Closure $icons, bool $asImage = false): static { - if ($asImage) { - foreach ($icons as $key => $icon) { - if (! str($icon)->startsWith(['http://', 'https://'])) { - throw new \Exception('All icons must be URLs when $asImage is true.'); - } - } - } - $this->renderIconAsImage = $asImage; - $this->icons = $icons; return $this; } + public function darkIcons(array | Closure $darkIcons): static + { + $this->darkIcons = $darkIcons; + + return $this; + } + public function iconSize(int | Closure | null $size = null): static { $this->iconSize = $size; @@ -189,12 +151,6 @@ public function simple(bool | Closure $condition = true): static return $this; } - /** - * Whether to sort the panels by their order or not. - * 1. null - Default order, provided by the user through the `panels` method. - * 2. 'asc' - Ascending order - * 3. 'desc' - Descending order - */ public function sort(string $order = 'asc'): static { $this->sortOrder = $order; @@ -202,26 +158,31 @@ public function sort(string $order = 'asc'): static return $this; } - public function visible(bool | Closure $visible): static + public function getModalHeading(): string { - $this->visible = $visible; - - return $this; + return (string) $this->evaluate($this->modalHeading); } - public function getExcludes(): array + public function getIcons(): array { - return (array) $this->evaluate($this->excludes); - } + $icons = (array) $this->evaluate($this->icons); - public function getModalHeading(): string - { - return (string) $this->evaluate($this->modalHeading); + if ($this->renderIconAsImage) { + $this->ensureIconsAreUrls($icons); + } + + return $icons; } - public function getIcons(): array + public function getDarkIcons(): array { - return (array) $this->evaluate($this->icons); + $darkIcons = (array) $this->evaluate($this->darkIcons); + + if ($this->renderIconAsImage) { + $this->ensureIconsAreUrls($darkIcons); + } + + return $darkIcons; } public function getIconSize(): int @@ -241,7 +202,9 @@ public function getModalWidth(): string public function isAbleToSwitchPanels(): bool { - if (($user = auth()->user()) === null) { + $user = auth()?->user(); + + if (blank($user )) { return false; } @@ -249,7 +212,7 @@ public function isAbleToSwitchPanels(): bool return $user->canSwitchPanels(); } - return $this->evaluate($this->canSwitchPanel); + return true; } public function isModalSlideOver(): bool @@ -264,7 +227,7 @@ public function isSimple(): bool public function isVisible(): bool { - return (bool) $this->evaluate($this->visible); + return count($this->getPanels()) >= 2; } public function getSortOrder(): ?string @@ -277,7 +240,7 @@ public function getPanels(): array $panelIds = (array) $this->evaluate($this->panels); return collect(Filament::getPanels()) - ->reject(fn (Panel $panel) => in_array($panel->getId(), $this->getExcludes())) + ->filter($this->canUserAccessPanel(...)) ->when( value: filled($panelIds), callback: function ($panelCollection) use ($panelIds) { @@ -314,21 +277,39 @@ public function getCurrentPanel(): Panel public function getRenderHook(): string { - if ($this->renderHook !== null) { - return $this->renderHook; - } + return match (true) { + filled($this->renderHook) => $this->renderHook, + $this->getCurrentPanel()->hasTopbar() => PanelsRenderHook::GLOBAL_SEARCH_BEFORE, + default => PanelsRenderHook::USER_MENU_BEFORE + }; + } - $currentPanel = $this->getCurrentPanel(); + public function getRenderIconAsImage(): bool + { + return (bool) $this->evaluate($this->renderIconAsImage); + } - if ($currentPanel->hasTopbar()) { - return PanelsRenderHook::GLOBAL_SEARCH_BEFORE; + private function ensureIconsAreUrls(array $icons): void + { + foreach ($icons as $panel => $icon) { + if (! str($icon)->startsWith(['http://', 'https://'])) { + throw new \InvalidArgumentException("The icon for the [{$panel}] panel must be a URL starting with http:// or https://."); + } } - - return PanelsRenderHook::USER_MENU_BEFORE; } - public function getRenderIconAsImage(): bool + protected function canUserAccessPanel(Panel $panel): bool { - return $this->renderIconAsImage; + $user = auth()->user(); + + if (blank($user)) { + return false; + } + + if (method_exists($user, 'canAccessPanel')) { + return $user->canAccessPanel($panel); + } + + return true; } -} +} \ No newline at end of file From 54c43540acfa5a5190784f3b5cd4c5ce43a1b523 Mon Sep 17 00:00:00 2001 From: bezhanSalleh <10007504+bezhanSalleh@users.noreply.github.com> Date: Sun, 8 Feb 2026 06:02:57 +0000 Subject: [PATCH 2/2] Fix styling --- src/PanelSwitch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PanelSwitch.php b/src/PanelSwitch.php index 69ab77a..befb786 100644 --- a/src/PanelSwitch.php +++ b/src/PanelSwitch.php @@ -204,7 +204,7 @@ public function isAbleToSwitchPanels(): bool { $user = auth()?->user(); - if (blank($user )) { + if (blank($user)) { return false; }