mirror of
https://github.com/filamentphp/filament.git
synced 2026-10-16 15:42:48 +08:00
feat: Add strict hierarchical breadcrumbs option (#20153)
* Add strict hierarchical option to breadcrumbs method Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Update Page.php Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Update Page.php Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Update Page.php Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Add strict hierarchical breadcrumbs documentation Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Implement getClusterHierarchicalBreadcrumbs method Add method to retrieve hierarchical breadcrumbs for clusters. Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Update Page.php Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Update Page.php Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> * Add strict hierarchical breadcrumb coverage * Improve navigation hierarchy breadcrumbs * Refine custom page breadcrumb defaults * Support clustered resource record breadcrumbs --------- Signed-off-by: ahmed-rashad-alnaggar <131385452+ahmed-rashad-alnaggar@users.noreply.github.com> Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
co-authored by
Dan Harrin
parent
5f1a0a6341
commit
b986eee58f
@@ -639,6 +639,40 @@ public function panel(Panel $panel): Panel
|
||||
}
|
||||
```
|
||||
|
||||
## Adding a breadcrumb to a custom page
|
||||
|
||||
Custom pages do not include a breadcrumb for the current page by default. You may add one using the `$breadcrumb` property:
|
||||
|
||||
```php
|
||||
use Filament\Pages\Page;
|
||||
|
||||
class Settings extends Page
|
||||
{
|
||||
protected static ?string $breadcrumb = 'Settings';
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Including the navigation hierarchy in breadcrumbs
|
||||
|
||||
By default, breadcrumbs for a page are built from its resource and parent record relationships. For example, an `EditRecord` page's breadcrumbs are derived from its resource's list and view pages, and any parent resources in the case of nested resources.
|
||||
|
||||
If you would also like breadcrumbs to reflect the full navigation hierarchy of a page, including its [cluster](clusters), [navigation group](#grouping-navigation-items), and [navigation parent item](#grouping-navigation-items-under-other-items), you can enable this behavior in your [configuration](../panel-configuration):
|
||||
|
||||
```php
|
||||
use Filament\Panel;
|
||||
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
// ...
|
||||
->breadcrumbs(hasNavigationHierarchy: true);
|
||||
}
|
||||
```
|
||||
|
||||
When enabled, the navigation hierarchy is prepended to the page's existing breadcrumbs. If a custom page does not have a `$breadcrumb`, its title is used as the current breadcrumb when its navigation hierarchy can be resolved. If the page is not present in the navigation, its existing breadcrumbs are used instead.
|
||||
|
||||
## Reloading the sidebar and topbar
|
||||
|
||||
Once a page in the panel is loaded, the sidebar and topbar are not reloaded until you navigate away from the page, or until a menu item is clicked to trigger an action. You can manually reload these components to update them by dispatching a `refresh-sidebar` or `refresh-topbar` browser event.
|
||||
|
||||
@@ -9,6 +9,7 @@ use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Clusters\Cluster;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Filament\Panel;
|
||||
use Filament\Schemas\Components\Component;
|
||||
@@ -45,6 +46,8 @@ abstract class Page extends BasePage
|
||||
*/
|
||||
protected static ?string $cluster = null;
|
||||
|
||||
protected static ?string $breadcrumb = null;
|
||||
|
||||
protected static bool $isDiscovered = true;
|
||||
|
||||
/**
|
||||
@@ -179,16 +182,175 @@ abstract class Page extends BasePage
|
||||
return $panel->generateRouteName($routeName);
|
||||
}
|
||||
|
||||
public function getBreadcrumb(): ?string
|
||||
{
|
||||
return static::$breadcrumb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
* @return array<string | Htmlable>
|
||||
*/
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
if (filled($cluster = static::getCluster())) {
|
||||
return $cluster::unshiftClusterBreadcrumbs([]);
|
||||
$breadcrumb = $this->getBreadcrumb();
|
||||
|
||||
if (Filament::getCurrentOrDefaultPanel()->hasNavigationHierarchyInBreadcrumbs()) {
|
||||
$navigationHierarchyBreadcrumbs = $this->getNavigationHierarchyBreadcrumbs();
|
||||
|
||||
if ($navigationHierarchyBreadcrumbs !== null) {
|
||||
return [
|
||||
...$navigationHierarchyBreadcrumbs,
|
||||
$breadcrumb ?? $this->getTitle(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
$breadcrumbs = filled($cluster = static::getCluster())
|
||||
? $cluster::unshiftClusterBreadcrumbs([])
|
||||
: [];
|
||||
|
||||
if ($breadcrumb !== null) {
|
||||
$breadcrumbs[] = $breadcrumb;
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string> | null
|
||||
*/
|
||||
protected function getNavigationHierarchyBreadcrumbs(): ?array
|
||||
{
|
||||
if (filled($cluster = static::getCluster())) {
|
||||
$clusterBreadcrumbs = $this->getNavigationBreadcrumbs(
|
||||
$cluster,
|
||||
static fn (): string => $cluster::getNavigationUrl(),
|
||||
);
|
||||
$pageBreadcrumbs = $this->getNavigationBreadcrumbs(
|
||||
$this->getSubNavigationBreadcrumbItemKey(),
|
||||
fn (): ?string => $this->getSubNavigationBreadcrumbItemUrl(),
|
||||
$this->getCachedSubNavigation(),
|
||||
);
|
||||
|
||||
if (($clusterBreadcrumbs === null) || ($pageBreadcrumbs === null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
...$clusterBreadcrumbs,
|
||||
$cluster::getUrl() => $cluster::getClusterBreadcrumb(),
|
||||
...$pageBreadcrumbs,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->getNavigationBreadcrumbs(
|
||||
$this->getNavigationBreadcrumbItemKey(),
|
||||
fn (): ?string => $this->getNavigationBreadcrumbItemUrl(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getNavigationBreadcrumbItemKey(): string
|
||||
{
|
||||
return static::class;
|
||||
}
|
||||
|
||||
protected function getNavigationBreadcrumbItemUrl(): ?string
|
||||
{
|
||||
if (! static::shouldRegisterNavigation()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::getNavigationUrl();
|
||||
}
|
||||
|
||||
protected function getSubNavigationBreadcrumbItemKey(): string
|
||||
{
|
||||
return $this->getNavigationBreadcrumbItemKey();
|
||||
}
|
||||
|
||||
protected function getSubNavigationBreadcrumbItemUrl(): ?string
|
||||
{
|
||||
return $this->getNavigationBreadcrumbItemUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure(): ?string $navigationItemUrl
|
||||
* @param ?array<NavigationGroup> $navigation
|
||||
* @return array<string> | null
|
||||
*/
|
||||
protected function getNavigationBreadcrumbs(string $navigationItemKey, Closure $navigationItemUrl, ?array $navigation = null): ?array
|
||||
{
|
||||
if ($navigation === null) {
|
||||
$panel = Filament::getCurrentOrDefaultPanel();
|
||||
|
||||
if (! $panel->hasNavigation()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$navigation = $panel->getNavigation();
|
||||
}
|
||||
|
||||
foreach ($navigation as $navigationGroup) {
|
||||
$breadcrumbs = $this->getNavigationItemBreadcrumbs(
|
||||
$navigationItemKey,
|
||||
$navigationItemUrl,
|
||||
$navigationGroup->getItems(),
|
||||
);
|
||||
|
||||
if ($breadcrumbs === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filled($navigationGroupLabel = $navigationGroup->getLabel())) {
|
||||
$breadcrumbs = [
|
||||
$navigationGroupLabel,
|
||||
...$breadcrumbs,
|
||||
];
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure(): ?string $navigationItemUrl
|
||||
* @param iterable<NavigationItem> $navigationItems
|
||||
* @return array<string> | null
|
||||
*/
|
||||
protected function getNavigationItemBreadcrumbs(string $navigationItemKey, Closure $navigationItemUrl, iterable $navigationItems): ?array
|
||||
{
|
||||
foreach ($navigationItems as $navigationItem) {
|
||||
if ($navigationItem->getKey() === $navigationItemKey) {
|
||||
$resolvedNavigationItemUrl = $navigationItemUrl();
|
||||
|
||||
if (filled($resolvedNavigationItemUrl) && ($navigationItem->getUrl() === $resolvedNavigationItemUrl)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
$breadcrumbs = $this->getNavigationItemBreadcrumbs(
|
||||
$navigationItemKey,
|
||||
$navigationItemUrl,
|
||||
$navigationItem->getChildItems(),
|
||||
);
|
||||
|
||||
if ($breadcrumbs === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$navigationItemUrl = $navigationItem->getUrl();
|
||||
|
||||
return [
|
||||
...(filled($navigationItemUrl)
|
||||
? [$navigationItemUrl => $navigationItem->getLabel()]
|
||||
: [$navigationItem->getLabel()]),
|
||||
...$breadcrumbs,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): string | UnitEnum | null
|
||||
|
||||
@@ -8,9 +8,12 @@ trait HasBreadcrumbs
|
||||
{
|
||||
protected bool | Closure $hasBreadcrumbs = true;
|
||||
|
||||
public function breadcrumbs(bool | Closure $condition = true): static
|
||||
protected bool | Closure $hasNavigationHierarchyInBreadcrumbs = false;
|
||||
|
||||
public function breadcrumbs(bool | Closure $condition = true, bool | Closure $hasNavigationHierarchy = false): static
|
||||
{
|
||||
$this->hasBreadcrumbs = $condition;
|
||||
$this->hasNavigationHierarchyInBreadcrumbs = $hasNavigationHierarchy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -19,4 +22,9 @@ trait HasBreadcrumbs
|
||||
{
|
||||
return (bool) $this->evaluate($this->hasBreadcrumbs);
|
||||
}
|
||||
|
||||
public function hasNavigationHierarchyInBreadcrumbs(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->hasNavigationHierarchyInBreadcrumbs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,6 @@ abstract class Page extends BasePage
|
||||
use CanAuthorizeResourceAccess;
|
||||
use InteractsWithParentRecord;
|
||||
|
||||
protected static ?string $breadcrumb = null;
|
||||
|
||||
protected static string $resource;
|
||||
|
||||
protected static bool $isDiscovered = false;
|
||||
@@ -227,6 +225,17 @@ abstract class Page extends BasePage
|
||||
}
|
||||
}
|
||||
|
||||
if (Filament::getCurrentOrDefaultPanel()->hasNavigationHierarchyInBreadcrumbs()) {
|
||||
$navigationHierarchyBreadcrumbs = $this->getNavigationHierarchyBreadcrumbs();
|
||||
|
||||
if ($navigationHierarchyBreadcrumbs !== null) {
|
||||
return [
|
||||
...$navigationHierarchyBreadcrumbs,
|
||||
...$breadcrumbs,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (filled($cluster = static::getCluster())) {
|
||||
return $cluster::unshiftClusterBreadcrumbs($breadcrumbs);
|
||||
}
|
||||
@@ -245,6 +254,42 @@ abstract class Page extends BasePage
|
||||
];
|
||||
}
|
||||
|
||||
protected function getNavigationBreadcrumbItemKey(): string
|
||||
{
|
||||
return static::getResource();
|
||||
}
|
||||
|
||||
protected function getNavigationBreadcrumbItemUrl(): ?string
|
||||
{
|
||||
$resource = static::getResource();
|
||||
|
||||
if ((! $resource::shouldRegisterNavigation()) || $resource::getParentResourceRegistration()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $resource::getNavigationUrl();
|
||||
}
|
||||
|
||||
protected function getSubNavigationBreadcrumbItemKey(): string
|
||||
{
|
||||
return $this->getSubNavigationParameters() ? static::class : parent::getSubNavigationBreadcrumbItemKey();
|
||||
}
|
||||
|
||||
protected function getSubNavigationBreadcrumbItemUrl(): ?string
|
||||
{
|
||||
$parameters = $this->getSubNavigationParameters();
|
||||
|
||||
if (! $parameters) {
|
||||
return parent::getSubNavigationBreadcrumbItemUrl();
|
||||
}
|
||||
|
||||
if (! static::shouldRegisterNavigation($parameters)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::getNavigationUrl($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $parameters
|
||||
*/
|
||||
|
||||
@@ -4,10 +4,30 @@ use Filament\Facades\Filament;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Filament\Tests\Fixtures\Pages\ConfigurableSettings;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\ConfigurablePostResource;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\Pages\ListConfigurablePosts;
|
||||
use Filament\Tests\Panels\Configuration\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
it('includes the navigation hierarchy in breadcrumbs for the active page and resource configurations', function (): void {
|
||||
Filament::getCurrentOrDefaultPanel()->breadcrumbs(hasNavigationHierarchy: true);
|
||||
|
||||
Filament::forPageConfiguration(ConfigurableSettings::class, 'general');
|
||||
|
||||
expect(app(ConfigurableSettings::class)->getBreadcrumbs())->toBe([
|
||||
'Settings',
|
||||
'Configurable Settings',
|
||||
]);
|
||||
|
||||
Filament::setCurrentPageConfigurationKey(null);
|
||||
Filament::forResourceConfiguration(ConfigurablePostResource::class, 'archived');
|
||||
|
||||
expect(app(ListConfigurablePosts::class)->getResourceBreadcrumbs())->toBe([
|
||||
'Archive',
|
||||
ConfigurablePostResource::getUrl() => 'Posts',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('resource configuration navigation', function (): void {
|
||||
it('registers separate navigation items for each resource configuration', function (): void {
|
||||
$navigation = Filament::getNavigation();
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Navigation\NavigationBuilder;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Resources\Pages\Page as ResourcePage;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tests\Fixtures\Clusters\UserManagement;
|
||||
use Filament\Tests\Fixtures\Clusters\UserManagement\Pages\ManageAdmins;
|
||||
use Filament\Tests\Fixtures\Clusters\WithoutSubNavigationCluster;
|
||||
use Filament\Tests\Fixtures\Clusters\WithoutSubNavigationCluster\Pages\ClusteredPageWithoutSubNavigation;
|
||||
use Filament\Tests\Fixtures\Pages\Settings;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\Pages\ListPosts;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\PostResource;
|
||||
use Filament\Tests\Panels\Pages\TestCase;
|
||||
|
||||
use function Filament\Tests\livewire;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
afterEach(function (): void {
|
||||
PostResource::navigationGroup('Blog');
|
||||
PostResource::navigationParentItem(null);
|
||||
UserManagement::navigationGroup(null);
|
||||
UserManagement::navigationParentItem(null);
|
||||
WithoutSubNavigationCluster::navigationGroup(null);
|
||||
});
|
||||
|
||||
it('can configure the navigation hierarchy in breadcrumbs with a `Closure`', function (): void {
|
||||
$panel = Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(false, hasNavigationHierarchy: fn (): bool => true);
|
||||
|
||||
expect($panel->hasBreadcrumbs())->toBeFalse()
|
||||
->and($panel->hasNavigationHierarchyInBreadcrumbs())->toBeTrue();
|
||||
});
|
||||
|
||||
it('preserves existing page and resource breadcrumbs when the navigation hierarchy is not included', function (): void {
|
||||
expect(app(Settings::class)->getBreadcrumbs())->toBe([])
|
||||
->and(app(ListPosts::class)->getResourceBreadcrumbs())->toBe([
|
||||
PostResource::getUrl() => 'Posts',
|
||||
]);
|
||||
});
|
||||
|
||||
it('includes an explicitly configured custom page breadcrumb without the navigation hierarchy', function (): void {
|
||||
expect(app(PageWithBreadcrumb::class)->getBreadcrumbs())->toBe([
|
||||
'Custom breadcrumb',
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves the title fallback for resource page breadcrumbs', function (): void {
|
||||
expect(app(ResourcePageWithTitle::class)->getBreadcrumb())->toBe('Resource page title');
|
||||
});
|
||||
|
||||
it('includes the navigation hierarchy in breadcrumbs for a grouped page with a parent item', function (): void {
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigationItems([
|
||||
NavigationItem::make('Administration home')
|
||||
->group('Administration')
|
||||
->icon(Heroicon::OutlinedHome)
|
||||
->url('/administration'),
|
||||
...GroupedBreadcrumbsPage::getNavigationItems(),
|
||||
]);
|
||||
|
||||
expect(app(GroupedBreadcrumbsPage::class)->getBreadcrumbs())->toBe([
|
||||
'Administration',
|
||||
'/administration' => 'Administration home',
|
||||
'Reports',
|
||||
]);
|
||||
|
||||
livewire(GroupedBreadcrumbsPage::class)
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
it('includes the navigation hierarchy in breadcrumbs for a grouped resource page with a parent item', function (): void {
|
||||
PostResource::navigationParentItem('Blog home');
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigationItems([
|
||||
NavigationItem::make('Blog home')
|
||||
->group('Blog')
|
||||
->icon(Heroicon::OutlinedHome)
|
||||
->url('/blog'),
|
||||
]);
|
||||
|
||||
expect(app(ListPosts::class)->getResourceBreadcrumbs())->toBe([
|
||||
'Blog',
|
||||
'/blog' => 'Blog home',
|
||||
PostResource::getUrl() => 'Posts',
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders the navigation hierarchy in breadcrumbs', function (): void {
|
||||
PostResource::navigationParentItem('Blog home');
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigationItems([
|
||||
NavigationItem::make('Blog home')
|
||||
->group('Blog')
|
||||
->icon(Heroicon::OutlinedHome)
|
||||
->url('/blog'),
|
||||
]);
|
||||
|
||||
visit(PostResource::getUrl())
|
||||
->assertScript(<<<'JS'
|
||||
Array.from(document.querySelectorAll('.fi-breadcrumbs-item-label')).map((item) => [
|
||||
item.textContent.trim(),
|
||||
item.href ? new URL(item.href).pathname : null,
|
||||
])
|
||||
JS, [
|
||||
['Blog', null],
|
||||
['Blog home', '/blog'],
|
||||
['Posts', '/posts'],
|
||||
['List', null],
|
||||
])
|
||||
->assertNoAccessibilityIssues();
|
||||
|
||||
visit(PostResource::getUrl())
|
||||
->inDarkMode()
|
||||
->assertNoAccessibilityIssues();
|
||||
});
|
||||
|
||||
it('includes the navigation hierarchy in breadcrumbs for clustered pages', function (): void {
|
||||
UserManagement::navigationGroup('Administration');
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true);
|
||||
|
||||
expect(app(ManageAdmins::class)->getBreadcrumbs())->toBe([
|
||||
'Administration',
|
||||
UserManagement::getUrl() => 'User Management',
|
||||
'User Management',
|
||||
'Manage Admins',
|
||||
]);
|
||||
});
|
||||
|
||||
it('includes the navigation hierarchy in breadcrumbs for clustered resource record pages with sub-navigation', function (): void {
|
||||
UserManagement::navigationGroup('Administration');
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true);
|
||||
|
||||
expect(app(ClusteredResourceRecordPage::class)->getBreadcrumbs())->toBe([
|
||||
'Administration',
|
||||
UserManagement::getUrl() => 'User Management',
|
||||
'Record',
|
||||
PostResource::getUrl() => 'Posts',
|
||||
'Edit post',
|
||||
]);
|
||||
});
|
||||
|
||||
it('falls back to cluster breadcrumbs when cluster sub-navigation is disabled', function (): void {
|
||||
WithoutSubNavigationCluster::navigationGroup('Administration');
|
||||
|
||||
$breadcrumbs = app(ClusteredPageWithoutSubNavigation::class)->getBreadcrumbs();
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()->breadcrumbs(hasNavigationHierarchy: true);
|
||||
|
||||
expect(app(ClusteredPageWithoutSubNavigation::class)->getBreadcrumbs())->toBe($breadcrumbs);
|
||||
|
||||
$this->get(ClusteredPageWithoutSubNavigation::getUrl())
|
||||
->assertSuccessful();
|
||||
});
|
||||
|
||||
it('falls back to cluster breadcrumbs when a custom `NavigationBuilder` omits the cluster', function (): void {
|
||||
$breadcrumbs = app(ManageAdmins::class)->getBreadcrumbs();
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigation(fn (NavigationBuilder $navigation): NavigationBuilder => $navigation);
|
||||
|
||||
expect(app(ManageAdmins::class)->getBreadcrumbs())->toBe($breadcrumbs);
|
||||
});
|
||||
|
||||
it('falls back to existing breadcrumbs when a custom `NavigationBuilder` omits the page hierarchy', function (): void {
|
||||
$breadcrumbs = app(GroupedBreadcrumbsPage::class)->getBreadcrumbs();
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigation(fn (NavigationBuilder $navigation): NavigationBuilder => $navigation);
|
||||
|
||||
expect(app(GroupedBreadcrumbsPage::class)->getBreadcrumbs())->toBe($breadcrumbs);
|
||||
});
|
||||
|
||||
it('includes the navigation hierarchy in breadcrumbs from a custom `NavigationBuilder`', function (): void {
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigation(fn (NavigationBuilder $navigation): NavigationBuilder => $navigation
|
||||
->group(NavigationGroup::make('Administration')
|
||||
->items(GroupedBreadcrumbsPage::getNavigationItems())));
|
||||
|
||||
expect(app(GroupedBreadcrumbsPage::class)->getBreadcrumbs())->toBe([
|
||||
'Administration',
|
||||
'Reports',
|
||||
]);
|
||||
});
|
||||
|
||||
it('falls back to existing breadcrumbs for a grouped page that does not register navigation', function (): void {
|
||||
$breadcrumbs = app(GroupedUnregisteredBreadcrumbsPage::class)->getBreadcrumbs();
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigationItems([
|
||||
NavigationItem::make('Published reports')
|
||||
->key(GroupedUnregisteredBreadcrumbsPage::class)
|
||||
->group('Administration')
|
||||
->icon(Heroicon::OutlinedDocumentText)
|
||||
->childItems([
|
||||
NavigationItem::make('Published report')
|
||||
->url('/published-reports'),
|
||||
]),
|
||||
]);
|
||||
|
||||
expect(app(GroupedUnregisteredBreadcrumbsPage::class)->getBreadcrumbs())->toBe($breadcrumbs);
|
||||
});
|
||||
|
||||
it('falls back to existing breadcrumbs when navigation is disabled', function (): void {
|
||||
$breadcrumbs = app(GroupedBreadcrumbsPage::class)->getBreadcrumbs();
|
||||
|
||||
Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true)
|
||||
->navigationItems([
|
||||
NavigationItem::make('Administration home')
|
||||
->group('Administration')
|
||||
->icon(Heroicon::OutlinedHome)
|
||||
->url('/administration'),
|
||||
...GroupedBreadcrumbsPage::getNavigationItems(),
|
||||
])
|
||||
->navigation(false);
|
||||
|
||||
expect(app(GroupedBreadcrumbsPage::class)->getBreadcrumbs())->toBe($breadcrumbs);
|
||||
});
|
||||
|
||||
class GroupedBreadcrumbsPage extends Page
|
||||
{
|
||||
protected static string | UnitEnum | null $navigationGroup = 'Administration';
|
||||
|
||||
protected static ?string $navigationParentItem = 'Administration home';
|
||||
|
||||
protected static ?string $title = 'Reports';
|
||||
|
||||
protected string $view = 'filament-panels::pages.page';
|
||||
|
||||
public static function getNavigationUrl(): string
|
||||
{
|
||||
return '/reports';
|
||||
}
|
||||
}
|
||||
|
||||
class PageWithBreadcrumb extends Page
|
||||
{
|
||||
protected static ?string $breadcrumb = 'Custom breadcrumb';
|
||||
|
||||
protected string $view = 'filament-panels::pages.page';
|
||||
}
|
||||
|
||||
class ResourcePageWithTitle extends ResourcePage
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
|
||||
protected static ?string $title = 'Resource page title';
|
||||
}
|
||||
|
||||
class ClusteredResourceRecordPage extends ResourcePage
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
|
||||
protected static ?string $title = 'Edit post';
|
||||
|
||||
public static function getCluster(): ?string
|
||||
{
|
||||
return UserManagement::class;
|
||||
}
|
||||
|
||||
public function getSubNavigation(): array
|
||||
{
|
||||
return [
|
||||
NavigationItem::make('Edit post')
|
||||
->key(static::class)
|
||||
->group('Record')
|
||||
->url('/posts/1/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getSubNavigationParameters(): array
|
||||
{
|
||||
return ['record' => 1];
|
||||
}
|
||||
|
||||
public static function getNavigationUrl(array $parameters = []): string
|
||||
{
|
||||
return "/posts/{$parameters['record']}/edit";
|
||||
}
|
||||
}
|
||||
|
||||
class GroupedUnregisteredBreadcrumbsPage extends Page
|
||||
{
|
||||
protected static string | UnitEnum | null $navigationGroup = 'Administration';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Archived reports';
|
||||
|
||||
protected string $view = 'filament-panels::pages.page';
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Tests\Fixtures\Models\Company;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Filament\Tests\Fixtures\Models\Team;
|
||||
@@ -25,6 +26,22 @@ use function Pest\Laravel\assertSoftDeleted;
|
||||
uses(TestCase::class);
|
||||
|
||||
describe('soft-deletable nested resource', function (): void {
|
||||
it('can render with the navigation hierarchy in breadcrumbs regardless of panel navigation visibility', function (): void {
|
||||
$parentRecord = User::factory()->create();
|
||||
$url = UserPostResource::getUrl('index', [
|
||||
'author' => $parentRecord,
|
||||
]);
|
||||
|
||||
$panel = Filament::getCurrentOrDefaultPanel()
|
||||
->breadcrumbs(hasNavigationHierarchy: true);
|
||||
|
||||
$this->get($url)->assertSuccessful();
|
||||
|
||||
$panel->navigation(false);
|
||||
|
||||
$this->get($url)->assertSuccessful();
|
||||
});
|
||||
|
||||
it('can render list page', function (): void {
|
||||
$parentRecord = User::factory()->create();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user