Passed
Push — master ( 438f38...27fec6 )
by Wilmer
02:48
created

Offcanvas::begin()   B

Complexity

Conditions 7
Paths 32

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 7.0031

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 24
nc 32
nop 0
dl 0
loc 40
ccs 24
cts 25
cp 0.96
crap 7.0031
rs 8.6026
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use Yiisoft\Arrays\ArrayHelper;
8
use Yiisoft\Html\Html;
9
10
final class Offcanvas extends Widget
11
{
12
    public const PLACEMENT_TOP = 'offcanvas-top';
13
    public const PLACEMENT_END = 'offcanvas-end';
14
    public const PLACEMENT_BOTTOM = 'offcanvas-bottom';
15
    public const PLACEMENT_START = 'offcanvas-start';
16
17
    private array $options = [];
18
    private array $headerOptions = [];
19
    private array $titleOptions = [];
20
    private array $bodyOptions = [];
21
    private array $closeButtonOptions = [
22
        'class' => 'btn-close',
23
    ];
24
    private ?string $title = null;
25
    private string $placement = self::PLACEMENT_START;
26
    private bool $scroll = false;
27
    private bool $withoutBackdrop = false;
28
29 12
    public function getId(?string $suffix = '-offcanvas'): ?string
30
    {
31 12
        return $this->options['id'] ?? parent::getId($suffix);
32
    }
33
34
    /**
35
     * Enable/disable body scroll when offcanvas show
36
     *
37
     * @link https://getbootstrap.com/docs/5.1/components/offcanvas/#backdrop
38
     */
39 1
    public function scroll(bool $scroll = true): self
40
    {
41 1
        $new = clone $this;
42 1
        $new->scroll = $scroll;
43
44 1
        return $new;
45
    }
46
47
    /**
48
     * Enable/disable offcanvas backdrop
49
     *
50
     * @link https://getbootstrap.com/docs/5.1/components/offcanvas/#backdrop
51
     */
52 2
    public function withoutBackdrop(bool $withoutBackdrop = true): self
53
    {
54 2
        $new = clone $this;
55 2
        $new->withoutBackdrop = $withoutBackdrop;
56
57 2
        return $new;
58
    }
59
60
    /**
61
     * Set placement for opened offcanvas
62
     *
63
     * @link https://getbootstrap.com/docs/5.1/components/offcanvas/#placement
64
     */
65 1
    public function placement(string $placement): self
66
    {
67 1
        $new = clone $this;
68 1
        $new->placement = $placement;
69
70 1
        return $new;
71
    }
72
73
    /**
74
     * The HTML attributes for the widget container tag. The following special options are recognized.
75
     *
76
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
77
     */
78 2
    public function options(array $options): self
79
    {
80 2
        $new = clone $this;
81 2
        $new->options = $options;
82
83 2
        return $new;
84
    }
85
86
    /**
87
     * The HTML attributes for the widget header tag. The following special options are recognized.
88
     *
89
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
90
     */
91 1
    public function headerOptions(array $options): self
92
    {
93 1
        $new = clone $this;
94 1
        $new->headerOptions = $options;
95
96 1
        return $new;
97
    }
98
99
    /**
100
     * Set/remove offcanvas title
101
     */
102 11
    public function title(?string $title): self
103
    {
104 11
        $new = clone $this;
105 11
        $new->title = $title;
106
107 11
        return $new;
108
    }
109
110
    /**
111
     * The HTML attributes for the widget title tag. The following special options are recognized.
112
     *
113
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
114
     */
115 1
    public function titleOptions(array $options): self
116
    {
117 1
        $new = clone $this;
118 1
        $new->titleOptions = $options;
119
120 1
        return $new;
121
    }
122
123
    /**
124
     * The HTML attributes for the widget body tag. The following special options are recognized.
125
     *
126
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
127
     */
128 1
    public function bodyOptions(array $options): self
129
    {
130 1
        $new = clone $this;
131 1
        $new->bodyOptions = $options;
132
133 1
        return $new;
134
    }
135
136
    /**
137
     * The HTML attributes for the widget close button tag. The following special options are recognized.
138
     *
139
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
140
     */
141
    public function closeButtonOptions(array $options): self
142
    {
143
        $new = clone $this;
144
        $new->closeButtonOptions = $options;
145
146
        return $new;
147
    }
148
149 12
    public function begin(): string
150
    {
151 12
        parent::begin();
152
153 12
        $options = $this->options;
154 12
        $bodyOptions = $this->bodyOptions;
155 12
        $tag = ArrayHelper::remove($options, 'tag', 'div');
156 12
        $bodyTag = ArrayHelper::remove($bodyOptions, 'tag', 'div');
157
158 12
        Html::addCssClass($options, ['widget' => 'offcanvas', 'placement' => $this->placement]);
159 12
        Html::addCssClass($bodyOptions, ['widget' => 'offcanvas-body']);
160
161 12
        $options['id'] = $this->getId();
162 12
        $options['tabindex'] = -1;
163
164 12
        if (!empty($this->title)) {
165 10
            if (isset($this->titleOptions['id'])) {
166
                $options['aria-labelledby'] = $this->titleOptions['id'];
167 10
            } elseif ($options['id']) {
168 10
                $options['aria-labelledby'] = $options['id'] . '-title';
169
            }
170
        }
171
172 12
        if ($this->scroll) {
173 1
            $options['data-bs-scroll'] = 'true';
174
        }
175
176 12
        if ($this->withoutBackdrop) {
177 2
            $options['data-bs-backdrop'] = 'false';
178
        }
179
180 12
        if ($this->theme) {
181 1
            $options['data-bs-theme'] = $this->theme;
182
        }
183
184 12
        $html = Html::openTag($tag, $options);
185 12
        $html .= $this->renderHeader();
186 12
        $html .= Html::openTag($bodyTag, $bodyOptions);
187
188 12
        return $html;
189
    }
190
191 12
    public function render(): string
192
    {
193 12
        $tag = $this->options['tag'] ?? 'div';
194 12
        $bodyTag = $this->bodyOptions['tag'] ?? 'div';
195
196 12
        return Html::closeTag($bodyTag) . Html::closeTag($tag);
197
    }
198
199
    /**
200
     * Renders offcanvas header.
201
     *
202
     * @return string the rendering header.
203
     */
204 12
    private function renderHeader(): string
205
    {
206 12
        $options = $this->headerOptions;
207 12
        $tag = ArrayHelper::remove($options, 'tag', 'header');
208
209 12
        Html::addCssClass($options, ['widget' => 'offcanvas-header']);
210
211 12
        $title = (string) $this->renderTitle();
212 12
        $closeButton = $this->renderCloseButton();
213
214 12
        return Html::tag($tag, $title . $closeButton, $options)
215 12
            ->encode(false)
216 12
            ->render();
217
    }
218
219
    /**
220
     * Renders offcanvas title.
221
     *
222
     * @return string|null the rendering header.
223
     */
224 12
    private function renderTitle(): ?string
225
    {
226 12
        if ($this->title === null) {
227 2
            return null;
228
        }
229
230 11
        $options = $this->titleOptions;
231 11
        $tag = ArrayHelper::remove($options, 'tag', 'h5');
232 11
        $encode = ArrayHelper::remove($options, 'encode');
233
234 11
        Html::addCssClass($options, ['offcanvas-title']);
235
236 11
        if (!isset($options['id']) && $id = $this->getId()) {
237 11
            $options['id'] = $id . '-title';
238
        }
239
240 11
        return Html::tag($tag, $this->title, $options)
241 11
            ->encode($encode)
242 11
            ->render();
243
    }
244
245
    /**
246
     * Renders offcanvas close button.
247
     *
248
     * @return string the rendering close button.
249
     */
250 12
    private function renderCloseButton(): string
251
    {
252 12
        $options = $this->closeButtonOptions;
253 12
        $label = ArrayHelper::remove($options, 'label', '');
254 12
        $encode = ArrayHelper::remove($options, 'encode');
255
256 12
        $options['type'] = 'button';
257 12
        $options['aria-label'] = 'Close';
258 12
        $options['data-bs-dismiss'] = 'offcanvas';
259
260 12
        return Html::button($label, $options)
261 12
            ->encode($encode)
262 12
            ->render();
263
    }
264
}
265