Passed
Push — master ( 2301b1...386ec7 )
by Alexander
14:22 queued 11:36
created

Alert::render()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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