From c5e48ff9039d5c743be4b1076a2f18717a39271a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:55:21 +0000 Subject: [PATCH 1/3] Initial plan From 6a03bb35a9bde1c5cae7841c16c6164838b52fd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:46:22 +0000 Subject: [PATCH 2/3] Re-add activateParents feature to Nav widget Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/Nav.php | 50 +++++++++++++++++++- tests/NavTest.php | 115 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/src/Nav.php b/src/Nav.php index b2155ce5..fc471dfb 100644 --- a/src/Nav.php +++ b/src/Nav.php @@ -62,6 +62,8 @@ final class Nav extends Widget private bool $activateItems = true; + private bool $activateParents = false; + private array $attributes = []; private array $cssClasses = []; @@ -103,6 +105,29 @@ public function activateItems(bool $enabled): self return $new; } + /** + * Whether to activate the parent dropdown item when one of its child items is active. + * + * When set to `true`, if a dropdown item's URL matches {@see currentPath}, the dropdown toggle (parent) will also + * receive the `active` CSS class. + * + * @param bool $enabled Whether to activate parent items. Defaults to `false`. + * + * @return self A new instance with the specified activate parents value. + * + * Example usage: + * ```php + * $nav->activateParents(true); + * ``` + */ + public function activateParents(bool $enabled): self + { + $new = clone $this; + $new->activateParents = $enabled; + + return $new; + } + /** * Adds a set of attributes. * @@ -591,6 +616,24 @@ private function isTabsOrPills(): bool || in_array(NavStyle::PILLS, $this->styleClasses, true); } + /** + * Checks whether any item in the dropdown is active. + * + * @param Dropdown $dropdown The dropdown to check. + * + * @return bool Whether any item in the dropdown is active. + */ + private function hasActiveDropdownItem(Dropdown $dropdown): bool + { + foreach ($dropdown->getItems() as $item) { + if ($item->isActive()) { + return true; + } + } + + return false; + } + /** * Renders the items for the nav component. * @@ -621,6 +664,11 @@ private function renderItems(): array private function renderItemsDropdown(Dropdown $items): Li { $dropDownItems = $this->isDropdownActive($items); + $togglerClasses = ['nav-link', 'dropdown-toggle']; + + if ($this->activateParents && $this->hasActiveDropdownItem($dropDownItems)) { + $togglerClasses[] = self::NAV_LINK_ACTIVE_CLASS; + } return Li::tag() ->addClass(...$this->dropdownCssClasses, ...$dropDownItems->getCssClasses()) @@ -629,7 +677,7 @@ private function renderItemsDropdown(Dropdown $items): Li $dropDownItems ->container(false) ->togglerAsLink() - ->togglerClass('nav-link', 'dropdown-toggle') + ->togglerClass(...$togglerClasses) ->render(), "\n", ) diff --git a/tests/NavTest.php b/tests/NavTest.php index 6a6c3c10..719dc936 100644 --- a/tests/NavTest.php +++ b/tests/NavTest.php @@ -22,6 +22,120 @@ #[Group('nav')] final class NavTest extends TestCase { + public function testActivateParents(): void + { + Assert::equalsWithoutLE( + << + + + + + + HTML, + Nav::widget() + ->activateParents(true) + ->currentPath('/test/link/another-action') + ->items( + NavLink::to('Active', '/test'), + Dropdown::widget() + ->items( + DropdownItem::link('Action', '/test/link/action'), + DropdownItem::link('Another action', '/test/link/another-action'), + DropdownItem::link('Something else here', '/test/link/something-else'), + DropdownItem::divider(), + DropdownItem::link('Separated link', '/test/link/separated-link'), + ) + ->togglerContent('Dropdown'), + NavLink::to('Link', '/test/link'), + NavLink::to('Disabled', '/test/disabled', disabled: true), + ) + ->render(), + ); + } + + public function testActivateParentsWithFalseValue(): void + { + Assert::equalsWithoutLE( + << + + + + + + HTML, + Nav::widget() + ->activateParents(false) + ->currentPath('/test/link/another-action') + ->items( + NavLink::to('Active', '/test'), + Dropdown::widget() + ->items( + DropdownItem::link('Action', '/test/link/action'), + DropdownItem::link('Another action', '/test/link/another-action'), + DropdownItem::link('Something else here', '/test/link/something-else'), + DropdownItem::divider(), + DropdownItem::link('Separated link', '/test/link/separated-link'), + ) + ->togglerContent('Dropdown'), + NavLink::to('Link', '/test/link'), + NavLink::to('Disabled', '/test/disabled', disabled: true), + ) + ->render(), + ); + } + public function testAddAttributes(): void { Assert::equalsWithoutLE( @@ -841,6 +955,7 @@ public function testImmutability(): void $navWidget = Nav::widget(); $this->assertNotSame($navWidget, $navWidget->activateItems(false)); + $this->assertNotSame($navWidget, $navWidget->activateParents(true)); $this->assertNotSame($navWidget, $navWidget->addAttributes([])); $this->assertNotSame($navWidget, $navWidget->addClass('')); $this->assertNotSame($navWidget, $navWidget->addCssStyle('')); From 5c1842b9ff7ecefb4ed4e07c42d2b5a7809cc264 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Thu, 5 Mar 2026 23:12:17 +0300 Subject: [PATCH 3/3] Add a line for CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d398694..c25721e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Enh #298: Update Bootstrap from 5.0.0-beta to 5.3.8 (@rossaddison) - Bug #299: Link buttons get `btn-secondary` class instead of `btn-link` class (@jdanhutch) - Enh #290: Add `visible` to `Dropdown` and `DropdownItem` (@samdark) +- Enh #286: Add `Nav::activateParents()` that makes parent dropdown active when one of its child items is active (@samdark) ## 1.0.0 April 13, 2025