Passed
Pull Request — master (#94)
by Sergei
03:41
created

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