Passed
Pull Request — master (#79)
by Wilmer
02:24
created

Panel::render()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 2
nop 0
dl 0
loc 24
ccs 14
cts 14
cp 1
crap 2
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use InvalidArgumentException;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Html\Html;
10
use Yiisoft\Html\Tag\A;
11
use Yiisoft\Html\Tag\I;
12
use Yiisoft\Html\Tag\P;
13
use Yiisoft\Html\Tag\Span;
14
use Yiisoft\Widget\Widget;
15
16
use function implode;
17
use function in_array;
18
19
/**
20
 * Panel renders a panel component.
21
 *
22
 * @see https://bulma.io/documentation/components/panel/
23
 */
24
final class Panel extends Widget
25
{
26
    public const COLOR_PRIMARY = 'is-primary';
27
    public const COLOR_LINK = 'is-link';
28
    public const COLOR_INFO = 'is-info';
29
    public const COLOR_SUCCESS = 'is-success';
30
    public const COLOR_WARNING = 'is-warning';
31
    public const COLOR_DANGER = 'is-danger';
32
    public const COLOR_DARK = 'is-dark';
33
    private const COLOR_ALL = [
34
        self::COLOR_PRIMARY,
35
        self::COLOR_LINK,
36
        self::COLOR_INFO,
37
        self::COLOR_SUCCESS,
38
        self::COLOR_WARNING,
39
        self::COLOR_DANGER,
40
        self::COLOR_DARK,
41
    ];
42
43
    private array $attributes = [];
44
    private string $autoIdPrefix = 'w';
45
    private string $blockClass = 'panel-block';
46
    private string $color = '';
47
    private ?string $heading = null;
48
    private array $headingAttributes = [];
49
    private string $headingClass = 'panel-heading';
50
    private string $iconClass = 'panel-icon';
51
    private string $isActiveClass = 'is-active';
52
    private string $panelClass = 'panel';
53
    /** @psalm-var array<int, array> */
54
    private array $tabs = [];
55
    private array $tabsAttributes = [];
56
    private string $tabClass = 'panel-tabs';
57
    /** @psalm-var array<int, string> */
58
    private array $tabItems = [];
59
    private string $template = '{panelBegin}{panelHeading}{panelTabs}{panelItems}{panelEnd}';
60
61
    /**
62
     * Returns a new instance with the specified HTML attributes for widget.
63
     *
64
     * @param array $values Attribute values indexed by attribute names.
65
     *
66
     * @return self
67
     *
68
     * {@see \Yiisoft\Html\Html::renderTagAttributes()} For details on how attributes are being rendered.
69
     */
70 2
    public function attributes(array $values): self
71
    {
72 2
        $new = clone $this;
73 2
        $new->attributes = $values;
74 2
        return $new;
75
    }
76
77
    /**
78
     * Returns a new instance with the specified prefix to the automatically generated widget IDs.
79
     *
80
     * @param string $value The prefix to the automatically generated widget IDs.
81
     *
82
     * @return self
83
     */
84 1
    public function autoIdPrefix(string $value): self
85
    {
86 1
        $new = clone $this;
87 1
        $new->autoIdPrefix = $value;
88 1
        return $new;
89
    }
90
91
    /**
92
     * Returns a new instance with the specified the panel block class.
93
     *
94
     * @param string $value The block class.
95
     *
96
     * @return self
97
     */
98 2
    public function blockClass(string $value): self
99
    {
100 2
        $new = clone $this;
101 2
        $new->blockClass = $value;
102 2
        return $new;
103
    }
104
105
    /**
106
     * Returns a new instance with the specified panel color class.
107
     *
108
     * @param string $value The panel color class. Default any color.
109
     * Possible values are: Panel::COLOR_PRIMARY, Panel::COLOR_INFO, Panel::COLOR_SUCCESS, Panel::COLOR_WARNING,
110
     * Panel::COLOR_DANGER, Panel::COLOR_DARK
111
     *
112
     * @return self
113
     */
114 5
    public function color(string $value): self
115
    {
116 5
        if (!in_array($value, self::COLOR_ALL, true)) {
117 1
            $values = implode(' ', self::COLOR_ALL);
118 1
            throw new InvalidArgumentException("Invalid color. Valid values are: \"$values\".");
119
        }
120
121 4
        $new = clone $this;
122 4
        $new->color = $value;
123 4
        return $new;
124
    }
125
126
    /**
127
     * Returns a new instance with the specified the panel class.
128
     *
129
     * @param string $value The panel class.
130
     *
131
     * @return self
132
     */
133 2
    public function cssClass(string $value): self
134
    {
135 2
        $new = clone $this;
136 2
        $new->panelClass = $value;
137 2
        return $new;
138
    }
139
140
    /**
141
     * Returns a new instance with the specified panel heading.
142
     *
143
     * @param string $value The panel heading.
144
     *
145
     * @return self
146
     */
147 7
    public function heading(string $value): self
148
    {
149 7
        $new = clone $this;
150 7
        $new->heading = $value;
151 7
        return $new;
152
    }
153
154
    /**
155
     * Returns a new instance with the specified panel heading attributes.
156
     *
157
     * @param array $values Attribute values indexed by attribute names.
158
     *
159
     * @return self
160
     */
161 2
    public function headingAttributes(array $values): self
162
    {
163 2
        $new = clone $this;
164 2
        $new->headingAttributes = $values;
165 2
        return $new;
166
    }
167
168
    /**
169
     * Returns a new instance with the specified the panel heading class.
170
     *
171
     * @param string $value The heading class.
172
     *
173
     * @return self
174
     */
175 2
    public function headingClass(string $value): self
176
    {
177 2
        $new = clone $this;
178 2
        $new->headingClass = $value;
179 2
        return $new;
180
    }
181
182
    /**
183
     * Returns a new instance with the specified the panel icon class.
184
     *
185
     * @param string $value The icon class.
186
     *
187
     * @return self
188
     */
189 1
    public function iconClass(string $value): self
190
    {
191 1
        $new = clone $this;
192 1
        $new->iconClass = $value;
193 1
        return $new;
194
    }
195
196
    /**
197
     * Returns a new instance with the specified ID of the widget.
198
     *
199
     * @param string $value The ID of the widget.
200
     *
201
     * @return self
202
     */
203 1
    public function id(string $value): self
204
    {
205 1
        $new = clone $this;
206 1
        $new->attributes['id'] = $value;
207 1
        return $new;
208
    }
209
210
    /**
211
     * Returns a new instance with the specified active tab class.
212
     *
213
     * @param string $value The active tab class.
214
     *
215
     * @return self
216
     */
217 1
    public function isActiveClass(string $value): self
218
    {
219 1
        $new = clone $this;
220 1
        $new->isActiveClass = $value;
221 1
        return $new;
222
    }
223
224
    /**
225
     * Returns a new instance with the specified panel tabs.
226
     *
227
     * @param array $value The panel tabs.
228
     *
229
     * @return self
230
     *
231
     * @psalm-param array<int, array> $value
232
     */
233 12
    public function tabs(array $value): self
234
    {
235 12
        $new = clone $this;
236 12
        $new->tabs = $value;
237 12
        return $new;
238
    }
239
240
    /**
241
     * Returns a new instance with the specified the panel tab class.
242
     *
243
     * @param string $value The tab class.
244
     *
245
     * @return self
246
     */
247 2
    public function tabClass(string $value): self
248
    {
249 2
        $new = clone $this;
250 2
        $new->tabClass = $value;
251 2
        return $new;
252
    }
253
254
    /**
255
     * Returns a new instance with the specified panel tabs attributes.
256
     *
257
     * @param array $values Attribute values indexed by attribute names.
258
     *
259
     * @return self
260
     */
261 2
    public function tabsAttributes(array $values): self
262
    {
263 2
        $new = clone $this;
264 2
        $new->tabsAttributes = $values;
265 2
        return $new;
266
    }
267
268
    /**
269
     * Returns a new instance with the specified template.
270
     *
271
     * @param string $value The string the template for rendering panel:
272
     *
273
     * - `{panelBegin}`: _string_, which will render the panel container begin.
274
     * - `{panelHeading}`: _string_, which will render the panel heading.
275
     * - `{panelTabs}`: _string_, which will render the panel tabs.
276
     * - `{panelItems}`: _string_, which will render the panel items.
277
     * - `{panelEnd}`: _string_, which will render the panel container end.
278
     *
279
     * @return self
280
     */
281 2
    public function template(string $value): self
282
    {
283 2
        $new = clone $this;
284 2
        $new->template = $value;
285 2
        return $new;
286
    }
287
288 17
    public function render(): string
289
    {
290 17
        $attributes = $this->attributes;
291
292
        /** @var string */
293 17
        $attributes['id'] ??= (Html::generateId($this->autoIdPrefix) . '-panel');
294
295 17
        $id = $attributes['id'];
296
297
        /** @var string */
298 17
        $tag = $attributes['tag'] ?? 'nav';
299
300 17
        Html::addCssClass($attributes, $this->panelClass);
301
302 17
        if ($this->color !== '') {
303 3
            Html::addCssClass($attributes, $this->color);
304
        }
305
306 17
        return strtr($this->template, [
307 17
            '{panelBegin}' => Html::openTag($tag, $attributes) . PHP_EOL,
308 17
            '{panelHeading}' => $this->renderHeading(),
309 17
            '{panelTabs}' => $this->renderTabs($id),
310 17
            '{panelItems}' => implode('', $this->tabItems),
311 17
            '{panelEnd}' => Html::closeTag($tag),
312 17
        ]);
313
    }
314
315 17
    private function renderHeading(): string
316
    {
317 17
        $headingAttributes = $this->headingAttributes;
318
319 17
        if (!empty($this->heading)) {
320 6
            Html::addCssClass($headingAttributes, $this->headingClass);
321
322 6
            return P::tag()
323 6
                ->attributes($headingAttributes)
324 6
                ->content($this->heading)
325 6
                ->render() . PHP_EOL;
326
        }
327
328 11
        return '';
329
    }
330
331 17
    private function renderTabs(string $id): string
332
    {
333 17
        $tabsAttributes = $this->tabsAttributes;
334
335 17
        if (!empty($this->tabs)) {
336 11
            $tabs = '';
337
338 11
            foreach ($this->tabs as $index => $item) {
339 11
                $tabs .= $this->renderTab($index, $item, $id . '-' . $index) . PHP_EOL;
340
            }
341
342 9
            Html::addCssClass($tabsAttributes, $this->tabClass);
343
344 9
            return P::tag()
345 9
                ->attributes($tabsAttributes)
346 9
                ->content(PHP_EOL . $tabs)
347 9
                ->encode(false)
348 9
                ->render() . PHP_EOL;
349
        }
350
351 6
        return '';
352
    }
353
354 11
    private function renderTab(int $index, array $item, string $id): string
355
    {
356
        /** @var string */
357 11
        $url = $item['url'] ?? '';
358
359
        /** @var string */
360 11
        $label = $item['label'] ?? '';
361
362
        /** @var bool */
363 11
        $encode = $item['encode'] ?? true;
364
365
        /** @var array */
366 11
        $urlAttributes = $item['urlAttributes'] ?? [];
367
368
        /** @var array */
369 11
        $tabItems = $item['items'] ?? [];
370
371 11
        if ($url !== '') {
372 1
            $urlAttributes['href'] = $url;
373
        }
374
375 11
        if ($label === '') {
376 1
            throw new InvalidArgumentException('The "label" option is required.');
377
        }
378
379 10
        if ($encode === true) {
380 10
            $label = Html::encode($label);
381
        }
382
383 10
        if ($this->isActive($item)) {
384 3
            Html::addCssClass($urlAttributes, $this->isActiveClass);
385
        }
386
387 10
        if (!empty($tabItems)) {
388
            /** @var string */
389 5
            $urlAttributes['href'] ??= '#' . $id;
390
391 5
            $tabsItems = '';
392
393
            /** @psalm-var array[] */
394 5
            foreach ($tabItems as $tabItem) {
395 5
                $tabsItems .= $this->renderItem($tabItem) . PHP_EOL;
396
            }
397
398 4
            $this->tabItems[$index] = $tabsItems;
399
        }
400
401 9
        return A::tag()
402 9
            ->attributes($urlAttributes)
403 9
            ->content($label)
404 9
            ->encode(false)
405 9
            ->render();
406
    }
407
408 5
    private function renderItem(array $item): string
409
    {
410
        /** @var array */
411 5
        $urlAttributes = $item['urlAttributes'] ?? [];
412
413
        /** @var string */
414 5
        $label = $item['label'] ?? '';
415
416
        /** @var string */
417 5
        $icon = $item['icon'] ?? '';
418
419
        /** @var bool */
420 5
        $encode = $item['encode'] ?? true;
421
422 5
        if ($label === '') {
423 1
            throw new InvalidArgumentException('The "label" option is required.');
424
        }
425
426
        /** @var array */
427 4
        $labelAttributes = $item['labelAttributes'] ?? [];
428
429 4
        if ($encode) {
430 4
            $label = Html::encode($label);
431
        }
432
433 4
        Html::addCssClass($urlAttributes, $this->blockClass);
434
435 4
        if ($this->isActive($item)) {
436 1
            Html::addCssClass($urlAttributes, $this->isActiveClass);
437
        }
438
439 4
        Html::addCssClass($labelAttributes, $this->iconClass);
440
441 4
        if ($icon !== '') {
442 3
            $icon = PHP_EOL . I::tag()
443 3
                ->attributes(['aria-hidden' => 'true'])
444 3
                ->class($icon) . PHP_EOL;
445 3
            $label = PHP_EOL . Span::tag()
446 3
                ->attributes($labelAttributes)
447 3
                ->content($icon)
448 3
                ->encode(false) . PHP_EOL .
449 3
                $label . PHP_EOL;
450
        }
451
452 4
        return A::tag()
453 4
            ->attributes($urlAttributes)
454 4
            ->content($label)
455 4
            ->encode(false)
456 4
            ->render();
457
    }
458
459
    /**
460
     * Checking if active item.
461
     *
462
     * @param array $item
463
     *
464
     * @return bool
465
     */
466 10
    private function isActive(array $item): bool
467
    {
468 10
        return (bool) ArrayHelper::getValue($item, 'active', false);
469
    }
470
}
471