Passed
Pull Request — master (#53)
by
unknown
02:53
created

Dropdown   F

Complexity

Total Complexity 78

Size/Duplication

Total Lines 770
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 262
c 1
b 0
f 0
dl 0
loc 770
ccs 281
cts 281
cp 1
rs 2.16
wmc 78

46 Methods

Rating   Name   Duplication   Size   Complexity  
A renderItemContainer() 0 9 2
A itemsContainerTag() 0 6 1
A dividerClass() 0 6 1
A toggleAttributes() 0 6 1
A renderItemLink() 0 16 2
A itemClass() 0 6 1
A splitButtonSpanClass() 0 6 1
B renderItem() 0 64 9
A containerTag() 0 5 1
A splitButtonAttributes() 0 5 1
A renderLabel() 0 23 5
A renderToggleLink() 0 3 1
A itemContainerAttributes() 0 6 1
A itemContainerTag() 0 6 1
A container() 0 6 1
A headerClass() 0 6 1
A disabledClass() 0 6 1
A dividerTag() 0 6 1
A renderItems() 0 13 2
A run() 0 17 3
A renderToggleButton() 0 3 1
A toggleType() 0 6 1
A id() 0 6 1
A normalizeItems() 0 43 4
A renderDropdown() 0 16 1
A itemContainer() 0 6 1
A renderDivider() 0 14 3
A renderToggleSplit() 0 7 1
A renderToggle() 0 14 3
A splitButtonClass() 0 6 1
A renderItemsContainer() 0 15 3
A dividerAttributes() 0 6 1
A containerAttributes() 0 6 1
A renderToggleSplitButton() 0 3 1
A itemsContainerAttributes() 0 6 1
A itemsContainerClass() 0 6 1
A headerTag() 0 6 1
A toggleClass() 0 6 1
A renderItemContent() 0 12 1
A items() 0 6 1
A itemTag() 0 6 1
A containerClass() 0 6 1
A itemContainerClass() 0 6 1
A label() 0 18 5
A activeClass() 0 6 1
A renderHeader() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like Dropdown often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Dropdown, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Widgets;
6
7
use InvalidArgumentException;
8
use Yiisoft\Definitions\Exception\CircularReferenceException;
9
use Yiisoft\Definitions\Exception\InvalidConfigException;
10
use Yiisoft\Definitions\Exception\NotInstantiableException;
11
use Yiisoft\Factory\NotFoundException;
12
use Yiisoft\Html\Html;
13
use Yiisoft\Html\Tag\A;
14
use Yiisoft\Html\Tag\I;
15
use Yiisoft\Html\Tag\Button;
16
use Yiisoft\Html\Tag\Span;
17
use Yiisoft\Widget\Widget;
18
19
use function gettype;
20
21
final class Dropdown extends Widget
22
{
23
    private string $activeClass = 'active';
24
    private bool $container = true;
25
    private array $containerAttributes = [];
26
    private string $containerClass = '';
27
    private string $containerTag = 'div';
28
    private string $disabledClass = 'disabled';
29
    private array $dividerAttributes = [];
30
    private string $dividerClass = 'dropdown-divider';
31
    private string $dividerTag = 'hr';
32
    private string $headerClass = '';
33
    private string $headerTag = 'span';
34
    private string $id = '';
35
    private string $itemClass = '';
36
    private string $itemTag = 'a';
37
    private bool $itemContainer = true;
38
    private array $itemContainerAttributes = [];
39
    private string $itemContainerTag = 'li';
40
    private array $items = [];
41
    private array $itemsContainerAttributes = [];
42
    private string $itemsContainerTag = 'ul';
43
    private array $splitButtonAttributes = [];
44
    private array $splitButtonSpanAttributes = [];
45
    private array $toggleAttributes = [];
46
    private string $toggleType = 'button';
47
48
    /**
49
     * Returns a new instance with the specified active class.
50
     *
51
     * @param string $value The active class.
52
     */
53 2
    public function activeClass(string $value): self
54
    {
55 2
        $new = clone $this;
56 2
        $new->activeClass = $value;
57
58 2
        return $new;
59
    }
60
61
    /**
62
     * Returns a new instance with the specified if the container is enabled, or not. Default is true.
63
     *
64
     * @param bool $value The container enabled.
65
     */
66 14
    public function container(bool $value): self
67
    {
68 14
        $new = clone $this;
69 14
        $new->container = $value;
70
71 14
        return $new;
72
    }
73
74
    /**
75
     * Returns a new instance with the specified container HTML attributes.
76
     *
77
     * @param array $values Attribute values indexed by attribute names.
78
     */
79 2
    public function containerAttributes(array $values): self
80
    {
81 2
        $new = clone $this;
82 2
        $new->containerAttributes = $values;
83
84 2
        return $new;
85
    }
86
87
    /**
88
     * Returns a new instance with the specified container class.
89
     *
90
     * @param string $value The container class.
91
     */
92 9
    public function containerClass(string $value): self
93
    {
94 9
        $new = clone $this;
95 9
        $new->containerClass = $value;
96
97 9
        return $new;
98
    }
99
100
    /**
101
     * Returns a new instance with the specified container tag.
102
     *
103
     * @param string $value The container tag.
104
     */
105 3
    public function containerTag(string $value): self
106
    {
107 3
        $new = clone $this;
108 3
        $new->containerTag = $value;
109 3
        return $new;
110
    }
111
112
    /**
113
     * Returns a new instance with the specified disabled class.
114
     *
115
     * @param string $value The disabled class.
116
     */
117 2
    public function disabledClass(string $value): self
118
    {
119 2
        $new = clone $this;
120 2
        $new->disabledClass = $value;
121
122 2
        return $new;
123
    }
124
125
    /**
126
     * Returns a new instance with the specified divider HTML attributes.
127
     *
128
     * @param array $values Attribute values indexed by attribute names.
129
     */
130 14
    public function dividerAttributes(array $values): self
131
    {
132 14
        $new = clone $this;
133 14
        $new->dividerAttributes = $values;
134
135 14
        return $new;
136
    }
137
138
    /**
139
     * Returns a new instance with the specified divider class.
140
     *
141
     * @param string $value The divider class.
142
     */
143 11
    public function dividerClass(string $value): self
144
    {
145 11
        $new = clone $this;
146 11
        $new->dividerClass = $value;
147
148 11
        return $new;
149
    }
150
151
    /**
152
     * Returns a new instance with the specified divider tag.
153
     *
154
     * @param string $value The divider tag.
155
     */
156 3
    public function dividerTag(string $value): self
157
    {
158 3
        $new = clone $this;
159 3
        $new->dividerTag = $value;
160
161 3
        return $new;
162
    }
163
164
    /**
165
     * Returns a new instance with the specified header class.
166
     *
167
     * @param string $value The header class.
168
     */
169 14
    public function headerClass(string $value): self
170
    {
171 14
        $new = clone $this;
172 14
        $new->headerClass = $value;
173
174 14
        return $new;
175
    }
176
177
    /**
178
     * Returns a new instance with the specified header tag.
179
     *
180
     * @param string $value The header tag.
181
     */
182 14
    public function headerTag(string $value): self
183
    {
184 14
        $new = clone $this;
185 14
        $new->headerTag = $value;
186
187 14
        return $new;
188
    }
189
190
    /**
191
     * Returns a new instance with the specified Widget ID.
192
     *
193
     * @param string $value The id of the widget.
194
     */
195 7
    public function id(string $value): self
196
    {
197 7
        $new = clone $this;
198 7
        $new->id = $value;
199
200 7
        return $new;
201
    }
202
203
    /**
204
     * Returns a new instance with the specified item class.
205
     *
206
     * @param string $value The item class.
207
     */
208 14
    public function itemClass(string $value): self
209
    {
210 14
        $new = clone $this;
211 14
        $new->itemClass = $value;
212
213 14
        return $new;
214
    }
215
216
    /**
217
     * Returns a new instance with the specified item container, if false, the item container will not be rendered.
218
     *
219
     * @param bool $value The item container.
220
     */
221 2
    public function itemContainer(bool $value): self
222
    {
223 2
        $new = clone $this;
224 2
        $new->itemContainer = $value;
225
226 2
        return $new;
227
    }
228
229
    /**
230
     * Returns a new instance with the specified item container HTML attributes.
231
     *
232
     * @param array $values Attribute values indexed by attribute names.
233
     */
234 14
    public function itemContainerAttributes(array $values): self
235
    {
236 14
        $new = clone $this;
237 14
        $new->itemContainerAttributes = $values;
238
239 14
        return $new;
240
    }
241
242
    /**
243
     * Returns a new instance with the specified item container class.
244
     *
245
     * @param string $value The item container class.
246
     */
247 2
    public function itemContainerClass(string $value): self
248
    {
249 2
        $new = clone $this;
250 2
        Html::addCssClass($new->itemContainerAttributes, $value);
251
252 2
        return $new;
253
    }
254
255
    /**
256
     * Returns a new instance with the specified item container tag.
257
     *
258
     * @param string $value The item container tag.
259
     */
260 15
    public function itemContainerTag(string $value): self
261
    {
262 15
        $new = clone $this;
263 15
        $new->itemContainerTag = $value;
264
265 15
        return $new;
266
    }
267
268
    /**
269
     * Returns a new instance with the specified item tag.
270
     *
271
     * @param string $value The item tag.
272
     */
273 15
    public function itemTag(string $value): self
274
    {
275 15
        $new = clone $this;
276 15
        $new->itemTag = $value;
277
278 15
        return $new;
279
    }
280
281
    /**
282
     * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a
283
     * single menu with the following structure:
284
     *
285
     * - label: string, required, the nav item label.
286
     * - active: bool, whether the item should be on active state or not.
287
     * - disabled: bool, whether the item should be on disabled state or not. For default `disabled` is false.
288
     * - enclose: bool, whether the item should be enclosed by a `<li>` tag or not. For default `enclose` is true.
289
     * - encodeLabel: bool, whether the label should be HTML encoded or not. For default `encodeLabel` is true.
290
     * - headerAttributes: array, HTML attributes to be rendered in the item header.
291
     * - link: string, the item's href. Defaults to "#". For default `link` is "#".
292
     * - linkAttributes: array, the HTML attributes of the item's link. For default `linkAttributes` is `[]`.
293
     * - icon: string, the item's icon. For default `icon` is ``.
294
     * - iconAttributes: array, the HTML attributes of the item's icon. For default `iconAttributes` is `[]`.
295
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
296
     * - items: array, optional, the submenu items. The structure is the same as this property.
297
     *   Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
298
     * - itemsAttributes: array, optional, the HTML attributes for sub-menu.
299
     *
300
     * To insert dropdown divider use `-`.
301
     *
302
     * @param array $value
303
     */
304 30
    public function items(array $value): self
305
    {
306 30
        $new = clone $this;
307 30
        $new->items = $value;
308
309 30
        return $new;
310
    }
311
312
    /**
313
     * Returns a new instance with the specified items container HTML attributes.
314
     *
315
     * @param array $values Attribute values indexed by attribute names.
316
     */
317 14
    public function itemsContainerAttributes(array $values): self
318
    {
319 14
        $new = clone $this;
320 14
        $new->itemsContainerAttributes = $values;
321
322 14
        return $new;
323
    }
324
325
    /**
326
     * Returns a new instance with the specified item container class.
327
     *
328
     * @param string $value The item container class.
329
     */
330 9
    public function itemsContainerClass(string $value): self
331
    {
332 9
        $new = clone $this;
333 9
        Html::addCssClass($new->itemsContainerAttributes, $value);
334
335 9
        return $new;
336
    }
337
338
    /**
339
     * Returns a new instance with the specified items container tag.
340
     *
341
     * @param string $value The items container tag.
342
     */
343 2
    public function itemsContainerTag(string $value): self
344
    {
345 2
        $new = clone $this;
346 2
        $new->itemsContainerTag = $value;
347
348 2
        return $new;
349
    }
350
351
    /**
352
     * Returns a new instance with the specified split button attributes.
353
     *
354
     * @param array $values Attribute values indexed by attribute names.
355
     */
356 1
    public function splitButtonAttributes(array $values): self
357
    {
358 1
        $new = clone $this;
359 1
        $new->splitButtonAttributes = $values;
360 1
        return $new;
361
    }
362
363
    /**
364
     * Returns a new instance with the specified split button class.
365
     *
366
     * @param string $value The split button class.
367
     */
368 7
    public function splitButtonClass(string $value): self
369
    {
370 7
        $new = clone $this;
371 7
        Html::addCssClass($new->splitButtonAttributes, $value);
372
373 7
        return $new;
374
    }
375
376
    /**
377
     * Returns a new instance with the specified split button span class.
378
     *
379
     * @param string $value The split button span class.
380
     */
381 7
    public function splitButtonSpanClass(string $value): self
382
    {
383 7
        $new = clone $this;
384 7
        Html::addCssClass($new->splitButtonSpanAttributes, $value);
385
386 7
        return $new;
387
    }
388
389
    /**
390
     * Returns a new instance with the specified toggle HTML attributes.
391
     *
392
     * @param array $values Attribute values indexed by attribute names.
393
     */
394 14
    public function toggleAttributes(array $values): self
395
    {
396 14
        $new = clone $this;
397 14
        $new->toggleAttributes = $values;
398
399 14
        return $new;
400
    }
401
402
    /**
403
     * Returns a new instance with the specified toggle class.
404
     *
405
     * @param string $value The toggle class.
406
     */
407 9
    public function toggleClass(string $value): self
408
    {
409 9
        $new = clone $this;
410 9
        Html::addCssClass($new->toggleAttributes, $value);
411
412 9
        return $new;
413
    }
414
415
    /**
416
     * Returns a new instance with the specified toggle type, if `button` the toggle will be a button, otherwise a
417
     * `a` tag will be used.
418
     *
419
     * @param string $value The toggle tag.
420
     */
421 14
    public function toggleType(string $value): self
422
    {
423 14
        $new = clone $this;
424 14
        $new->toggleType = $value;
425
426 14
        return $new;
427
    }
428
429
    /**
430
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
431
     */
432 29
    protected function run(): string
433
    {
434 29
        $containerAttributes = $this->containerAttributes;
435 29
        $items = $this->normalizeItems($this->items);
436 26
        $items = $this->renderItems($items) . PHP_EOL;
437
438 22
        if ($this->containerClass !== '') {
439 8
            Html::addCssClass($containerAttributes, $this->containerClass);
440
        }
441
442 22
        if ($this->containerTag === '') {
443 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
444
        }
445
446 21
        return match ($this->container) {
447 16
            true => Html::normalTag($this->containerTag, $items, $containerAttributes)->encode(false)->render(),
448 21
            false => $items,
449
        };
450
    }
451
452 29
    private function label(array $item): string
453
    {
454 29
        if (!isset($item['label'])) {
455 1
            throw new InvalidArgumentException('The "label" option is required.');
456
        }
457
458 28
        if (!is_string($item['label'])) {
459 1
            throw new InvalidArgumentException('The "label" option must be a string.');
460
        }
461
462 27
        if ($item['label'] === '') {
463 1
            throw new InvalidArgumentException('The "label" cannot be an empty string.');
464
        }
465
466
        /** @var bool */
467 26
        $encode = $item['encodeLabel'] ?? true;
468
469 26
        return $encode ? Html::encode($item['label']) : $item['label'];
470
    }
471
472 29
    private function normalizeItems(array $items): array
473
    {
474
        /**
475
         * @psalm-var array[] $items
476
         * @psalm-suppress RedundantConditionGivenDocblockType
477
         */
478 29
        foreach ($items as $i => $child) {
479 29
            if (is_array($child)) {
480 29
                $items[$i]['label'] = $this->label($child);
481
                /** @var bool */
482 26
                $items[$i]['active'] = $child['active'] ?? false;
483
                /** @var bool */
484 26
                $items[$i]['disabled'] = $child['disabled'] ?? false;
485
                /** @var bool */
486 26
                $items[$i]['enclose'] = $child['enclose'] ?? true;
487
                /** @var array */
488 26
                $items[$i]['headerAttributes'] = $child['headerAttributes'] ?? [];
489
                /** @var string */
490 26
                $items[$i]['link'] = $child['link'] ?? '/';
491
                /** @var array */
492 26
                $items[$i]['linkAttributes'] = $child['linkAttributes'] ?? [];
493
                /** @var string */
494 26
                $items[$i]['icon'] = $child['icon'] ?? '';
495
                /** @var array */
496 26
                $items[$i]['iconAttributes'] = $child['iconAttributes'] ?? [];
497
                /** @var string */
498 26
                $items[$i]['iconClass'] = $child['iconClass'] ?? '';
499
                /** @var array */
500 26
                $items[$i]['iconContainerAttributes'] = $child['iconContainerAttributes'] ?? [];
501
                /** @var bool */
502 26
                $items[$i]['visible'] = $child['visible'] ?? true;
503
                /** @var array */
504 26
                $dropdown = $child['items'] ?? [];
505
                /** @var array */
506 26
                $items[$i]['itemsAttributes'] = $child['itemsAttributes'] ?? [];
507
508 26
                if ($dropdown !== []) {
509 13
                    $items[$i]['items'] = $this->normalizeItems($dropdown);
510
                }
511
            }
512
        }
513
514 26
        return $items;
515
    }
516
517 20
    private function renderDivider(): string
518
    {
519 20
        $dividerAttributes = $this->dividerAttributes;
520
521 20
        if ($this->dividerClass !== '') {
522 20
            Html::addCssClass($dividerAttributes, $this->dividerClass);
523
        }
524
525 20
        if ($this->dividerTag === '') {
526 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
527
        }
528
529 19
        return $this->renderItemContainer(
530 19
            Html::tag($this->dividerTag, '', $dividerAttributes)->encode(false)->render(),
531
        );
532
    }
533
534
    /**
535
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
536
     */
537 13
    private function renderDropdown(array $items, array $itemsAttributes = []): string
538
    {
539 13
        return self::widget()
540 13
            ->container(false)
0 ignored issues
show
Bug introduced by
The method container() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Yii\Widgets\Menu or Yiisoft\Yii\Widgets\Dropdown. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

540
            ->/** @scrutinizer ignore-call */ container(false)
Loading history...
541 13
            ->dividerAttributes($this->dividerAttributes)
542 13
            ->headerClass($this->headerClass)
543 13
            ->headerTag($this->headerTag)
544 13
            ->itemClass($this->itemClass)
545 13
            ->itemContainerAttributes($this->itemContainerAttributes)
546 13
            ->itemContainerTag($this->itemContainerTag)
547 13
            ->items($items)
548 13
            ->itemsContainerAttributes(array_merge($this->itemsContainerAttributes, $itemsAttributes))
549 13
            ->itemTag($this->itemTag)
550 13
            ->toggleAttributes($this->toggleAttributes)
551 13
            ->toggleType($this->toggleType)
552 13
            ->render();
553
    }
554
555 2
    private function renderHeader(string $label, array $headerAttributes = []): string
556
    {
557 2
        if ($this->headerClass !== '') {
558 1
            Html::addCssClass($headerAttributes, $this->headerClass);
559
        }
560
561 2
        if ($this->headerTag === '') {
562 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
563
        }
564
565 1
        return $this->renderItemContainer(
566 1
            Html::normalTag($this->headerTag, $label, $headerAttributes)->encode(false)->render(),
567
        );
568
    }
569
570
    /**
571
     * @param array $item The item to be rendered.
572
     *
573
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
574
     */
575 26
    private function renderItem(array $item): string
576
    {
577
        /** @var bool */
578 26
        $enclose = $item['enclose'] ?? true;
579
        /** @var array */
580 26
        $headerAttributes = $item['headerAttributes'];
581
        /** @var array */
582 26
        $items = $item['items'] ?? [];
583
        /** @var array */
584 26
        $itemsAttributes = $item['itemsAttributes'] ?? [];
585
        /**
586
         * @var string $item['label']
587
         * @var string $item['icon']
588
         * @var string $item['iconClass']
589
         * @var array $item['iconAttributes']
590
         * @var array $item['iconContainerAttributes']
591
         */
592 26
        $label = $this->renderLabel(
593 26
            $item['label'],
594 26
            $item['icon'],
595 26
            $item['iconClass'],
596 26
            $item['iconAttributes'],
597 26
            $item['iconContainerAttributes'],
598
        );
599
        /** @var array */
600 26
        $lines = [];
601
        /** @var string */
602 26
        $link = $item['link'];
603
        /** @var array */
604 26
        $linkAttributes = $item['linkAttributes'];
605
        /** @var array */
606 26
        $toggleAttributes = $item['toggleAttributes'] ?? [];
607
608 26
        if ($this->itemClass !== '') {
609 8
            Html::addCssClass($linkAttributes, $this->itemClass);
610
        }
611
612 26
        if ($item['active']) {
613 8
            $linkAttributes['aria-current'] = 'true';
614 8
            Html::addCssClass($linkAttributes, [$this->activeClass]);
615
        }
616
617 26
        if ($item['disabled']) {
618 8
            Html::addCssClass($linkAttributes, $this->disabledClass);
619
        }
620
621 26
        if ($items === []) {
622 26
            $lines[] = $this->renderItemContent($label, $link, $enclose, $linkAttributes, $headerAttributes);
623
        } else {
624 13
            $itemContainer = $this->renderItemsContainer($this->renderDropdown($items, $itemsAttributes));
625 11
            $toggle = $this->renderToggle($label, $link, $toggleAttributes);
626 11
            $toggleSplitButton = $this->renderToggleSplitButton($label);
627
628 11
            if ($this->toggleType === 'split' && !str_contains($this->containerClass, 'dropstart')) {
629 5
                $lines[] = $toggleSplitButton . PHP_EOL . $toggle . PHP_EOL . $itemContainer;
630 6
            } elseif ($this->toggleType === 'split' && str_contains($this->containerClass, 'dropstart')) {
631 1
                $lines[] = $toggle . PHP_EOL . $itemContainer . PHP_EOL . $toggleSplitButton;
632
            } else {
633 5
                $lines[] = $toggle . PHP_EOL . $itemContainer;
634
            }
635
        }
636
637
        /** @psalm-var string[] $lines */
638 23
        return implode(PHP_EOL, $lines);
639
    }
640
641
    /**
642
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
643
     */
644 24
    private function renderItemContainer(string $content): string
645
    {
646 24
        if ($this->itemContainerTag === '') {
647 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
648
        }
649
650 23
        return Html::normalTag($this->itemContainerTag, $content, $this->itemContainerAttributes)
651 23
            ->encode(false)
652 23
            ->render();
653
    }
654
655
    /**
656
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
657
     */
658 12
    private function renderItemsContainer(string $content): string
659
    {
660 12
        $itemsContainerAttributes = $this->itemsContainerAttributes;
661
662 12
        if ($this->id !== '') {
663 6
            $itemsContainerAttributes['aria-labelledby'] = $this->id;
664
        }
665
666 12
        if ($this->itemsContainerTag === '') {
667 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
668
        }
669
670 11
        return Html::normalTag($this->itemsContainerTag, $content, $itemsContainerAttributes)
671 11
            ->encode(false)
672 11
            ->render();
673
    }
674
675 26
    private function renderItemContent(
676
        string $label,
677
        string $link,
678
        bool $enclose,
679
        array $linkAttributes = [],
680
        array $headerAttributes = []
681
    ): string {
682
        return match (true) {
683 26
            $label === '-' => $this->renderDivider(),
684 26
            $enclose === false => $label,
685 26
            $link === '' => $this->renderHeader($label, $headerAttributes),
686 25
            default => $this->renderItemLink($label, $link, $linkAttributes),
687
        };
688
    }
689
690
    /**
691
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
692
     */
693 26
    private function renderItems(array $items = []): string
694
    {
695 26
        $lines = [];
696
697
        /** @var array|string $item */
698 26
        foreach ($items as $item) {
699 26
            $lines[] = match (gettype($item)) {
700 26
                'array' => $this->renderItem($item),
701 20
                'string' => $this->renderDivider(),
702
            };
703
        }
704
705 22
        return PHP_EOL . implode(PHP_EOL, $lines);
706
    }
707
708 26
    private function renderLabel(
709
        string $label,
710
        string $icon,
711
        string $iconClass,
712
        array $iconAttributes = [],
713
        array $iconContainerAttributes = []
714
    ): string {
715 26
        $html = '';
716
717 26
        if ($iconClass !== '') {
718 1
            Html::addCssClass($iconAttributes, $iconClass);
719
        }
720
721 26
        if ($icon !== '' || $iconClass !== '') {
722 1
            $i = I::tag()->addAttributes($iconAttributes)->content($icon)->encode(false)->render();
723 1
            $html = Span::tag()->addAttributes($iconContainerAttributes)->content($i)->encode(false)->render();
724
        }
725
726 26
        if ($label !== '') {
727 26
            $html .= $label;
728
        }
729
730 26
        return $html;
731
    }
732
733 25
    private function renderItemLink(
734
        string $label,
735
        string $link,
736
        array $linkAttributes = []
737
    ): string {
738 25
        $linkAttributes['href'] = $link;
739
740 25
        if ($this->itemTag === '') {
741 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
742
        }
743
744 24
        $linkTag = Html::normalTag($this->itemTag, $label, $linkAttributes)->encode(false)->render();
745
746 24
        return match ($this->itemContainer) {
747 23
            true => $this->renderItemContainer($linkTag),
748 23
            default => $linkTag,
749
        };
750
    }
751
752 11
    private function renderToggle(string $label, string $link, array $toggleAttributes = []): string
753
    {
754 11
        if ($toggleAttributes === []) {
755 11
            $toggleAttributes = $this->toggleAttributes;
756
        }
757
758 11
        if ($this->id !== '') {
759 6
            $toggleAttributes['id'] = $this->id;
760
        }
761
762 11
        return match ($this->toggleType) {
763 4
            'link' => $this->renderToggleLink($label, $link, $toggleAttributes),
764 6
            'split' => $this->renderToggleSplit($label, $toggleAttributes),
765 11
            default => $this->renderToggleButton($label, $toggleAttributes),
766
        };
767
    }
768
769 1
    private function renderToggleButton(string $label, array $toggleAttributes = []): string
770
    {
771 1
        return Button::tag()->addAttributes($toggleAttributes)->content($label)->type('button')->render();
772
    }
773
774 4
    private function renderToggleLink(string $label, string $link, array $toggleAttributes = []): string
775
    {
776 4
        return A::tag()->addAttributes($toggleAttributes)->content($label)->href($link)->render();
777
    }
778
779 6
    private function renderToggleSplit(string $label, array $toggleAttributes = []): string
780
    {
781 6
        return Button::tag()
782 6
            ->addAttributes($toggleAttributes)
783 6
            ->content(Span::tag()->addAttributes($this->splitButtonSpanAttributes)->content($label))
784 6
            ->type('button')
785 6
            ->render();
786
    }
787
788 11
    private function renderToggleSplitButton(string $label): string
789
    {
790 11
        return Button::tag()->addAttributes($this->splitButtonAttributes)->content($label)->type('button')->render();
791
    }
792
}
793