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

Dropdown::itemsContainerTag()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

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

593
    private function renderDropdown(array $items, /** @scrutinizer ignore-unused */ array $itemsAttributes = []): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
594
    {
595 13
        return self::widget()
596 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

596
            ->/** @scrutinizer ignore-call */ container(false)
Loading history...
597
            //->containerAttributes($itemsAttributes)
598 13
            ->dividerAttributes($this->dividerAttributes)
599 13
            ->headerClass($this->headerClass)
600 13
            ->headerTag($this->headerTag)
601 13
            ->itemClass($this->itemClass)
602 13
            ->itemContainerAttributes($this->itemContainerAttributes)
603 13
            ->itemContainerTag($this->itemContainerTag)
604 13
            ->itemTag($this->itemTag)
605 13
            ->items($items)
606 13
            ->itemsContainerAttributes($this->itemsContainerAttributes)
607 13
            ->toggleAttributes($this->toggleAttributes)
608 13
            ->toggleType($this->toggleType)
609 13
            ->render();
610
    }
611
612 2
    private function renderHeader(string $label, array $headerAttributes = []): string
613
    {
614 2
        if ($this->headerClass !== '') {
615 1
            Html::addCssClass($headerAttributes, $this->headerClass);
616
        }
617
618 2
        if ($this->headerTag === '') {
619 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
620
        }
621
622 1
        return $this->renderItemContainer(
623 1
            Html::normalTag($this->headerTag, $label, $headerAttributes)->encode(false)->render(),
624
        );
625
    }
626
627
    /**
628
     * @param array $item The item to be rendered.
629
     *
630
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
631
     */
632 26
    private function renderItem(array $item): string
633
    {
634
        /** @var bool */
635 26
        $enclose = $item['enclose'] ?? true;
636
        /** @var array */
637 26
        $headerAttributes = $item['headerAttributes'];
638
        /** @var array */
639 26
        $items = $item['items'] ?? [];
640
        /** @var array */
641 26
        $itemsAttributes = $item['itemsAttributes'] ?? [];
642
        /**
643
         * @var string $item['label']
644
         * @var string $item['icon']
645
         * @var string $item['iconClass']
646
         * @var array $item['iconAttributes']
647
         * @var array $item['iconContainerAttributes']
648
         */
649 26
        $label = $this->renderLabel(
650 26
            $item['label'],
651 26
            $item['icon'],
652 26
            $item['iconClass'],
653 26
            $item['iconAttributes'],
654 26
            $item['iconContainerAttributes'],
655
        );
656
        /** @var array */
657 26
        $lines = [];
658
        /** @var string */
659 26
        $link = $item['link'];
660
        /** @var array */
661 26
        $linkAttributes = $item['linkAttributes'];
662
        /** @var array */
663 26
        $toggleAttributes = $item['toggleAttributes'] ?? [];
664
665 26
        if ($this->itemClass !== '') {
666 8
            Html::addCssClass($linkAttributes, $this->itemClass);
667
        }
668
669 26
        if ($item['active']) {
670 8
            $linkAttributes['aria-current'] = 'true';
671 8
            Html::addCssClass($linkAttributes, [$this->activeClass]);
672
        }
673
674 26
        if ($item['disabled']) {
675 8
            Html::addCssClass($linkAttributes, $this->disabledClass);
676
        }
677
678 26
        if ($items === []) {
679 26
            $lines[] = $this->renderItemContent($label, $link, $enclose, $linkAttributes, $headerAttributes);
680
        } else {
681 13
            $itemContainer = $this->renderItemsContainer($this->renderDropdown($items, $itemsAttributes));
682 11
            $toggle = $this->renderToggle($label, $link, $toggleAttributes);
683 11
            $toggleSplitButton = $this->renderToggleSplitButton($label);
684
685 11
            if ($this->toggleType === 'split' && !str_contains($this->containerClass, 'dropstart')) {
686 5
                $lines[] = $toggleSplitButton . PHP_EOL . $toggle . PHP_EOL . $itemContainer;
687 6
            } elseif ($this->toggleType === 'split' && str_contains($this->containerClass, 'dropstart')) {
688 1
                $lines[] = $toggle . PHP_EOL . $itemContainer . PHP_EOL . $toggleSplitButton;
689
            } else {
690 5
                $lines[] = $toggle . PHP_EOL . $itemContainer;
691
            }
692
        }
693
694
        /** @psalm-var string[] $lines */
695 23
        return implode(PHP_EOL, $lines);
696
    }
697
698
    /**
699
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
700
     */
701 24
    private function renderItemContainer(string $content): string
702
    {
703 24
        if ($this->itemContainerTag === '') {
704 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
705
        }
706
707 23
        return Html::normalTag($this->itemContainerTag, $content, $this->itemContainerAttributes)
708 23
            ->encode(false)
709 23
            ->render();
710
    }
