Passed
Push — master ( fdfa94...107742 )
by Alexander
02:22
created

Dropdown::renderIcon()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use JsonException;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Factory\Exceptions\InvalidConfigException;
10
use Yiisoft\Html\Html;
11
12
use function array_key_exists;
13
use function array_merge;
14
use function implode;
15
use function is_array;
16
17
final class Dropdown extends Widget
18
{
19
    private string $buttonLabel = '';
20
    private array $buttonLabelOptions = [];
21
    private array $buttonIcon = ['class' => 'fas fa-angle-down', 'aria-hidden' => 'true'];
22
    private array $buttonIconOptions = [];
23
    private string $dividerClass = 'dropdown-divider';
24
    private string $itemsClass = 'dropdown-menu';
25
    private string $itemClass = 'dropdown-item';
26
    private bool $encodeLabels = true;
27
    private bool $encloseByContainer = true;
28
    private array $items = [];
29
    private array $itemsOptions = [];
30
    private array $options = [];
31
    private array $buttonOptions = [];
32
    private array $linkOptions = ['aria-haspopup' => 'true', 'aria-expanded' => 'false'];
33
    private array $triggerOptions = [];
34
35 17
    protected function run(): string
36
    {
37 17
        $this->buildOptions();
38
39 17
        return $this->buildDropdown();
40
    }
41
42
    /**
43
     * Set label button dropdown.
44
     *
45
     * @param string $value
46
     *
47
     * @return $this
48
     */
49 6
    public function buttonLabel(string $value): self
50
    {
51 6
        $this->buttonLabel = $value;
52 6
        return $this;
53
    }
54
55
    /**
56
     * The HTML attributes for the button dropdown.
57
     *
58
     * @param array $value
59
     *
60
     * @return $this
61
     *
62
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
63
     */
64 1
    public function buttonLabelOptions(array $value): self
65
    {
66 1
        $this->buttonLabelOptions = $value;
67 1
        return $this;
68
    }
69
70
    /**
71
     * Set css class divider dropdown.
72
     *
73
     * @param $value
74
     *
75
     * @return $this
76
     */
77 11
    public function dividerClass(string $value): self
78
    {
79 11
        $this->dividerClass  = $value;
80 11
        return $this;
81
    }
82
83
    /**
84
     * Set css class item dropdown.
85
     *
86
     * @param $value
87
     *
88
     * @return $this
89
     */
90 11
    public function itemClass(string $value): self
91
    {
92 11
        $this->itemClass  = $value;
93 11
        return $this;
94
    }
95
96
    /**
97
     * Set css class items container dropdown.
98
     *
99
     * @param $value
100
     *
101
     * @return $this
102
     */
103 11
    public function itemsClass(string $value): self
104
    {
105 11
        $this->itemsClass  = $value;
106 11
        return $this;
107
    }
108
109
    /**
110
     * Whether the labels for header items should be HTML-encoded.
111
     *
112
     * @param bool $value
113
     *
114
     * @return $this
115
     */
116 11
    public function encodeLabels(bool $value): self
117
    {
118 11
        $this->encodeLabels = $value;
119 11
        return $this;
120
    }
121
122
    /**
123
     * Set enclosed by container dropdown.
124
     *
125
     * @param $value
126
     *
127
     * @return $this
128
     */
129 11
    public function encloseByContainer(bool $value): self
130
    {
131 11
        $this->encloseByContainer = $value;
132 11
        return $this;
133
    }
134
135
    /**
136
     * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a
137
     * single menu with the following structure:
138
     *
139
     * - label: string, required, the label of the item link.
140
     * - encode: bool, optional, whether to HTML-encode item label.
141
     * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}.
142
     *   If not set, the item will be treated as a menu header when the item has no sub-menu.
143
     * - visible: bool, optional, whether this menu item is visible. Defaults to true.
144
     * - linkOptions: array, optional, the HTML attributes of the item link.
145
     * - itemsOptions: array, optional, the HTML attributes of the item.
146
     * - items: array, optional, the submenu items. The structure is the same as this property.
147
     *
148
     * To insert divider use `-`.
149
     *
150
     * @param array $value
151
     *
152
     * @return $this
153
     */
154 17
    public function items(array $value): self
155
    {
156 17
        $this->items = $value;
157 17
        return $this;
158
    }
159
160
    /**
161
     * The HTML attributes for the widget container tag.
162
     *
163
     * @param array $value
164
     *
165
     * @return $this
166
     *
167
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
168
     */
169 1
    public function options(array $value): self
170
    {
171 1
        $this->options = $value;
172 1
        return $this;
173
    }
174
175
    /**
176
     * The HTML attributes for the widget button tag.
177
     *
178
     * @param array $value
179
     *
180
     * @return $this
181
     *
182
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
183
     */
184 1
    public function buttonOptions(array $value): self
185
    {
186 1
        $this->buttonOptions = $value;
187 1
        return $this;
188
    }
189
190
    /**
191
     * The HTML attributes for the widget items.
192
     *
193
     * @param array $value
194
     *
195
     * @return $this
196
     *
197
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
198
     */
199 11
    public function itemsOptions(array $value): self
200
    {
201 11
        $this->itemsOptions = $value;
202 11
        return $this;
203
    }
204
205
    /**
206
     * The HTML attributes for the widget container trigger.
207
     *
208
     * @param array $value
209
     *
210
     * @return $this
211
     *
212
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
213
     */
214 1
    public function triggerOptions(array $value): self
215
    {
216 1
        $this->triggerOptions = $value;
217 1
        return $this;
218
    }
219
220 17
    private function buildDropdown(): string
221
    {
222 17
        if ($this->encloseByContainer) {
223 6
            $html = Html::beginTag('div', $this->options) . "\n";
224 6
            $html .= $this->buildDropdownTrigger();
225 6
            $html .= $this->renderItems($this->items, $this->itemsOptions) . "\n";
226 6
            $html .= Html::endTag('div');
227
        } else {
228 11
            $html = $this->renderItems($this->items, $this->itemsOptions);
229
        }
230
231 16
        return $html;
232
    }
233
234 6
    private function buildDropdownTrigger(): string
235
    {
236
        return
237 6
            Html::beginTag('div', $this->triggerOptions) . "\n" .
238 6
                Html::beginTag('button', $this->buttonOptions) . "\n" .
239 6
                    Html::tag('span', $this->buttonLabel, $this->buttonLabelOptions) . "\n" .
240 6
                    Html::beginTag('span', $this->buttonIconOptions) . "\n" .
241 6
                        Html::tag('i', '', $this->buttonIcon) . "\n" .
242 6
                    Html::endTag('span') . "\n" .
243 6
                Html::endTag('button') . "\n" .
244 6
            Html::endTag('div') . "\n";
245
    }
246
247 17
    private function buildOptions(): void
248
    {
249 17
        if ($this->encloseByContainer && (!isset($this->options['id']))) {
250 6
            $this->options['id'] = "{$this->getId()}-dropdown";
251 6
            $this->options = $this->addOptions($this->options, 'dropdown');
252 6
            $this->triggerOptions = $this->addOptions($this->triggerOptions, 'dropdown-trigger');
253 6
            $this->buttonOptions = $this->addOptions(
254 6
                array_merge(
255 6
                    $this->buttonOptions,
256 6
                    [ 'aria-haspopup' => 'true', 'aria-controls' => 'dropdown-menu']
257
                ),
258 6
                'button'
259
            );
260
261 6
            $this->buttonIconOptions = $this->addOptions($this->buttonIconOptions, 'icon is-small');
262 11
        } elseif (!isset($this->itemsOptions['id'])) {
263 10
            $this->itemsOptions['id'] = "{$this->getId()}-dropdown";
264
        }
265
266 17
        $this->itemsOptions = $this->addOptions($this->itemsOptions, $this->itemsClass);
267 17
    }
268
269
    /**
270
     * Renders menu items.
271
     *
272
     * @param array $items the menu items to be rendered
273
     * @param array $itemsOptions the container HTML attributes
274
     *
275
     * @throws InvalidConfigException|JsonException if the label option is not specified in one of the items.
276
     *
277
     * @return string the rendering result.
278
     */
279 17
    private function renderItems(array $items, array $itemsOptions = []): string
280
    {
281 17
        $lines = [];
282
283 17
        foreach ($items as $item) {
284 16
            if ($item === '-') {
285 9
                $lines[] = Html::tag('div', '', ['class' => $this->dividerClass]);
286 9
                continue;
287
            }
288
289 16
            if (!isset($item['label']) && $item !== '-') {
290 1
                throw new InvalidConfigException("The 'label' option is required.");
291
            }
292
293 15
            $this->encodeLabels = $item['encode'] ?? $this->encodeLabels;
294
295 15
            if ($this->encodeLabels) {
296 13
                $label = Html::encode($item['label']);
297
            } else {
298 2
                $label = $item['label'];
299
            }
300
301 15
            $iconOptions = [];
302
303 15
            $icon = $item['icon'] ?? '';
304
305 15
            if (array_key_exists('iconOptions', $item) && is_array($item['iconOptions'])) {
306 1
                $iconOptions = $this->addOptions($iconOptions, 'icon');
307
            }
308
309 15
            $label = $this->renderIcon($label, $icon, $iconOptions);
310
311 15
            $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
312 15
            $active = ArrayHelper::getValue($item, 'active', false);
313 15
            $disabled = ArrayHelper::getValue($item, 'disabled', false);
314
315 15
            Html::addCssClass($linkOptions, $this->itemClass);
316
317 15
            if ($disabled) {
318 1
                Html::addCssStyle($linkOptions, 'opacity:.65; pointer-events:none;');
319
            }
320
321
            /** @psalm-suppress ConflictingReferenceConstraint */
322 15
            if ($active) {
323 3
                Html::addCssClass($linkOptions, 'is-active');
324
            }
325
326 15
            $url = $item['url'] ?? null;
327
328 15
            if (empty($item['items'])) {
329 15
                $lines[] = Html::a($label, $url, $linkOptions);
330
            } else {
331 1
                $lines[] = Html::a($label, $url, array_merge($this->linkOptions, $linkOptions));
332
333 1
                $lines[] = self::widget()
334 1
                    ->dividerClass($this->dividerClass)
0 ignored issues
show
Bug introduced by
The method dividerClass() 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. ( Ignorable by Annotation )

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

334
                    ->/** @scrutinizer ignore-call */ dividerClass($this->dividerClass)
Loading history...
335 1
                    ->itemClass($this->itemClass)
336 1
                    ->itemsClass($this->itemsClass)
337 1
                    ->encloseByContainer($this->encloseByContainer)
338 1
                    ->encodeLabels($this->encodeLabels)
339 1
                    ->items($item['items'])
340 1
                    ->render();
341
            }
342
        }
343
344
        return
345 16
            Html::beginTag('div', $itemsOptions) . "\n" .
346 16
                implode("\n", $lines) . "\n" .
347 16
            Html::endTag('div');
348
    }
349
350 15
    private function renderIcon(string $label, string $icon, array $iconOptions): string
351
    {
352 15
        if ($icon !== '') {
353 1
            $label = Html::beginTag('span', $iconOptions) .
354 1
                Html::tag('i', '', ['class' => $icon]) .
355 1
                Html::endTag('span') .
356 1
                Html::tag('span', $label);
357
        }
358
359 15
        return $label;
360
    }
361
}
362