Skip to content

Commit 5cfd158

Browse files
authored
Active state for submenu header (#124)
1 parent dd1e138 commit 5cfd158

File tree

3 files changed

+144
-18
lines changed

3 files changed

+144
-18
lines changed

src/Menu.php

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Menu implements Item, Countable, HasHtmlAttributes, HasParentAttributes, I
2727
/** @var array */
2828
protected $filters = [];
2929

30-
/** @var string */
30+
/** @var string|Item */
3131
protected $prepend;
3232
protected $append = '';
3333

@@ -232,7 +232,6 @@ public function submenu($header, $menu = null)
232232
[$header, $menu] = $this->parseSubmenuArgs(func_get_args());
233233

234234
$menu = $this->createSubmenuMenu($menu);
235-
$header = $this->createSubmenuHeader($header);
236235

237236
return $this->add($menu->prependIf($header, $header));
238237
}
@@ -395,6 +394,10 @@ public function isActive(): bool
395394
}
396395
}
397396

397+
if ($this->prepend && $this->prepend instanceof Item && $this->prepend->isActive()) {
398+
return true;
399+
}
400+
398401
return false;
399402
}
400403

@@ -405,6 +408,10 @@ public function isActive(): bool
405408
*/
406409
public function isExactActive(): bool
407410
{
411+
if ($this->prepend && $this->prepend instanceof Item && $this->prepend->isExactActive()) {
412+
return true;
413+
}
414+
408415
return false;
409416
}
410417

@@ -466,6 +473,10 @@ public function setActiveFromUrl(string $url, string $root = '/')
466473
$menu->setActiveFromUrl($url, $root);
467474
});
468475

476+
if ($this->prepend && $this->prepend instanceof Item) {
477+
$this->prepend->determineActiveForUrl($url, $root);
478+
}
479+
469480
$this->applyToAll(function (Activatable $item) use ($url, $root) {
470481
$item->determineActiveForUrl($url, $root);
471482
});
@@ -701,6 +712,10 @@ public function render(): string
701712

702713
$wrappedContents = $tag ? $tag->withContents($contents) : implode('', $contents);
703714

715+
if ($this->prepend instanceof Item && $this->prepend->isActive()) {
716+
$this->prepend = $this->renderActiveClassOnLink($this->prepend);
717+
}
718+
704719
$menu = $this->prepend.$wrappedContents.$this->append;
705720

706721
if (! empty($this->wrap)) {
@@ -732,14 +747,7 @@ protected function renderItem(Item $item): string
732747
}
733748
}
734749

735-
if ($this->activeClassOnLink && $item instanceof HasHtmlAttributes) {
736-
$item->addClass($this->activeClass);
737-
738-
/** @psalm-suppress UndefinedInterfaceMethod */
739-
if ($item->isExactActive()) {
740-
$item->addClass($this->exactActiveClass);
741-
}
742-
}
750+
$item = $this->renderActiveClassOnLink($item);
743751
}
744752

745753
if ($item instanceof HasParentAttributes) {
@@ -753,6 +761,24 @@ protected function renderItem(Item $item): string
753761
return Tag::make($this->parentTagName, $attributes)->withContents($item->render());
754762
}
755763