711
712
    /**
713
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
714
     */
715 12
    private function renderItemsContainer(string $content): string
716
    {
717 12
        $itemsContainerAttributes = $this->itemsContainerAttributes;
718
719 12
        if ($this->id !== '') {
720 6
            $itemsContainerAttributes['aria-labelledby'] =  $this->id;
721
        }
722
723 12
        if ($this->itemsContainerTag === '') {
724 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
725
        }
726
727 11
        return Html::normalTag($this->itemsContainerTag, $content, $itemsContainerAttributes)
728 11
            ->encode(false)
729 11
            ->render();
730
    }
731
732 26
    private function renderItemContent(
733
        string $label,
734
        string $link,
735
        bool $enclose,
736
        array $linkAttributes = [],
737
        array $headerAttributes = []
738
    ): string {
739
        return match (true) {
740 26
            $label === '-' => $this->renderDivider(),
741 26
            $enclose === false => $label,
742 26
            $link === '' => $this->renderHeader($label, $headerAttributes),
743 25
            default => $this->renderItemLink($label, $link, $linkAttributes),
744
        };
745
    }
746
747
    /**
748
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
749
     */
750 26
    private function renderItems(array $items = []): string
751
    {
752 26
        $lines = [];
753
754
        /** @var array|string $item */
755 26
        foreach ($items as $item) {
756 26
            $lines[] = match (gettype($item)) {
757 26
                'array' => $this->renderItem($item),
758 20
                'string' => $this->renderDivider(),
759
            };
760
        }
761
762 22
        return PHP_EOL . implode(PHP_EOL, $lines);
763
    }
764
765 26
    private function renderLabel(
766
        string $label,
767
        string $icon,
768
        string $iconClass,
769
        array $iconAttributes = [],
770
        array $iconContainerAttributes = []
771
    ): string {
772 26
        $html = '';
773
774 26
        if ($iconClass !== '') {
775 1
            Html::addCssClass($iconAttributes, $iconClass);
776
        }
777
778 26
        if ($icon !== '' || $iconClass !== '') {
779 1
            $i = I::tag()->addAttributes($iconAttributes)->content($icon)->encode(false)->render();
780 1
            $html = Span::tag()->addAttributes($iconContainerAttributes)->content($i)->encode(false)->render();
781
        }
782
783 26
        if ($label !== '') {
784 26
            $html .= $label;
785
        }
786
787 26
        return $html;
788
    }
789
790 25
    private function renderItemLink(
791
        string $label,
792
        string $link,
793
        array $linkAttributes = []
794
    ): string {
795 25
        $itemContainerAttributes = $this->itemContainerAttributes;
0 ignored issues
show
Unused Code introduced by
The assignment to $itemContainerAttributes is dead and can be removed.
Loading history...
796 25
        $linkAttributes['href'] = $link;
797
798 25
        if ($this->itemTag === '') {
799 1
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
800
        }
801
802 24
        $linkTag = Html::normalTag($this->itemTag, $label, $linkAttributes)->encode(false)->render();
803
804 24
        return match ($this->itemContainer) {
805 23
            true => $this->renderItemContainer($linkTag),
806 23
            default => $linkTag,
807
        };
808
    }
809
810 11
    private function renderToggle(string $label, string $link, array $toggleAttributes = []): string
811
    {
812 11
        if ($toggleAttributes === []) {
813 11
            $toggleAttributes = $this->toggleAttributes;
814
        }
815
816 11
        if ($this->id !== '') {
817 6
            $toggleAttributes['id'] = $this->id;
818
        }
819
820 11
        return match ($this->toggleType) {
821 4
            'link' => $this->renderToggleLink($label, $link, $toggleAttributes),
822 6
            'split' => $this->renderToggleSplit($label, $toggleAttributes),
823 11
            default => $this->renderToggleButton($label, $toggleAttributes),
824
        };
825
    }
826
827 1
    private function renderToggleButton(string $label, array $toggleAttributes = []): string
828
    {
829 1
        return Button::tag()->addAttributes($toggleAttributes)->content($label)->type('button')->render();
830
    }
831
832 4
    private function renderToggleLink(string $label, string $link, array $toggleAttributes = []): string
833
    {
834 4
        return A::tag()->addAttributes($toggleAttributes)->content($label)->href($link)->render();
835
    }
836
837 6
    private function renderToggleSplit(string $label, array $toggleAttributes = []): string
838
    {
839 6
        return Button::tag()
840 6
            ->addAttributes($toggleAttributes)
841 6
            ->content(Span::tag()->addAttributes($this->splitButtonSpanAttributes)->content($label))
842 6
            ->type('button')
843 6
            ->render();
844
    }
845
846 11
    private function renderToggleSplitButton(string $label): string
847
    {
848 11
        return Button::tag()->addAttributes($this->splitButtonAttributes)->content($label)->type('button')->render();
849
    }
850
}
851