Support dynamic navigation visibility (#19701)

* support dynamic navigation visibility

* clean up and test and docs

* simplify

---------

Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
Omar Alnabris
2026-04-20 15:26:23 +03:00
committed by GitHub
parent ecae6fcf8b
commit b8c80cdee8
3 changed files with 61 additions and 5 deletions
+13
View File
@@ -552,6 +552,19 @@ public function panel(Panel $panel): Panel
<AutoScreenshot name="panels/navigation/disabled-navigation" alt="Disabled navigation sidebar" version="4.x" />
Alternatively, you may pass a closure that returns a boolean to decide dynamically. Returning `false` hides the navigation, while returning `true` renders the default auto-discovered navigation items. This is useful for flows such as onboarding or setup wizards where the navigation should only appear once the user has reached a particular state:
```php
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->navigation(fn (): bool => auth()->user()->hasCompletedOnboarding());
}
```
### Disabling the topbar
You may disable topbar entirely by passing `false` to the `topbar()` method:
@@ -38,10 +38,9 @@ trait HasNavigation
*/
public function buildNavigation(): array
{
/** @var NavigationBuilder $builder */
$builder = app()->call($this->navigationBuilder);
$resolved = $this->resolveNavigationBuilder();
return $builder->getNavigation();
return $resolved instanceof NavigationBuilder ? $resolved->getNavigation() : [];
}
/**
@@ -98,12 +97,27 @@ trait HasNavigation
public function hasNavigation(): bool
{
return $this->navigationBuilder !== false;
return $this->resolveNavigationBuilder() !== false;
}
public function hasNavigationBuilder(): bool
{
return $this->navigationBuilder instanceof Closure;
return $this->resolveNavigationBuilder() instanceof NavigationBuilder;
}
protected function resolveNavigationBuilder(): NavigationBuilder | bool
{
if (! $this->navigationBuilder instanceof Closure) {
return $this->navigationBuilder;
}
$result = app()->call($this->navigationBuilder);
if ($result instanceof NavigationBuilder) {
return $result;
}
return (bool) $result;
}
/**
@@ -106,3 +106,32 @@ it('can register navigation groups individually', function (): void {
->each->toBeInstanceOf(NavigationItem::class),
);
});
it('hides navigation when a closure returns `false`', function (): void {
Filament::getCurrentOrDefaultPanel()->navigation(fn (): bool => false);
expect(Filament::hasNavigation())->toBeFalse();
});
it('shows auto-discovered navigation items when a closure returns `true`', function (): void {
Filament::getCurrentOrDefaultPanel()->navigation(fn (): bool => true);
expect(Filament::hasNavigation())->toBeTrue()
->and(Filament::getCurrentOrDefaultPanel()->hasNavigationBuilder())->toBeFalse();
$itemLabels = collect(Filament::getNavigation())
->flatMap(fn (NavigationGroup $group) => $group->getItems())
->map(fn (NavigationItem $item) => $item->getLabel());
expect($itemLabels)
->toContain('Dashboard')
->toContain('Users')
->toContain('Posts');
});
it('hides navigation when `navigation(false)` is set', function (): void {
Filament::getCurrentOrDefaultPanel()->navigation(false);
expect(Filament::hasNavigation())->toBeFalse();
});