764+
/**
765+
* @param Item $item
766+
*/
767+
protected function renderActiveClassOnLink(Item $item): Item
768+
{
769+
if ($this->activeClassOnLink && $item instanceof HasHtmlAttributes && ! $item instanceof Menu) {
770+
$item->addClass($this->activeClass);
771+
772+
/** @psalm-suppress UndefinedInterfaceMethod */
773+
if ($item->isExactActive()) {
774+
$item->addClass($this->exactActiveClass);
775+
}
776+
}
777+
778+
return $item;
779+
}
780+
781+
756782
/**
757783
* The amount of items in the menu.
758784
*

src/Traits/HasTextAttributes.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

33
namespace Spatie\Menu\Traits;
44

5+
use Spatie\Menu\Item;
6+
57
trait HasTextAttributes
68
{
79
/**
810
* Prepend the anchor with a string of html on render.
911
*
10-
* @param string $prepend
12+
* @param string|Item $prepend
1113
*
1214
* @return $this
1315
*/
14-
public function prepend(string $prepend)
16+
public function prepend($prepend)
1517
{
1618
$this->prepend = $prepend;
1719

@@ -23,11 +25,11 @@ public function prepend(string $prepend)
2325
* met.
2426
*
2527
* @param mixed $condition
26-
* @param string $prepend
28+
* @param string|Item $prepend
2729
*
2830
* @return $this
2931
*/
30-
public function prependIf($condition, string $prepend)
32+
public function prependIf($condition, $prepend)
3133
{
3234
if ($this->resolveCondition($condition)) {
3335
return $this->prepend($prepend);
@@ -39,11 +41,11 @@ public function prependIf($condition, string $prepend)
3941
/**
4042
* Append a text of html to the menu on render.
4143
*
42-
* @param string $append
44+
* @param string|Item $append
4345
*
4446
* @return $this
4547
*/
46-
public function append(string $append)
48+
public function append($append)
4749
{
4850
$this->append = $append;
4951

@@ -55,11 +57,11 @@ public function append(string $append)
5557
* met.
5658
*
5759
* @param bool $condition
58-
* @param string $append
60+
* @param string|Item $append
5961
*
6062
* @return static
6163
*/
62-
public function appendIf($condition, string $append)
64+
public function appendIf($condition, $append)
6365
{
6466
if ($this->resolveCondition($condition)) {
6567
return $this->append($append);

tests/MenuSetActiveTest.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,104 @@ public function it_can_set_items_exact_active()
4343
');
4444
}
4545

46+
/** @test */
47+
public function it_can_set_exact_active_for_submenu_header()
48+
{
49+
$this->menu = Menu::new()
50+
->link('/', 'Home')
51+
->submenu(Link::to('/people', 'People'), Menu::new()->link('/people/sebastian', 'Sebastian'))
52+
->setActive('/people');
53+
54+
$this->assertRenders('
55+
<ul>
56+
<li><a href="/">Home</a></li>
57+
<li class="active exact-active">
58+
<a href="/people">People</a>
59+
<ul>
60+
<li><a href="/people/sebastian">Sebastian</a></li>
61+
</ul>
62+
</li>
63+
</ul>
64+
');
65+
}
66+
67+
/** @test */
68+
public function it_can_set_active_for_submenu_header()
69+
{
70+
$this->menu = Menu::new()
71+
->link('/', 'Home')
72+
->submenu(Link::to('/people', 'People'), Menu::new()->link('/people/sebastian', 'Sebastian'))
73+
->setActive('/people/sebastian');
74+
75+
$this->assertRenders('
76+
<ul>
77+
<li><a href="/">Home</a></li>
78+
<li class="active">
79+
<a href="/people">People</a>
80+
<ul>
81+
<li class="active exact-active"><a href="/people/sebastian">Sebastian</a></li>
82+
</ul>
83+
</li>
84+
</ul>
85+
');
86+
}
87+
88+
/** @test */
89+
public function it_can_set_link_exact_active_for_submenu_header()
90+
{
91+
$submenu = Menu::new()
92+
->link('/people/sebastian', 'Sebastian')
93+
->setActiveClassOnLink(true)
94+
->setActiveClassOnParent(false);
95+
96+
$this->menu = Menu::new()
97+
->link('/', 'Home')
98+
->submenu(Link::to('/people', 'People'), $submenu)
99+
->setActive('/people')
100+
->setActiveClassOnLink(true)
101+
->setActiveClassOnParent(false);
102+
103+
$this->assertRenders('
104+
<ul>
105+
<li><a href="/">Home</a></li>
106+
<li>
107+
<a href="/people" class="active exact-active">People</a>
108+
<ul>
109+
<li><a href="/people/sebastian">Sebastian</a></li>
110+
</ul>
111+
</li>
112+
</ul>
113+
');
114+
}
115+
116+
/** @test */
117+
public function it_can_set_link_active_for_submenu_header()
118+
{
119+
$submenu = Menu::new()
120+
->link('/people/sebastian', 'Sebastian')
121+
->setActiveClassOnLink(true)
122+
->setActiveClassOnParent(false);
123+
124+
$this->menu = Menu::new()
125+
->link('/', 'Home')
126+
->submenu(Link::to('/people', 'People'), $submenu)
127+
->setActive('/people/sebastian')
128+
->setActiveClassOnLink(true)
129+
->setActiveClassOnParent(false);
130+
131+
$this->assertRenders('
132+
<ul>
133+
<li><a href="/">Home</a></li>
134+
<li>
135+
<a href="/people" class="active">People</a>
136+
<ul>
137+
<li><a href="/people/sebastian" class="active exact-active">Sebastian</a></li>
138+
</ul>
139+
</li>
140+
</ul>
141+
');
142+
}
143+
46144
/** @test */
47145
public function it_only_sets_exact_active_on_exact_url_match()
48146
{

0 commit comments

Comments
 (0)