Passed
Pull Request — master (#78)
by
unknown
12:12
created

Alert::addClassNames()   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
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use JsonException;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Html\Html;
10
11
use function array_merge;
12
13
/**
14
 * Alert renders an alert bootstrap component.
15
 *
16
 * For example,
17
 *
18
 * ```php
19
 * echo Alert::widget()
20
 *     ->options([
21
 *         'class' => 'alert-info',
22
 *     ])
23
 *     ->body('Say hello...');
24
 * ```
25
 *
26
 * @link https://getbootstrap.com/docs/5.0/components/alerts/
27
 */
28
final class Alert extends Widget
29
{
30
    private const TYPE_PRIMARY = 'alert-primary';
31
    private const TYPE_SECONDARY = 'alert-secondary';
32
    private const TYPE_SUCCESS = 'alert-success';
33
    private const TYPE_DANGER = 'alert-danger';
34
    private const TYPE_WARNING = 'alert-warning';
35
    private const TYPE_INFO = 'alert-info';
36
    private const TYPE_LIGHT = 'alert-light';
37
    private const TYPE_DARK = 'alert-dark';
38
39
    private string $body = '';
40
    private ?string $header = null;
41
    private array $headerOptions = [];
42
    private string $headerTag = 'h4';
43
    private ?array $closeButton = [
44
        'class' => 'btn-close',
45
    ];
46
    private string $buttonTag = 'button';
47
    private bool $encode = false;
48
    private array $options = [];
49
    private array $classNames = [];
50
    private bool $fade = false;
51
52 17
    public function getId(?string $suffix = '-alert'): ?string
53
    {
54 17
        return $this->options['id'] ?? parent::getId($suffix);
55
    }
56
57 17
    protected function run(): string
58
    {
59 17
        $options = $this->prepareOptions();
60 17
        $tag = ArrayHelper::remove($options, 'tag', 'div');
61
62 17
        $content = Html::openTag($tag, $options);
63 17
        $content .= $this->renderHeader();
64 17
        $content .= $this->encode ? Html::encode($this->body) : $this->body;
65 17
        $content .= $this->renderCloseButton(false);
66 17
        $content .= Html::closeTag($tag);
67
68 17
        return $content;
69
    }
70
71
    /**
72
     * The body content in the alert component. Alert widget will also be treated as the body content, and will be
73
     * rendered before this.
74
     *
75
     * @param string $value
76
     *
77
     * @return self
78
     */
79 17
    public function body(string $value): self
80
    {
81 17
        $new = clone $this;
82 17
        $new->body = $value;
83
84 17
        return $new;
85
    }
86
87
    /**
88
     * The header content in alert component
89
     *
90
     * @param string|null $header
91
     *
92
     * @return self
93
     */
94 1
    public function header(?string $header): self
95
    {
96 1
        $new = clone $this;
97 1
        $new->header = $header;
98
99 1
        return $new;
100
    }
101
102
    /**
103
     * The HTML attributes for the widget header tag. The following special options are recognized.
104
     *
105
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
106
     *
107
     * @param array $options
108
     *
109
     * @return self
110
     */
111 1
    public function headerOptions(array $options): self
112
    {
113 1
        $new = clone $this;
114 1
        $new->headerOptions = $options;
115
116 1
        return $new;
117
    }
118
119
    /**
120
     * Set tag name for header
121
     *
122
     * @param string $tag
123
     *
124
     * @return self
125
     */
126 1
    public function headerTag(string $tag): self
127
    {
128 1
        $new = clone $this;
129 1
        $new->headerTag = $tag;
130
131 1
        return $new;
132
    }
133
134
    /**
135
     * The options for rendering the close button tag.
136
     *
137
     * The close button is displayed in the header of the modal window. Clicking on the button will hide the modal
138
     * window. If {@see closeButtonEnabled} is false, no close button will be rendered.
139
     *
140
     * The following special options are supported:
141
     *
142
     * - tag: string, the tag name of the button. Defaults to 'button'.
143
     * - label: string, the label of the button. Defaults to '&times;'.
144
     *
145
     * The rest of the options will be rendered as the HTML attributes of the button tag.
146
     *
147
     * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts) for the supported HTML
148
     * attributes.
149
     *
150
     * @param array $value
151
     *
152
     * @return self
153
     */
154 1
    public function closeButton(array $value): self
155
    {
156 1
        $new = clone $this;
157 1
        $new->closeButton = $value;
158
159 1
        return $new;
160
    }
161
162
    /**
163
     * Disable close button.
164
     *
165
     * @return self
166
     */
167 10
    public function withoutCloseButton(): self
168
    {
169 10
        $new = clone $this;
170 10
        $new->closeButton = null;
171
172 10
        return $new;
173
    }
174
175
    /**
176
     * Set close button tag
177
     *
178
     * @param string $tag
179
     *
180
     * @return self
181
     */
182 1
    public function buttonTag(string $tag): self
183
    {
184 1
        $new = clone $this;
185 1
        $new->buttonTag = $tag;
186
187 1
        return $new;
188
    }
189
190
    /**
191
     * The HTML attributes for the widget container tag. The following special options are recognized.
192
     *
193
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
194
     *
195
     * @param array $value
196
     *
197
     * @return self
198
     */
199 3
    public function options(array $value): self
200
    {
201 3
        $new = clone $this;
202 3
        $new->options = $value;
203
204 3
        return $new;
205
    }
206
207
    /**
208
     * Enable/Disable encode body
209
     *
210
     * @param bool $encode
211
     *
212
     * @return self
213
     */
214
    public function encode(bool $encode = true): self
215
    {
216
        $new = clone $this;
217
        $new->encode = $encode;
218
219
        return $new;
220
    }
221
222
    /**
223
     * Enable/Disable dissmiss animation
224
     *
225
     * @param bool $fade
226
     *
227
     * @return self
228
     */
229 2
    public function fade(bool $fade = true): self
230
    {
231 2
        $new = clone $this;
232 2
        $new->fade = $fade;
233
234 2
        return $new;
235
    }
236
237
    /**
238
     * Set type of alert, 'alert-success', 'alert-danger', 'custom-alert' etc
239
     *
240
     * @param string $type
241
     *
242
     * @return self
243
     */
244 9
    public function addClassNames(string ...$classNames): self
245
    {
246 9
        $new = clone $this;
247 9
        $new->classNames = array_filter($classNames, static fn ($name) => $name !== '');
248
249 9
        return $new;
250
    }
251
252
    /**
253
     * Short method for primary alert type
254
     *
255
     * @return self
256
     */
257 1
    public function primary(): self
258
    {
259 1
        return $this->addClassNames(self::TYPE_PRIMARY);
260
    }
261
262
    /**
263
     * Short method for secondary alert type
264
     *
265
     * @return self
266
     */
267 1
    public function secondary(): self
268
    {
269 1
        return $this->addClassNames(self::TYPE_SECONDARY);
270
    }
271
272
    /**
273
     * Short method for success alert type
274
     *
275
     * @return self
276
     */
277 1
    public function success(): self
278
    {
279 1
        return $this->addClassNames(self::TYPE_SUCCESS);
280
    }
281
282
    /**
283
     * Short method for danger alert type
284
     *
285
     * @return self
286
     */
287 1
    public function danger(): self
288
    {
289 1
        return $this->addClassNames(self::TYPE_DANGER);
290
    }
291
292
    /**
293
     * Short method for warning alert type
294
     *
295
     * @return self
296
     */
297 1
    public function warning(): self
298
    {
299 1
        return $this->addClassNames(self::TYPE_WARNING);
300
    }
301
302
    /**
303
     * Short method for info alert type
304
     *
305
     * @return self
306
     */
307 1
    public function info(): self
308
    {
309 1
        return $this->addClassNames(self::TYPE_INFO);
310
    }
311
312
    /**
313
     * Short method for light alert type
314
     *
315
     * @return self
316
     */
317 1
    public function light(): self
318
    {
319 1
        return $this->addClassNames(self::TYPE_LIGHT);
320
    }
321
322
    /**
323
     * Short method for dark alert type
324
     *
325
     * @return self
326
     */
327 1
    public function dark(): self
328
    {
329 1
        return $this->addClassNames(self::TYPE_DARK);
330
    }
331
332
    /**
333
     * Renders the close button.
334
     *
335
     * @throws JsonException
336
     *
337
     * @return string the rendering result
338
     */
339 17
    public function renderCloseButton(bool $outside = true): ?string
340
    {
341 17
        if ($this->closeButton === null) {
342 10
            return null;
343
        }
344
345 7
        $options = array_merge(
346 7
            $this->closeButton,
347
            [
348 7
                'aria-label' => 'Close',
349
                'data-bs-dismiss' => 'alert',
350
            ],
351
        );
352 7
        $label = ArrayHelper::remove($options, 'label', '');
353 7
        $encode = ArrayHelper::remove($options, 'encode', $this->encode);
354
355 7
        if ($this->buttonTag === 'button' && !isset($options['type'])) {
356 7
            $options['type'] = 'button';
357
        }
358
359 7
        if ($outside) {
360
            $options['data-bs-target'] = '#' . $this->getId();
361
        }
362
363 7
        return Html::tag($this->buttonTag, $label, $options)->encode($encode)->render();
364
    }
365
366
    /**
367
     * Render header tag
368
     *
369
     * @return string|null
370
     */
371 17
    private function renderHeader(): ?string
372
    {
373 17
        if ($this->header === null) {
374 16
            return null;
375
        }
376
377 1
        $options = $this->headerOptions;
378 1
        $encode = ArrayHelper::remove($options, 'encode', true);
379
380 1
        Html::addCssClass($options, ['alert-heading']);
381
382 1
        return Html::tag($this->headerTag, $this->header, $options)->encode($encode)->render();
383
    }
384
385
    /**
386
     * Prepare the widget options.
387
     *
388
     * This method returns the default values for various options.
389
     *
390
     * @return array
391
     */
392 17
    private function prepareOptions(): array
393
    {
394 17
        $options = $this->options;
395 17
        $options['id'] = $this->getId();
396 17
        $classNames = array_merge(['widget' => 'alert'], $this->classNames);
397
398 17
        if ($this->closeButton !== null) {
399 7
            $classNames[] = 'alert-dismissible';
400
        }
401
402 17
        if ($this->fade) {
403 2
            $classNames[] = 'fade show';
404
        }
405
406 17
        Html::addCssClass($options, $classNames);
407
408 17
        if (!isset($options['role'])) {
409 17
            $options['role'] = 'alert';
410
        }
411
412 17
        return $options;
413
    }
414
}
415