Issues (17)

src/Collapse.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bootstrap5;
6
7
use Stringable;
8
use Yiisoft\Arrays\ArrayHelper;
9
use Yiisoft\Html\Html;
10
11
use function array_key_exists;
12
13
/**
14
 * Collapse renders an collapse bootstrap JavaScript component.
15
 *
16
 * For example
17
 *
18
 * $collapse =Collapse::widget()
19
 *      ->withTitle('Foo')
20
 *      ->withContent('Bar');
21
 *
22
 * echo $collapse->render();
23
 *
24
 * Or
25
 *
26
 * $collapse = $collapse->withToggle(false);
27
 *
28
 * echo '<p>' . $collapse->renderToggle() . '</p><div>Some other content</div>' . $collapse->render();
29
 */
30
final class Collapse extends AbstractToggleWidget
31
{
32
    private array $options = [];
33
    private array $bodyOptions = [
34
        'tag' => 'div',
35
    ];
36
    private string|Stringable $content = '';
37
    private bool $horizontal = false;
38
    private bool $collapsed = false;
39
    private ?string $tagName = null;
40
41 41
    public function getId(?string $suffix = '-collapse'): ?string
42
    {
43 41
        return $this->options['id'] ?? parent::getId($suffix);
44
    }
45
46 41
    protected function toggleComponent(): string
47
    {
48 41
        return 'collapse';
49
    }
50
51 34
    public function withOptions(array $options): self
52
    {
53 34
        $new = clone $this;
54 34
        $new->options = $options;
55
56 34
        return $new;
57
    }
58
59 35
    public function withBodyOptions(array $options): self
60
    {
61 35
        $new = clone $this;
62 35
        $new->bodyOptions = $options;
63
64 35
        if (!array_key_exists('tag', $options)) {
65 11
            $new->bodyOptions['tag'] = 'div';
66
        }
67
68 35
        return $new;
69
    }
70
71 18
    public function withContent(string|Stringable $content): self
72
    {
73 18
        $new = clone $this;
74 18
        $new->content = $content;
75
76 18
        return $new;
77
    }
78
79 2
    public function withHorizontal(bool $horizontal): self
80
    {
81 2
        $new = clone $this;
82 2
        $new->horizontal = $horizontal;
83
84 2
        return $new;
85
    }
86
87 12
    public function withCollapsed(bool $collapsed): self
88
    {
89 12
        $new = clone $this;
90 12
        $new->collapsed = $collapsed;
91
92 12
        return $new;
93
    }
94
95 40
    private function prepareOptions(): array
96
    {
97 40
        $options = $this->options;
98 40
        $options['id'] = $this->getId();
99
100 40
        $classNames = [
101 40
            'widget' => 'collapse',
102 40
        ];
103
104 40
        if ($this->horizontal) {
105 2
            $classNames[] = 'collapse-horizontal';
106
        }
107
108 40
        if ($this->collapsed) {
109 7
            $classNames[] = 'show';
110
        }
111
112 40
        Html::addCssClass($options, $classNames);
113
114 40
        return $options;
115
    }
116
117 40
    private function prepareBodyOptions(): array
118
    {
119 40
        $options = $this->bodyOptions;
120 40
        Html::addCssClass($options, ['widget' => 'card card-body']);
121
122 40
        return $options;
123
    }
124
125 41
    protected function prepareToggleOptions(): array
126
    {
127 41
        [$tagName, $options, $encode] = parent::prepareToggleOptions();
128
129 41
        $options['aria-expanded'] = $this->collapsed ? 'true' : 'false';
130
131 41
        return [$tagName, $options, $encode];
132
    }
133
134 22
    public function begin(): ?string
135
    {
136 22
        parent::begin();
137
138 22
        $options = $this->prepareOptions();
139 22
        $bodyOptions = $this->prepareBodyOptions();
140 22
        $this->tagName = ArrayHelper::remove($options, 'tag', 'div');
141 22
        $html = $this->renderToggle ? $this->renderToggle() : '';
142 22
        $html .= Html::openTag($this->tagName, $options);
0 ignored issues
show
It seems like $this->tagName can also be of type null; however, parameter $name of Yiisoft\Html\Html::openTag() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
        $html .= Html::openTag(/** @scrutinizer ignore-type */ $this->tagName, $options);
Loading history...
143
144 22
        if ($bodyTag = ArrayHelper::remove($bodyOptions, 'tag')) {
145 1
            $html .= Html::openTag($bodyTag, $bodyOptions);
146
        }
147
148 22
        return $html;
149
    }
150
151
    /**
152
     * @inheritDoc
153
     */
154 40
    public function render(): string
155
    {
156 40
        if ($tagName = $this->tagName) {
157 22
            $this->tagName = null;
158
159 22
            if (isset($this->bodyOptions['tag'])) {
160 1
                return Html::closeTag($this->bodyOptions['tag']) . Html::closeTag($tagName);
161
            }
162
163 21
            return Html::closeTag($tagName);
164
        }
165
166 18
        if ($this->renderToggle) {
167 5
            return $this->renderToggle() . $this->renderCollapse();
168
        }
169
170 13
        return $this->renderCollapse();
171
    }
172
173 18
    private function renderCollapse(): string
174
    {
175 18
        $options = $this->prepareOptions();
176 18
        $tagName = ArrayHelper::remove($options, 'tag', 'div');
177
178 18
        return Html::tag($tagName, $this->renderBody(), $options)
179 18
            ->encode(false)
180 18
            ->render();
181
    }
182
183 18
    private function renderBody(): string
184
    {
185 18
        $options = $this->prepareBodyOptions();
186 18
        $tagName = ArrayHelper::remove($options, 'tag', 'div');
187 18
        $encode = ArrayHelper::remove($options, 'encode');
188
189 18
        if ($tagName === null) {
190 2
            return $encode ? Html::encode($this->content) : (string) $this->content;
191
        }
192
193 16
        return Html::tag($tagName, $this->content, $options)
194 16
            ->encode($encode)
195 16
            ->render();
196
    }
197
}
198