Passed
Push — master ( 61d1a6...f87603 )
by Alexander
04:12 queued 02:11
created

Dropdown::dropdownItemCssClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
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 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use InvalidArgumentException;
8
use ReflectionException;
9
use Yiisoft\Html\Html;
10
use Yiisoft\Html\Tag\A;
11
use Yiisoft\Html\Tag\Button;
12
use Yiisoft\Html\Tag\CustomTag;
13
use Yiisoft\Html\Tag\Div;
14
use Yiisoft\Html\Tag\Span;
15
use Yiisoft\Widget\Widget;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Yiisoft\Yii\Bulma\Widget. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
17
use function array_merge;
18
use function implode;
19
20
/**
21
 * The dropdown component is a container for a dropdown button and a dropdown menu.
22
 *
23
 * @link https://bulma.io/documentation/components/dropdown/
24
 */
25
final class Dropdown extends Widget
26
{
27
    private array $attributes = [];
28
    private string $autoIdPrefix = 'w';
29
    private array $buttonAttributes = [];
30
    private array $buttonIconAttributes = ['class' => 'icon is-small'];
31
    private string $buttonIconCssClass = '';
32
    private string $buttonIconText = '&#8595;';
33
    private string $buttonLabel = 'Click Me';
34
    private array $buttonLabelAttributes = [];
35
    private string $dividerCssClass = 'dropdown-divider';
36
    private string $dropdownCssClass = 'dropdown';
37
    private string $dropdownContentCssClass = 'dropdown-content';
38
    private string $dropdownItemActiveCssClass = 'is-active';
39
    private string $dropdownItemCssClass = 'dropdown-item';
40
    private string $dropdownItemDisabledStyleCss = 'opacity:.65;pointer-events:none;';
41
    private string $dropdownItemHeaderCssClass = 'dropdown-header';
42
    private string $dropdownMenuCssClass = 'dropdown-menu';
43
    private string $dropdownTriggerCssClass = 'dropdown-trigger';
44
    private bool $encloseByContainer = true;
45
    private array $items = [];
46
    private bool $submenu = false;
47
    private array $submenuAttributes = [];
48
49
    /**
50
     * Returns a new instance with the specified attributes.
51
     *
52
     * @param array $value The HTML attributes for the widget container tag.
53
     *
54
     * @return self
55
     *
56
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
57
     */
58 3
    public function attributes(array $value): self
59
    {
60 3
        $new = clone $this;
61 3
        $new->attributes = $value;
62 3
        return $new;
63
    }
64
65
    /**
66
     * Returns a new instance with the specified prefix to the automatically generated widget IDs.
67
     *
68
     * @param string $value The prefix to the automatically generated widget IDs.
69
     *
70
     * @return self
71
     */
72 1
    public function autoIdPrefix(string $value): self
73
    {
74 1
        $new = clone $this;
75 1
        $new->autoIdPrefix = $value;
76 1
        return $new;
77
    }
78
79
    /**
80
     * The HTML attributes for the dropdown button.
81
     *
82
     * @param array $value
83
     *
84
     * @return self
85
     */
86 2
    public function buttonAttributes(array $value): self
87
    {
88 2
        $new = clone $this;
89 2
        $new->buttonAttributes = $value;
90 2
        return $new;
91
    }
92
93
    /**
94
     * The HTML attributes for the dropdown button icon.
95
     *
96
     * @param array $value
97
     *
98
     * @return self
99
     */
100 2
    public function buttonIconAttributes(array $value): self
101
    {
102 2
        $new = clone $this;
103 2
        $new->buttonIconAttributes = $value;
104 2
        return $new;
105
    }
106
107
    /**
108
     * Set icon CSS class for the dropdown button.
109
     *
110
     * @param string $value
111
     *
112
     * @return self
113
     */
114 2
    public function buttonIconCssClass(string $value): self
115
    {
116 2
        $new = clone $this;
117 2
        $new->buttonIconCssClass = $value;
118 2
        return $new;
119
    }
120
121
    /**
122
     * Set icon text for the dropdown button.
123
     *
124
     * @param string $value
125
     *
126
     * @return self
127
     */
128 3
    public function buttonIconText(string $value): self
129
    {
130 3
        $new = clone $this;
131 3
        $new->buttonIconText = $value;
132 3
        return $new;
133
    }
134
135
    /**
136
     * Set label for the dropdown button.
137
     *
138
     * @param string $value
139
     *
140
     * @return self
141
     */
142 2
    public function buttonLabel(string $value): self
143
    {
144 2
        $new = clone $this;
145 2
        $new->buttonLabel = $value;
146 2
        return $new;
147
    }
148
149
    /**
150
     * The HTML attributes for the dropdown button label.
151
     *
152
     * @param array $value
153
     *
154
     * @return self
155
     */
156 2
    public function buttonLabelAttributes(array $value): self
157
    {
158 2
        $new = clone $this;
159 2
        $new->buttonLabelAttributes = $value;
160 2
        return $new;
161
    }
162
163
    /**
164
     * A CSS class for horizontal line separating dropdown items.
165
     *
166
     * @param string $value
167
     * @return self
168
     */
169 11
    public function dividerCssClass(string $value): self
170
    {
171 11
        $new = clone $this;
172 11
        $new->dividerCssClass = $value;
173 11
        return $new;
174
    }
175
176 9
    public function dropdownCssClass(string $value): self
177
    {
178 9
        $new = clone $this;
179 9
        $new->dropdownCssClass = $value;
180 9
        return $new;
181
    }
182
183
    /**
184
     * CSS class for dropdown content.
185
     *
186
     * @param string $value
187
     * @return self
188
     *
189
     * @link https://bulma.io/documentation/components/dropdown/#dropdown-content
190
     */
191 2
    public function dropdownContentCssClass(string $value): self
192
    {
193 2
        $new = clone $this;
194 2
        $new->dropdownContentCssClass = $value;
195 2
        return $new;
196
    }
197
198 2
    public function dropdownItemActiveCssClass(string $value): self
199
    {
200 2
        $new = clone $this;
201 2
        $new->dropdownItemActiveCssClass = $value;
202 2
        return $new;
203
    }
204
205 11
    public function dropdownItemCssClass(string $value): self
206
    {
207 11
        $new = clone $this;
208 11
        $new->dropdownItemCssClass = $value;
209 11
        return $new;
210
    }
211
212 2
    public function dropdownItemDisabledStyleCss(string $value): self
213
    {
214 2
        $new = clone $this;
215 2
        $new->dropdownItemDisabledStyleCss = $value;
216 2
        return $new;
217
    }
218
219 2
    public function dropdownItemHeaderCssClass(string $value): self
220
    {
221 2
        $new = clone $this;
222 2
        $new->dropdownItemHeaderCssClass = $value;
223 2
        return $new;
224
    }
225
226
    /**
227
     * Dropdown menu CSS class.
228
     *
229
     * @param string $value
230
     * @return self
231
     */
232 2
    public function dropdownMenuCssClass(string $value): self
233
    {
234 2
        $new = clone $this;
235 2
        $new->dropdownMenuCssClass = $value;
236 2
        return $new;
237
    }
238
239
    /**
240
     * Dropdown trigger CSS class.
241
     *
242
     * @param string $value
243
     * @return self
244
     */
245 2
    public function dropdownTriggerCssClass(string $value): self
246
    {
247 2
        $new = clone $this;
248 2
        $new->dropdownTriggerCssClass = $value;
249 2
        return $new;
250
    }
251
252
    /**
253
     * Returns a new instance with the specified ID of the widget.
254
     *
255
     * @param string $value The ID of the widget.
256
     *
257
     * @return self
258
     */
259 2
    public function id(string $value): self
260
    {
261 2
        $new = clone $this;
262 2
        $new->attributes['id'] = $value;
263 2
        return $new;
264
    }
265
266
    /**
267
     * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a
268
     * single menu with the following structure:
269
     *
270
     * - label: string, required, the label of the item link.
271
     * - encode: bool, optional, whether to HTML-encode item label.
272
     * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}.
273
     *   If not set, the item will be treated as a menu header when the item has no sub-menu.
274
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
275
     * - urlAttributes: array, optional, the HTML attributes of the item link.
276
     * - attributes: array, optional, the HTML attributes of the item.
277
     * - items: array, optional, the submenu items. The structure is the same as this property.
278
     *   Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
279
     * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be
280
     *   merged with {@see submenuOptions}.
281
     *
282
     * To insert divider use `-`.
283
     *
284
     * @param array $value
285
     *
286
     * @return self
287
     */
288 29
    public function items(array $value): self
289
    {
290 29
        $new = clone $this;
291 29
        $new->items = $value;
292 29
        return $new;
293
    }
294
295
    /**
296
     * Set if it is a submenu or sub-dropdown.
297
     *
298
     * @param bool $value
299
     *
300
     * @return self
301
     */
302 3
    public function submenu(bool $value): self
303
    {
304 3
        $new = clone $this;
305 3
        $new->submenu = $value;
306 3
        return $new;
307
    }
308
309
    /**
310
     * The HTML attributes for sub-menu container tags.
311
     *
312
     * @param array $value
313
     *
314
     * @return self
315
     */
316 3
    public function submenuAttributes(array $value): self
317
    {
318 3
        $new = clone $this;
319 3
        $new->submenuAttributes = $value;
320 3
        return $new;
321
    }
322
323
    /**
324
     * If the widget should be enclosed by container.
325
     *
326
     * @param bool $value
327
     *
328
     * @return self
329
     */
330 10
    public function enclosedByContainer(bool $value = false): self
331
    {
332 10
        $new = clone $this;
333 10
        $new->encloseByContainer = $value;
334 10
        return $new;
335
    }
336
337
    /**
338
     * @throws ReflectionException
339
     */
340 28
    private function renderDropdown(): string
341
    {
342 28
        $attributes = $this->attributes;
343
344
        /** @var string */
345 28
        $id = $attributes['id'] ?? (Html::generateId($this->autoIdPrefix) . '-dropdown');
346 28
        unset($attributes['id']);
347
348 28
        if ($this->encloseByContainer) {
349 20
            Html::addCssClass($attributes, $this->dropdownCssClass);
350 20
            $html = Div::tag()
351 20
                ->attributes($attributes)
352 20
                ->content(PHP_EOL . $this->renderDropdownTrigger($id) . PHP_EOL)
353 19
                ->encode(false)
354 19
                ->render();
355
        } else {
356 9
            $html = $this->renderItems();
357
        }
358
359 27
        return $html;
360
    }
361
362
    /**
363
     * Render dropdown button.
364
     *
365
     * @return string The rendering result.
366
     *
367
     * @link https://bulma.io/documentation/components/dropdown/#hoverable-or-toggable
368
     */
369 19
    private function renderDropdownButton(string $id): string
370
    {
371 19
        $buttonAttributes = $this->buttonAttributes;
372
373 19
        Html::addCssClass($buttonAttributes, 'button');
374
375 19
        $buttonAttributes['aria-haspopup'] = 'true';
376 19
        $buttonAttributes['aria-controls'] = $id;
377
378 19
        return Button::tag()
379 19
            ->attributes($buttonAttributes)
380 19
            ->content(
381 19
                $this->renderLabelButton(
382 19
                    $this->buttonLabel,
383 19
                    $this->buttonLabelAttributes,
384 19
                    $this->buttonIconText,
385 19
                    $this->buttonIconCssClass,
386 19
                    $this->buttonIconAttributes,
387
                )
388
            )
389 19
            ->encode(false)
390 19
            ->render() . PHP_EOL;
391
    }
392
393 2
    public function renderDropdownButtonLink(): string
394
    {
395 2
        return A::tag()
396 2
            ->class($this->dropdownItemCssClass)
397 2
            ->content(
398 2
                $this->renderLabelButton(
399 2
                    $this->buttonLabel,
400 2
                    $this->buttonLabelAttributes,
401 2
                    $this->buttonIconText,
402 2
                    $this->buttonIconCssClass,
403 2
                    $this->buttonIconAttributes,
404
                )
405
            )
406 2
            ->encode(false)
407 2
            ->render() . PHP_EOL;
408
    }
409
410
    /**
411
     * @throws ReflectionException
412
     */
413 28
    protected function run(): string
414
    {
415 28
        return $this->renderDropdown();
416
    }
417
418
    /**
419
     * @throws ReflectionException
420
     */
421 20
    private function renderDropdownContent(): string
422
    {
423 20
        return Div::tag()
424 20
            ->class($this->dropdownContentCssClass)
425 20
            ->content(PHP_EOL . $this->renderItems() . PHP_EOL)
426 19
            ->encode(false)
427 19
            ->render();
428
    }
429
430
    /**
431
     * @throws ReflectionException
432
     */
433 20
    private function renderDropdownMenu(string $id): string
434
    {
435 20
        return Div::tag()
436 20
            ->class($this->dropdownMenuCssClass)
437 20
            ->content(PHP_EOL . $this->renderDropdownContent() . PHP_EOL)
438 19
            ->encode(false)
439 19
            ->id($id)
440 19
            ->render();
441
    }
442
443
    /**
444
     * @throws ReflectionException
445
     */
446 20
    private function renderDropdownTrigger(string $id): string
447
    {
448 20
        if (!$this->submenu) {
449 19
            $button = $this->renderDropdownButton($id);
450
        } else {
451 2
            $button = $this->renderDropdownButtonLink();
452
        }
453
454 20
        return Div::tag()
455 20
            ->class($this->dropdownTriggerCssClass)
456 20
            ->content(PHP_EOL . $button)
457 20
            ->encode(false)
458 20
            ->render() . PHP_EOL . $this->renderDropdownMenu($id);
459
    }
460
461
    /**
462
     * Renders menu items.
463
     *
464
     * @throws InvalidArgumentException|ReflectionException If the label option is not specified in one of the items.
465
     *
466
     * @return string the rendering result.
467
     */
468 28
    private function renderItems(): string
469
    {
470 28
        $lines = [];
471
472
        /** @var array|string $item */
473 28
        foreach ($this->items as $item) {
474 28
            if ($item === '-') {
475 4
                $lines[] = CustomTag::name('hr')->class($this->dividerCssClass)->render();
476
            } else {
477 28
                if (!isset($item['label'])) {
478 1
                    throw new InvalidArgumentException('The "label" option is required.');
479
                }
480
481
                /** @var string */
482 27
                $itemLabel = $item['label'] ?? '';
483
484 27
                if (isset($item['encode']) && $item['encode'] === true) {
485 1
                    $itemLabel = Html::encode($itemLabel);
486
                }
487
488
                /** @var array */
489 27
                $items = $item['items'] ?? [];
490
491
                /** @var array */
492 27
                $urlAttributes = $item['urlAttributes'] ?? [];
493
494
                /** @var string */
495 27
                $iconText = $item['iconText'] ?? '';
496
497
                /** @var string */
498 27
                $iconCssClass = $item['iconCssClass'] ?? '';
499
500
                /** @var array */
501 27
                $iconAttributes = $item['iconAttributes'] ?? [];
502
503
                /** @var string */
504 27
                $url = $item['url'] ?? '';
505
506
                /** @var bool */
507 27
                $active = $item['active'] ?? false;
508
509
                /** @var bool */
510 27
                $disabled = $item['disable'] ?? false;
511
512
                /** @var bool */
513 27
                $enclose = $item['enclose'] ?? true;
514
515
                /** @var bool */
516 27
                $submenu = $item['submenu'] ?? false;
517
518 27
                $itemLabel = $this->renderLabelItem($itemLabel, $iconText, $iconCssClass, $iconAttributes);
519
520 27
                Html::addCssClass($urlAttributes, $this->dropdownItemCssClass);
521
522 27
                if ($disabled) {
523 2
                    Html::addCssStyle($urlAttributes, $this->dropdownItemDisabledStyleCss);
524 26
                } elseif ($active) {
525 5
                    Html::addCssClass($urlAttributes, $this->dropdownItemActiveCssClass);
526
                }
527
528 27
                if ($items === []) {
529 27
                    if ($itemLabel === '-') {
530 1
                        $content = CustomTag::name('hr')->class($this->dividerCssClass)->render();
531 27
                    } elseif ($enclose === false) {
532 1
                        $content = $itemLabel;
533 27
                    } elseif ($url === '') {
534 1
                        $content = CustomTag::name('h6')
535 1
                            ->class($this->dropdownItemHeaderCssClass)
536 1
                            ->content($itemLabel)
537 1
                            ->encode(null)
538 1
                            ->render();
539
                    } else {
540 26
                        $content = A::tag()
541 26
                            ->attributes($urlAttributes)
542 26
                            ->content($itemLabel)
543 26
                            ->encode(false)
544 26
                            ->url($url)
545 26
                            ->render();
546
                    }
547
548 27
                    $lines[] = $content;
549
                } else {
550
                    /** @var array */
551 2
                    $submenuAttributes = array_merge($this->submenuAttributes, $item['submenuAttributes'] ?? []);
552
553 2
                    $lines[] = self::widget()
554 2
                        ->attributes($this->attributes)
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 Yiisoft\Widget\Widget such as Yiisoft\Yii\Bulma\Dropdown or Yiisoft\Yii\Bulma\Breadcrumbs or Yiisoft\Yii\Bulma\Nav or Yiisoft\Yii\Bulma\NavBar. ( Ignorable by Annotation )

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

554
                        ->/** @scrutinizer ignore-call */ attributes($this->attributes)
Loading history...
555 2
                        ->dividerCssClass($this->dividerCssClass)
556 2
                        ->dropdownItemCssClass($this->dropdownItemCssClass)
557 2
                        ->items($items)
558 2
                        ->submenu($submenu)
559 2
                        ->submenuAttributes($submenuAttributes)
560 2
                        ->render();
561
                }
562
            }
563
        }
564
565 27
        return implode(PHP_EOL, $lines);
566
    }
567
568 20
    private function renderLabelButton(
569
        string $label,
570
        array $labelAttributes,
571
        string $iconText,
572
        string $iconCssClass,
573
        array $iconAttributes = []
574
    ): string {
575 20
        $html = '';
576
577 20
        if ($label !== '') {
578 20
            $html = PHP_EOL . Span::tag()
579 20
                ->attributes($labelAttributes)
580 20
                ->content($label)
581 20
                ->encode(false)
582 20
                ->render();
583
        }
584
585 20
        if ($iconText !== '' || $iconCssClass !== '') {
586 20
            $html .= PHP_EOL .
587 20
                Span::tag()
588 20
                    ->attributes($iconAttributes)
589 20
                    ->content(CustomTag::name('i')->class($iconCssClass)->content($iconText)->encode(false)->render())
590 20
                    ->encode(false)
591 20
                    ->render();
592
        }
593
594 20
        return $html . PHP_EOL;
595
    }
596
597 27
    private function renderLabelItem(
598
        string $label,
599
        string $iconText,
600
        string $iconCssClass,
601
        array $iconAttributes = []
602
    ): string {
603 27
        $html = '';
604
605 27
        if ($iconText !== '' || $iconCssClass !== '') {
606 1
            $html = Span::tag()
607 1
                ->attributes($iconAttributes)
608 1
                ->content(CustomTag::name('i')->class($iconCssClass)->content($iconText)->encode(false)->render())
609 1
                ->encode(false)
610 1
                ->render();
611
        }
612
613 27
        if ($label !== '') {
614 27
            $html .= $label;
615
        }
616
617 27
        return $html;
618
    }
619
}
620