Passed
Pull Request — master (#53)
by Wilmer
07:05 queued 04:41
created

Dropdown::renderItem()   B

Complexity

Conditions 9
Paths 32

Size

Total Lines 64
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 9.018

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 34
c 1
b 0
f 0
nc 32
nop 1
dl 0
loc 64
ccs 31
cts 33
cp 0.9394
crap 9.018
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 array $attributes = [];
25
    private bool $container = true;
26
    private array $containerAttributes = [];
27
    private string $containerClass = '';
28
    private string $containerTag = 'div';
29
    private string $disabledClass = 'disabled';
30
    private array $dividerAttributes = [];
31
    private string $dividerClass = 'dropdown-divider';
32
    private string $dividerTag = 'hr';
33
    private string $headerClass = '';
34
    private string $headerTag = 'span';
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 1
    public function activeClass(string $value): self
56
    {
57 1
        $new = clone $this;
58 1
        $new->activeClass = $value;
59
60 1
        return $new;
61
    }
62
63
    /**
64
     * Returns a new instance with the HTML attributes. The following special options are recognized.
65
     *
66
     * @param array $values Attribute values indexed by attribute names.
67
     *
68
     * @return static
69
     */
70 4
    public function attributes(array $values): static
71
    {
72 4
        $new = clone $this;
73 4
        $new->attributes = $values;
74
75 4
        return $new;
76
    }
77
78
    /**
79
     * Returns a new instance with the specified if the container is enabled, or not. Default is true.
80
     *
81
     * @param bool $value The container enabled.
82
     *
83
     * @return self
84
     */
85 4
    public function container(bool $value): self
86
    {
87 4
        $new = clone $this;
88 4
        $new->container = $value;
89
90 4
        return $new;
91
    }
92
93
    /**
94
     * Returns a new instance with the specified container HTML attributes.
95
     *
96
     * @param array $values Attribute values indexed by attribute names.
97
     *
98
     * @return self
99
     */
100
    public function containerAttributes(array $values): self
101
    {
102
        $new = clone $this;
103
        $new->containerAttributes = $values;
104
105
        return $new;
106
    }
107
108
    /**
109
     * Returns a new instance with the specified container class.
110
     *
111
     * @param string $value The container class.
112
     *
113
     * @return self
114
     */
115
    public function containerClass(string $value): self
116
    {
117
        $new = clone $this;
118
        $new->containerClass = $value;
119
120
        return $new;
121
    }
122
123
    /**
124
     * Returns a new instance with the specified container tag.
125
     *
126
     * @param string $value The container tag.
127
     *
128
     * @return self
129
     */
130
    public function containerTag(string $value): self
131
    {
132
        $new = clone $this;
133
        $new->containerTag = $value;
134
        return $new;
135
    }
136
137
    /**
138
     * Returns a new instance with the specified disabled class.
139
     *
140
     * @param string $value The disabled class.
141
     *
142
     * @return self
143
     */
144
    public function disabledClass(string $value): self
145
    {
146
        $new = clone $this;
147
        $new->disabledClass = $value;
148
149
        return $new;
150
    }
151
152
    /**
153
     * Returns a new instance with the specified divider HTML attributes.
154
     *
155
     * @param array $values Attribute values indexed by attribute names.
156
     *
157
     * @return self
158
     */
159 4
    public function dividerAttributes(array $values): self
160
    {
161 4
        $new = clone $this;
162 4
        $new->dividerAttributes = $values;
163
164 4
        return $new;
165
    }
166
167
    /**
168
     * Returns a new instance with the specified divider class.
169
     *
170
     * @param string $value The divider class.
171
     *
172
     * @return self
173
     */
174 4
    public function dividerClass(string $value): self
175
    {
176 4
        $new = clone $this;
177 4
        $new->dividerClass = $value;
178
179 4
        return $new;
180
    }
181
182
    /**
183
     * Returns a new instance with the specified divider tag.
184
     *
185
     * @param string $value The divider tag.
186
     *
187
     * @return self
188
     */
189
    public function dividerTag(string $value): self
190
    {
191
        $new = clone $this;
192
        $new->dividerTag = $value;
193
194
        return $new;
195
    }
196
197
    /**
198
     * Returns a new instance with the specified header class.
199
     *
200
     * @param string $value The header class.
201
     *
202
     * @return self
203
     */
204 4
    public function headerClass(string $value): self
205
    {
206 4
        $new = clone $this;
207 4
        $new->headerClass = $value;
208
209 4
        return $new;
210
    }
211
212
    /**
213
     * Returns a new instance with the specified header tag.
214
     *
215
     * @param string $value The header tag.
216
     *
217
     * @return self
218
     */
219 4
    public function headerTag(string $value): self
220
    {
221 4
        $new = clone $this;
222 4
        $new->headerTag = $value;
223
224 4
        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 4
    public function itemClass(string $value): self
235
    {
236 4
        $new = clone $this;
237 4
        $new->itemClass = $value;
238
239 4
        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
    public function itemContainer(bool $value): self
250
    {
251
        $new = clone $this;
252
        $new->itemContainer = $value;
253
254
        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 4
    public function itemContainerAttributes(array $values): self
265
    {
266 4
        $new = clone $this;
267 4
        $new->itemContainerAttributes = $values;
268
269 4
        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
    public function itemContainerClass(string $value): self
280
    {
281
        $new = clone $this;
282
        Html::addCssClass($new->itemContainerAttributes, $value);
283
284
        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 4
    public function itemContainerTag(string $value): self
295
    {
296 4
        $new = clone $this;
297 4
        $new->itemContainerTag = $value;
298
299 4
        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 4
    public function itemTag(string $value): self
310
    {
311 4
        $new = clone $this;
312 4
        $new->itemTag = $value;
313
314 4
        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 5
    public function items(array $value): self
343
    {
344 5
        $new = clone $this;
345 5
        $new->items = $value;
346
347 5
        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 4
    public function itemsContainerAttributes(array $values): self
358
    {
359 4
        $new = clone $this;
360 4
        $new->itemsContainerAttributes = $values;
361
362 4
        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 1
    public function itemsContainerClass(string $value): self
373
    {
374 1
        $new = clone $this;
375 1
        Html::addCssClass($new->itemsContainerAttributes, $value);
376
377 1
        return $new;
378
    }
379
380
    /**
381
     * Returns a new instance with the specified split button attributes.
382
     *
383
     * @param array $values Attribute values indexed by attribute names.
384
     *
385
     * @return self
386
     */
387
    public function splitButtonAttributes(array $values): self
388
    {
389
        $new = clone $this;
390
        $new->splitButtonAttributes = $values;
391
        return $new;
392
    }
393
394
    /**
395
     * Returns a new instance with the specified split button class.
396
     *
397
     * @param string $value The split button class.
398
     *
399
     * @return self
400
     */
401
    public function splitButtonClass(string $value): self
402
    {
403
        $new = clone $this;
404
        Html::addCssClass($new->splitButtonAttributes, $value);
405
406
        return $new;
407
    }
408
409
    /**
410
     * Returns a new instance with the specified split button span class.
411
     *
412
     * @param string $value The split button span class.
413
     *
414
     * @return self
415
     */
416
    public function splitButtonSpanClass(string $value): self
417
    {
418
        $new = clone $this;
419
        Html::addCssClass($new->splitButtonSpanAttributes, $value);
420
421
        return $new;
422
    }
423
424
    /**
425
     * Returns a new instance with the specified toggle HTML attributes.
426
     *
427
     * @param array $values Attribute values indexed by attribute names.
428
     *
429
     * @return self
430
     */
431 4
    public function toggleAttributes(array $values): self
432
    {
433 4
        $new = clone $this;
434 4
        $new->toggleAttributes = $values;
435
436 4
        return $new;
437
    }
438
439
    /**
440
     * Returns a new instance with the specified toggle class.
441
     *
442
     * @param string $value The toggle class.
443
     *
444
     * @return self
445
     */
446 1
    public function toggleClass(string $value): self
447
    {
448 1
        $new = clone $this;
449 1
        Html::addCssClass($new->toggleAttributes, $value);
450
451 1
        return $new;
452
    }
453
454
    /**
455
     * Returns a new instance with the specified toggle type, if `button` the toggle will be a button, otherwise a
456
     * `a` tag will be used.
457
     *
458
     * @param string $value The toggle tag.
459
     *
460
     * @return self
461
     */
462 4
    public function toggleType(string $value): self
463
    {
464 4
        $new = clone $this;
465 4
        $new->toggleType = $value;
466
467 4
        return $new;
468
    }
469
470
    /**
471
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
472
     */
473 5
    protected function run(): string
474
    {
475 5
        $containerAttributes = $this->containerAttributes;
476 5
        $items = $this->normalizeItems($this->items);
477 5
        $items = $this->renderItems($items) . PHP_EOL;
478
479 5
        if ($this->containerClass !== '') {
480
            Html::addCssClass($containerAttributes, $this->containerClass);
481
        }
482
483 5
        if ($this->containerTag === '') {
484
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
485
        }
486
487 5
        return match ($this->container) {
488 1
            true => Html::normalTag($this->containerTag, $items, $containerAttributes)->encode(false)->render(),
489 5
            false => $items,
490
        };
491
    }
492
493 5
    private function label(array $item): string
494
    {
495 5
        if (!isset($item['label'])) {
496
            throw new InvalidArgumentException('The "label" option is required.');
497
        }
498
499 5
        if (!is_string($item['label'])) {
500
            throw new InvalidArgumentException('The "label" option must be a string.');
501
        }
502
503 5
        if ($item['label'] === '') {
504
            throw new InvalidArgumentException('The "label" cannot be an empty string.');
505
        }
506
507
        /** @var bool */
508 5
        $encodeLabels = $item['encodeLabel'] ?? true;
509
510 5
        if ($encodeLabels) {
511 5
            return htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8', false);
512
        }
513
514
        return $item['label'];
515
    }
516
517 5
    private function normalizeItems(array $items): array
518
    {
519
        /**
520
         * @psalm-var array[] $items
521
         * @psalm-suppress RedundantConditionGivenDocblockType
522
         */
523 5
        foreach ($items as $i => $child) {
524 5
            if (is_array($child)) {
525 5
                $items[$i]['label'] = $this->label($child);
526
                /** @var bool */
527 5
                $items[$i]['active'] = $child['active'] ?? false;
528
                /** @var bool */
529 5
                $items[$i]['disabled'] = $child['disabled'] ?? false;
530
                /** @var bool */
531 5
                $items[$i]['enclose'] = $child['enclose'] ?? true;
532
                /** @var array */
533 5
                $items[$i]['headerAttributes'] = $child['headerAttributes'] ?? [];
534
                /** @var string */
535 5
                $items[$i]['link'] = $child['link'] ?? '/';
536
                /** @var array */
537 5
                $items[$i]['linkAttributes'] = $child['linkAttributes'] ?? [];
538
                /** @var string */
539 5
                $items[$i]['icon'] = $child['icon'] ?? '';
540
                /** @var array */
541 5
                $items[$i]['iconAttributes'] = $child['iconAttributes'] ?? [];
542
                /** @var string */
543 5
                $items[$i]['iconClass'] = $child['iconClass'] ?? '';
544
                /** @var array */
545 5
                $items[$i]['iconContainerAttributes'] = $child['iconContainerAttributes'] ?? [];
546
                /** @var bool */
547 5
                $items[$i]['visible'] = $child['visible'] ?? true;
548
                /** @var array */
549 5
                $dropdown = $child['items'] ?? [];
550
                /** @var array */
551 5
                $items[$i]['itemsAttributes'] = $child['itemsAttributes'] ?? [];
552
553 5
                if ($dropdown !== []) {
554 4
                    $items[$i]['items'] = $this->normalizeItems($dropdown);
555
                }
556
            }
557
        }
558
559 5
        return $items;
560
    }
561
562 5
    private function renderDivider(): string
563
    {
564 5
        $dividerAttributes = $this->dividerAttributes;
565
566 5
        if ($this->itemContainerTag === '') {
567
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
568
        }
569
570 5
        if ($this->dividerClass !== '') {
571 5
            Html::addCssClass($dividerAttributes, $this->dividerClass);
572
        }
573
574 5
        if ($this->dividerTag === '') {
575
            throw new InvalidArgumentException('Tag name must be a string and cannot be empty.');
576
        }
577
578 5
        return Html::normalTag(
579 5
            $this->itemContainerTag,
580 5
            Html::tag($this->dividerTag, '', $dividerAttributes),
581 5
            $this->itemContainerAttributes,
582 5
        )->encode(false)->render();
583
    }
584
585
    /**
586
     * @throws CircularReferenceException|InvalidConfigException|NotFoundException|NotInstantiableException
587
     */
588 4
    private function renderDropdown(array $items, array $itemsAttributes = []): string
589
    {
590 4
        return self::widget()
591 4
            ->attributes($itemsAttributes)
0 ignored issues
show
Bug introduced by
The method attributes() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of said class. However, the method does not exist in Yiisoft\Yii\Widgets\ContentDecorator or Yiisoft\Yii\Widgets\FragmentCache or Yiisoft\Yii\Widgets\Block. Are you sure you never get one of those? ( Ignorable by Annotation )

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

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