Passed
Pull Request — master (#127)
by
unknown
02:59
created

Offcanvas   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 260
Duplicated Lines 0 %

Test Coverage

Coverage 99.01%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 101
c 1
b 0
f 0
dl 0
loc 260
ccs 100
cts 101
cp 0.9901
rs 10
wmc 26

16 Methods

Rating   Name   Duplication   Size   Complexity  
A placement() 0 6 1
A titleOptions() 0 6 1
A render() 0 6 1
A renderCloseButton() 0 13 1
A getId() 0 3 1
A renderTitle() 0 19 4
A bodyOptions() 0 6 1
A headerOptions() 0 6 1
A withoutBackdrop() 0 6 1
B begin() 0 41 8
A bsToggle() 0 3 1
A title() 0 6 1
A scroll() 0 6 1
A renderHeader() 0 13 1
A options() 0 6 1
A closeButtonOptions() 0 6 1
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 AbstractToggleWidget
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
    protected bool $renderToggle = false;
29
30 18
    public function getId(?string $suffix = '-offcanvas'): ?string
31
    {
32 18
        return $this->options['id'] ?? parent::getId($suffix);
33
    }
34
35 5
    protected function bsToggle(): string
36
    {
37 5
        return 'offcanvas';
38
    }
39
40
    /**
41
     * Enable/disable body scroll when offcanvas show
42
     *
43
     * @link https://getbootstrap.com/docs/5.1/components/offcanvas/#backdrop
44
     */
45 1
    public function scroll(bool $scroll = true): self
46
    {
47 1
        $new = clone $this;
48 1
        $new->scroll = $scroll;
49
50 1
        return $new;
51
    }
52
53
    /**
54
     * Enable/disable offcanvas backdrop
55
     *
56
     * @link https://getbootstrap.com/docs/5.1/components/offcanvas/#backdrop
57
     */
58 4
    public function withoutBackdrop(bool $withoutBackdrop = true): self
59
    {
60 4
        $new = clone $this;
61 4
        $new->withoutBackdrop = $withoutBackdrop;
62
63 4
        return $new;
64
    }
65
66
    /**
67
     * Set placement for opened offcanvas
68
     *
69
     * @link https://getbootstrap.com/docs/5.1/components/offcanvas/#placement
70
     */
71 4
    public function placement(string $placement): self
72
    {
73 4
        $new = clone $this;
74 4
        $new->placement = $placement;
75
76 4
        return $new;
77
    }
78
79
    /**
80
     * The HTML attributes for the widget container tag. The following special options are recognized.
81
     *
82
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
83
     */
84 5
    public function options(array $options): self
85
    {
86 5
        $new = clone $this;
87 5
        $new->options = $options;
88
89 5
        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 1
    public function headerOptions(array $options): self
98
    {
99 1
        $new = clone $this;
100 1
        $new->headerOptions = $options;
101
102 1
        return $new;
103
    }
104
105
    /**
106
     * Set/remove offcanvas title
107
     */
108 13
    public function title(?string $title): self
109
    {
110 13
        $new = clone $this;
111 13
        $new->title = $title;
112
113 13
        return $new;
114
    }
115
116
    /**
117
     * The HTML attributes for the widget title tag. The following special options are recognized.
118
     *
119
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
120
     */
121 1
    public function titleOptions(array $options): self
122
    {
123 1
        $new = clone $this;
124 1
        $new->titleOptions = $options;
125
126 1
        return $new;
127
    }
128
129
    /**
130
     * The HTML attributes for the widget body tag. The following special options are recognized.
131
     *
132
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
133
     */
134 1
    public function bodyOptions(array $options): self
135
    {
136 1
        $new = clone $this;
137 1
        $new->bodyOptions = $options;
138
139 1
        return $new;
140
    }
141
142
    /**
143
     * The HTML attributes for the widget close button tag. The following special options are recognized.
144
     *
145
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
146
     */
147 1
    public function closeButtonOptions(array $options): self
148
    {
149 1
        $new = clone $this;
150 1
        $new->closeButtonOptions = $options;
151
152 1
        return $new;
153
    }
154
155 17
    public function begin(): string
156
    {
157 17
        parent::begin();
158
159 17
        $options = $this->options;
160 17
        $bodyOptions = $this->bodyOptions;
161 17
        $tag = ArrayHelper::remove($options, 'tag', 'div');
162 17
        $bodyTag = ArrayHelper::remove($bodyOptions, 'tag', 'div');
163
164 17
        Html::addCssClass($options, ['widget' => 'offcanvas', 'placement' => $this->placement]);
165 17
        Html::addCssClass($bodyOptions, ['widget' => 'offcanvas-body']);
166
167 17
        $options['id'] = $this->getId();
168 17
        $options['tabindex'] = -1;
169
170 17
        if (!empty($this->title)) {
171 12
            if (isset($this->titleOptions['id'])) {
172
                $options['aria-labelledby'] = $this->titleOptions['id'];
173 12
            } elseif ($options['id']) {
174 12
                $options['aria-labelledby'] = $options['id'] . '-title';
175
            }
176
        }
177
178 17
        if ($this->scroll) {
179 1
            $options['data-bs-scroll'] = 'true';
180
        }
181
182 17
        if ($this->withoutBackdrop) {
183 4
            $options['data-bs-backdrop'] = 'false';
184
        }
185
186 17
        if ($this->theme) {
187 3
            $options['data-bs-theme'] = $this->theme;
188
        }
189
190 17
        $html = $this->renderToggle ? $this->renderToggle() : '';
191 17
        $html .= Html::openTag($tag, $options);
192 17
        $html .= $this->renderHeader();
193 17
        $html .= Html::openTag($bodyTag, $bodyOptions);
194
195 17
        return $html;
196
    }
197
198 17
    public function render(): string
199
    {
200 17
        $tag = $this->options['tag'] ?? 'div';
201 17
        $bodyTag = $this->bodyOptions['tag'] ?? 'div';
202
203 17
        return Html::closeTag($bodyTag) . Html::closeTag($tag);
204
    }
205
206
    /**
207
     * Renders offcanvas header.
208
     *
209
     * @return string the rendering header.
210
     */
211 17
    private function renderHeader(): string
212
    {
213 17
        $options = $this->headerOptions;
214 17
        $tag = ArrayHelper::remove($options, 'tag', 'header');
215
216 17
        Html::addCssClass($options, ['widget' => 'offcanvas-header']);
217
218 17
        $title = (string) $this->renderTitle();
219 17
        $closeButton = $this->renderCloseButton();
220
221 17
        return Html::tag($tag, $title . $closeButton, $options)
222 17
            ->encode(false)
223 17
            ->render();
224
    }
225
226
    /**
227
     * Renders offcanvas title.
228
     *
229
     * @return string|null the rendering header.
230
     */
231 17
    private function renderTitle(): ?string
232
    {
233 17
        if ($this->title === null) {
234 5
            return null;
235
        }
236
237 13
        $options = $this->titleOptions;
238 13
        $tag = ArrayHelper::remove($options, 'tag', 'h5');
239 13
        $encode = ArrayHelper::remove($options, 'encode');
240
241 13
        Html::addCssClass($options, ['offcanvas-title']);
242
243 13
        if (!isset($options['id']) && $id = $this->getId()) {
244 13
            $options['id'] = $id . '-title';
245
        }
246
247 13
        return Html::tag($tag, $this->title, $options)
248 13
            ->encode($encode)
249 13
            ->render();
250
    }
251
252
    /**
253
     * Renders offcanvas close button.
254
     *
255
     * @return string the rendering close button.
256
     */
257 17
    private function renderCloseButton(): string
258
    {
259 17
        $options = $this->closeButtonOptions;
260 17
        $label = ArrayHelper::remove($options, 'label', '');
261 17
        $encode = ArrayHelper::remove($options, 'encode');
262
263 17
        $options['type'] = 'button';
264 17
        $options['aria-label'] = 'Close';
265 17
        $options['data-bs-dismiss'] = 'offcanvas';
266
267 17
        return Html::button($label, $options)
268 17
            ->encode($encode)
269 17
            ->render();
270
    }
271
}
272