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

Offcanvas::render()   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 0
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 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 11
    public function getId(?string $suffix = '-offcanvas'): ?string
30
    {
31 11
        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 1
    public function withoutBackdrop(bool $withoutBackdrop = true): self
53
    {
54 1
        $new = clone $this;
55 1
        $new->withoutBackdrop = $withoutBackdrop;
56
57 1
        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
     * @param array $options
79
     */
80 2
    public function options(array $options): self
81
    {
82 2
        $new = clone $this;
83 2
        $new->options = $options;
84
85 2
        return $new;
86
    }
87
88
    /**
89
     * The HTML attributes for the widget header tag. The following special options are recognized.
90
     *
91
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
92
     *
93
     * @param array $options
94
     */
95 1
    public function headerOptions(array $options): self
96
    {
97 1
        $new = clone $this;
98 1
        $new->headerOptions = $options;
99
100 1
        return $new;
101
    }
102
103
    /**
104
     * Set/remove offcanvas title
105
     */
106 10
    public function title(?string $title): self
107
    {
108 10
        $new = clone $this;
109 10
        $new->title = $title;
110
111 10
        return $new;
112
    }
113
114
    /**
115
     * The HTML attributes for the widget title tag. The following special options are recognized.
116
     *
117
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
118
     *
119
     * @param array $options
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
     * @param array $options
135
     */
136 1
    public function bodyOptions(array $options): self
137
    {
138 1
        $new = clone $this;
139 1
        $new->bodyOptions = $options;
140
141 1
        return $new;
142
    }
143
144
    /**
145
     * The HTML attributes for the widget close button tag. The following special options are recognized.
146
     *
147
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
148
     *
149
     * @param array $options
150
     */
151
    public function closeButtonOptions(array $options): self
152
    {
153
        $new = clone $this;
154
        $new->closeButtonOptions = $options;
155
156
        return $new;
157
    }
158
159 11
    public function begin(): string
160
    {
161 11
        parent::begin();
162
163 11
        $options = $this->options;
164 11
        $bodyOptions = $this->bodyOptions;
165 11
        $tag = ArrayHelper::remove($options, 'tag', 'div');
166 11
        $bodyTag = ArrayHelper::remove($bodyOptions, 'tag', 'div');
167
168 11
        Html::addCssClass($options, ['widget' => 'offcanvas', 'placement' => $this->placement]);
169 11
        Html::addCssClass($bodyOptions, ['widget' => 'offcanvas-body']);
170
171 11
        $options['id'] = $this->getId();
172 11
        $options['tabindex'] = -1;
173
174 11
        if (!empty($this->title)) {
175 9
            if (isset($this->titleOptions['id'])) {
176
                $options['aria-labelledby'] = $this->titleOptions['id'];
177 9
            } elseif ($options['id']) {
178 9
                $options['aria-labelledby'] = $options['id'] . '-title';
179
            }
180
        }
181
182 11
        if ($this->scroll) {
183 1
            $options['data-bs-scroll'] = 'true';
184
        }
185
186 11
        if ($this->withoutBackdrop) {
187 1
            $options['data-bs-backdrop'] = 'false';
188
        }
189
190 11
        $html = Html::openTag($tag, $options);
191 11
        $html .= $this->renderHeader();
192 11
        $html .= Html::openTag($bodyTag, $bodyOptions);
193
194 11
        return $html;
195
    }
196
197 11
    public function render(): string
198
    {
199 11
        $tag = $this->options['tag'] ?? 'div';
200 11
        $bodyTag = $this->bodyOptions['tag'] ?? 'div';
201
202 11
        return Html::closeTag($bodyTag) . Html::closeTag($tag);
203
    }
204
205
    /**
206
     * Renders offcanvas header.
207
     *
208
     * @return string the rendering header.
209
     */
210 11
    private function renderHeader(): string
211
    {
212 11
        $options = $this->headerOptions;
213 11
        $tag = ArrayHelper::remove($options, 'tag', 'header');
214
215 11
        Html::addCssClass($options, ['widget' => 'offcanvas-header']);
216
217 11
        $title = (string) $this->renderTitle();
218 11
        $closeButton = $this->renderCloseButton();
219
220 11
        return Html::tag($tag, $title . $closeButton, $options)
221 11
            ->encode(false)
222 11
            ->render();
223
    }
224
225
    /**
226
     * Renders offcanvas title.
227
     *
228
     * @return string|null the rendering header.
229
     */
230 11
    private function renderTitle(): ?string
231
    {
232 11
        if ($this->title === null) {
233 2
            return null;
234
        }
235
236 10
        $options = $this->titleOptions;
237 10
        $tag = ArrayHelper::remove($options, 'tag', 'h5');
238 10
        $encode = ArrayHelper::remove($options, 'encode');
239
240 10
        Html::addCssClass($options, ['offcanvas-title']);
241
242 10
        if (!isset($options['id']) && $id = $this->getId()) {
243 10
            $options['id'] = $id . '-title';
244
        }
245
246 10
        return Html::tag($tag, $this->title, $options)
247 10
            ->encode($encode)
248 10
            ->render();
249
    }
250
251
    /**
252
     * Renders offcanvas close button.
253
     *
254
     * @return string the rendering close button.
255
     */
256 11
    private function renderCloseButton(): string
257
    {
258 11
        $options = $this->closeButtonOptions;
259 11
        $label = ArrayHelper::remove($options, 'label', '');
260 11
        $encode = ArrayHelper::remove($options, 'encode');
261
262 11
        $options['type'] = 'button';
263 11
        $options['aria-label'] = 'Close';
264 11
        $options['data-bs-dismiss'] = 'offcanvas';
265
266 11
        return Html::button($label, $options)
267 11
            ->encode($encode)
268 11
            ->render();
269
    }
270
}
271