',
+ * ]);
+ *
+ * ActiveForm::end();
+ * ```
+ *
+ * @see ActiveForm
+ * @see https://getbootstrap.com/docs/5.1/components/forms/
+ *
+ * @author Michael Härtl
+ * @author Simon Karlen
+ */
+class ActiveField extends \yii\widgets\ActiveField
+{
+ /**
+ * @var bool whether to render [[checkboxList()]] and [[radioList()]] inline.
+ */
+ public bool $inline = false;
+ /**
+ * @var string|null optional template to render the `{input}` placeholder content
+ */
+ public ?string $inputTemplate = null;
+ /**
+ * @var array options for the wrapper tag, used in the `{beginWrapper}` placeholder
+ */
+ public array $wrapperOptions = [];
+ /**
+ * {@inheritdoc}
+ */
+ public $options = ['class' => ['widget' => 'mb-3']];
+ /**
+ * {@inheritdoc}
+ */
+ public $inputOptions = ['class' => ['widget' => 'form-control']];
+ /**
+ * @var array the default options for the input checkboxes. The parameter passed to individual
+ * input methods (e.g. [[checkbox()]]) will be merged with this property when rendering the input tag.
+ *
+ * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $checkOptions = [
+ 'class' => ['widget' => 'form-check-input'],
+ 'labelOptions' => [
+ 'class' => ['widget' => 'form-check-label'],
+ ],
+ ];
+ /**
+ * @var array the default options for the input radios. The parameter passed to individual
+ * input methods (e.g. [[radio()]]) will be merged with this property when rendering the input tag.
+ *
+ * If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $radioOptions = [
+ 'class' => ['widget' => 'form-check-input'],
+ 'labelOptions' => [
+ 'class' => ['widget' => 'form-check-label'],
+ ],
+ ];
+ /**
+ * {@inheritdoc}
+ */
+ public $errorOptions = ['class' => 'invalid-feedback'];
+ /**
+ * {@inheritdoc}
+ */
+ public $labelOptions = ['class' => ['widget' => 'form-label']];
+ /**
+ * {@inheritdoc}
+ */
+ public $hintOptions = ['class' => ['widget' => 'form-text', 'text-muted'], 'tag' => 'div'];
+ /**
+ * @var array|null CSS grid classes for horizontal layout. This must be an array with these keys:
+ * - 'offset' the offset grid class to append to the wrapper if no label is rendered
+ * - 'label' the label grid class
+ * - 'wrapper' the wrapper grid class
+ * - 'error' the error grid class
+ * - 'hint' the hint grid class
+ */
+ public array|null $horizontalCssClasses = [];
+ /**
+ * @var string the template for checkboxes in default layout
+ */
+ public string $checkTemplate = "
\n{input}\n{label}\n{error}\n{hint}\n
";
+ /**
+ * @var string the template forswitches (custom checkboxes) in default layout
+ */
+ public string $switchTemplate = "
\n{input}\n{label}\n{error}\n{hint}\n
";
+ /**
+ * @var string the template for radios in default layout
+ */
+ public string $radioTemplate = "
\n{input}\n{label}\n{error}\n{hint}\n
";
+ /**
+ * @var string the template for checkboxes and radios in horizontal layout
+ */
+ public string $checkHorizontalTemplate = "{beginWrapper}\n
\n{input}\n{label}\n{error}\n{hint}\n
\n{endWrapper}";
+ /**
+ * @var string the template for switches (custom checkboxes) in horizontal layout
+ */
+ public string $switchHorizontalTemplate = "{beginWrapper}\n
\n{input}\n{label}\n{error}\n{hint}\n
\n{endWrapper}";
+ /**
+ * @var string the template for checkboxes and radios in horizontal layout
+ */
+ public string $radioHorizontalTemplate = "{beginWrapper}\n
\n{input}\n{label}\n{error}\n{hint}\n
\n{endWrapper}";
+ /**
+ * @var string the `enclosed by label` template for checkboxes and radios in default layout
+ */
+ public string $checkEnclosedTemplate = "
\n";
+ /**
+ * @var array the HTML attributes for the widget nav container tag.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $navOptions = ['aria' => ['label' => 'breadcrumb']];
+
+ /**
+ * {@inheritDoc}
+ */
+ public function run(): string
+ {
+ if (empty($this->links)) {
+ return '';
+ }
+
+ // Normalize links
+ $links = [];
+ foreach ($this->links as $key => $value) {
+ if (is_array($value)) {
+ $links[] = $value;
+ } else {
+ $links[] = ['label' => $value, 'url' => is_string($key) ? $key : null];
+ }
+ }
+ $this->links = $links;
+ unset($links);
+
+ if ($this->homeLink === []) {
+ $this->homeLink = null;
+ }
+
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = "{$this->getId()}-breadcrumb";
+ }
+ Html::addCssClass($this->options, ['widget' => 'breadcrumb']);
+
+ // parent method not return result
+ ob_start();
+ parent::run();
+ $content = ob_get_clean();
+
+ return Html::tag('nav', $content, $this->navOptions);
+ }
+
+ /**
+ * The template used to render each active item in the breadcrumbs. The token `{link}` will be replaced with the
+ * actual HTML link for each active item.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function activeItemTemplate(string $value): self
+ {
+ $this->activeItemTemplate = $value;
+
+ return $this;
+ }
+
+ /**
+ * Whether to HTML-encode the link labels.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function encodeLabels(bool $value): self
+ {
+ $this->encodeLabels = $value;
+
+ return $this;
+ }
+
+ /**
+ * The first hyperlink in the breadcrumbs (called home link).
+ *
+ * Please refer to {@see links} on the format of the link.
+ *
+ * If this property is not set, it will default to a link pointing with the label 'Home.'
+ * If this property is false, the home link will not be rendered.
+ *
+ * @param array|bool $value
+ *
+ * @return $this
+ */
+ public function homeLink(bool|array $value): self
+ {
+ $this->homeLink = $value;
+
+ return $this;
+ }
+
+ /**
+ * The template used to render each inactive item in the breadcrumbs. The token `{link}` will be replaced with the
+ * actual HTML link for each inactive item.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function itemTemplate(string $value): self
+ {
+ $this->itemTemplate = $value;
+
+ return $this;
+ }
+
+ /**
+ * List of links to appear in the breadcrumbs. If this property is empty, the widget will not render anything.
+ * Each array element represents a single item in the breadcrumbs with the following structure.
+ *
+ * @param array $value
+ *
+ * @return $this
+ */
+ public function links(array $value): self
+ {
+ $this->links = $value;
+
+ return $this;
+ }
+
+ /**
+ * The HTML attributes for the widget nav container tag.
+ *
+ * {@see \yii\helpers\Html::renderTagAttributes()} for details on how attributes are being rendered.
+ *
+ * @param array $value
+ *
+ * @return $this
+ */
+ public function navOptions(array $value): self
+ {
+ $this->navOptions = $value;
+
+ return $this;
+ }
+
+ /**
+ * The HTML attributes for the widget container tag. The following special options are recognized.
+ *
+ * {@see \yii\helpers\Html::renderTagAttributes()} for details on how attributes are being rendered.
+ *
+ * @param array $value
+ *
+ * @return $this
+ */
+ public function options(array $value): self
+ {
+ $this->options = $value;
+
+ return $this;
+ }
+
+ /**
+ * The name of the breadcrumb container tag.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function tag(string $value): self
+ {
+ $this->tag = $value;
+
+ return $this;
+ }
+}
diff --git a/src/Button.php b/src/Button.php
new file mode 100644
index 0000000..830aba5
--- /dev/null
+++ b/src/Button.php
@@ -0,0 +1,74 @@
+ 'Action',
+ * 'options' => ['class' => 'btn-lg'],
+ * ]);
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/buttons/
+ *
+ * @author Antonio Ramirez
+ */
+class Button extends Widget
+{
+ /**
+ * @var string the tag to use to render the button
+ */
+ public string $tagName = 'button';
+ /**
+ * @var string the button label
+ */
+ public string $label = 'Button';
+ /**
+ * @var bool whether the label should be HTML-encoded.
+ */
+ public bool $encodeLabel = true;
+
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ *
+ * @throws InvalidConfigException
+ */
+ public function init(): void
+ {
+ parent::init();
+ $this->clientOptions = [];
+ Html::addCssClass($this->options, ['widget' => 'btn']);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ $this->registerPlugin('button');
+ return Html::tag(
+ $this->tagName,
+ $this->encodeLabel ? Html::encode($this->label) : $this->label,
+ $this->options
+ );
+ }
+}
diff --git a/src/ButtonDropdown.php b/src/ButtonDropdown.php
new file mode 100644
index 0000000..c2e5cc1
--- /dev/null
+++ b/src/ButtonDropdown.php
@@ -0,0 +1,214 @@
+ 'Action',
+ * 'dropdown' => [
+ * 'items' => [
+ * ['label' => 'DropdownA', 'url' => '/'],
+ * ['label' => 'DropdownB', 'url' => '#'],
+ * ],
+ * ],
+ * ]);
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/buttons/
+ * @see https://getbootstrap.com/docs/5.1/components/dropdowns/
+ *
+ * @author Antonio Ramirez
+ */
+class ButtonDropdown extends Widget
+{
+ /**
+ * The css class part of the dropdown
+ */
+ public const DIRECTION_DOWN = 'down';
+ /**
+ * The css class part of dropleft
+ */
+ public const DIRECTION_LEFT = 'left';
+ /**
+ * The css class part of dropright
+ */
+ public const DIRECTION_RIGHT = 'right';
+ /**
+ * The css class part of dropup
+ */
+ public const DIRECTION_UP = 'up';
+
+ /**
+ * @var string|null the button label
+ */
+ public string|null $label = null;
+ /**
+ * @var array the HTML attributes of the button.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $buttonOptions = [];
+ /**
+ * @var array the configuration array for [[Dropdown]].
+ */
+ public array $dropdown = [];
+ /**
+ * @var string the drop-direction of the widget
+ *
+ * Possible values are 'left,' 'right,' 'up', or 'down' (default)
+ */
+ public string $direction = self::DIRECTION_DOWN;
+ /**
+ * @var bool whether to display a group of split-styled button groups.
+ */
+ public bool $split = false;
+ /**
+ * @var string the tag to use to render the button
+ */
+ public string $tagName = 'button';
+ /**
+ * @var bool whether the label should be HTML-encoded.
+ */
+ public bool $encodeLabel = true;
+ /**
+ * @var string name of a class to use for rendering dropdowns withing this widget. Defaults to [[Dropdown]].
+ */
+ public string $dropdownClass = Dropdown::class;
+ /**
+ * @var bool whether to render the container using the [[options]] as HTML attributes. If set to `false`,
+ * the container element enclosing the button and dropdown will NOT be rendered.
+ */
+ public bool $renderContainer = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+
+ if (!isset($this->buttonOptions['id'])) {
+ $this->buttonOptions['id'] = $this->options['id'] . '-button';
+ }
+ if ($this->label === null) {
+ $this->label = Yii::t('yii/bootstrap5', 'Button');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws Throwable
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ $html = $this->renderButton() . "\n" . $this->renderDropdown();
+
+ if ($this->renderContainer) {
+ Html::addCssClass($this->options, ['widget' => 'drop' . $this->direction, 'btn-group']);
+ $options = $this->options;
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ $html = Html::tag($tag, $html, $options);
+ }
+
+ // Set options id to button options id to ensure the correct css selector in plugin initialisation
+ $this->options['id'] = $this->buttonOptions['id'];
+
+ $this->registerPlugin('dropdown');
+
+ return $html;
+ }
+
+ /**
+ * Generates the button dropdown.
+ *
+ * @throws Throwable
+ *
+ * @return string the rendering result.
+ */
+ protected function renderButton(): string
+ {
+ Html::addCssClass($this->buttonOptions, ['widget' => 'btn']);
+ $label = $this->label;
+ if ($this->encodeLabel) {
+ $label = Html::encode($label);
+ }
+
+ $buttonOptions = $this->buttonOptions;
+
+ if ($this->split) {
+ $this->buttonOptions['data'] = ['bs-toggle' => 'dropdown'];
+ $this->buttonOptions['aria'] = ['expanded' => 'false'];
+ Html::addCssClass($this->buttonOptions, ['toggle' => 'dropdown-toggle dropdown-toggle-split']);
+ unset($buttonOptions['id']);
+ $splitButton = Button::widget([
+ 'label' => '' . Yii::t('yii/bootstrap5', 'Toggle Dropdown') . '',
+ 'encodeLabel' => false,
+ 'options' => $this->buttonOptions,
+ 'view' => $this->getView(),
+ ]);
+ } else {
+ Html::addCssClass($buttonOptions, ['toggle' => 'dropdown-toggle']);
+ $buttonOptions['data'] = ['bs-toggle' => 'dropdown'];
+ $buttonOptions['aria'] = ['expanded' => 'false'];
+ $splitButton = '';
+ }
+
+ if (isset($buttonOptions['href'])) {
+ if (is_array($buttonOptions['href'])) {
+ $buttonOptions['href'] = Url::to($buttonOptions['href']);
+ }
+ } elseif ($this->tagName === 'a') {
+ $buttonOptions['href'] = '#';
+ $buttonOptions['role'] = 'button';
+ }
+
+ return Button::widget([
+ 'tagName' => $this->tagName,
+ 'label' => $label,
+ 'options' => $buttonOptions,
+ 'encodeLabel' => false,
+ 'view' => $this->getView(),
+ ]) . "\n" . $splitButton;
+ }
+
+ /**
+ * Generates the dropdown menu.
+ *
+ * @throws Throwable
+ *
+ * @return string the rendering result.
+ */
+ protected function renderDropdown(): string
+ {
+ $config = $this->dropdown;
+ $config['clientOptions'] = [];
+ $config['view'] = $this->getView();
+ /** @var Widget $dropdownClass */
+ $dropdownClass = $this->dropdownClass;
+
+ return $dropdownClass::widget($config);
+ }
+}
diff --git a/src/ButtonGroup.php b/src/ButtonGroup.php
new file mode 100644
index 0000000..93c2731
--- /dev/null
+++ b/src/ButtonGroup.php
@@ -0,0 +1,126 @@
+ [
+ * ['label' => 'A'],
+ * ['label' => 'B'],
+ * ['label' => 'C', 'visible' => false],
+ * ]
+ * ]);
+ *
+ * // button group with an item as a string
+ * echo ButtonGroup::widget([
+ * 'buttons' => [
+ * Button::widget(['label' => 'A']),
+ * ['label' => 'B'],
+ * ]
+ * ]);
+ * ```
+ *
+ * Pressing on the button should be handled via JavaScript. See the following for details:
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/buttons/
+ * @see https://getbootstrap.com/docs/5.1/components/button-group/
+ *
+ * @author Antonio Ramirez
+ * @author Simon Karlen
+ */
+class ButtonGroup extends Widget
+{
+ /**
+ * @var array list of buttons. Each array element represents a single button
+ * which can be specified as a string or an array of the following structure:
+ *
+ * - label: string, required, the button label.
+ * - options: array, optional, the HTML attributes of the button.
+ * - visible: bool, optional, whether this button is visible. Defaults to true.
+ */
+ public array $buttons = [];
+ /**
+ * @var bool whether to HTML-encode the button labels.
+ */
+ public bool $encodeLabels = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+ Html::addCssClass($this->options, ['widget' => 'btn-group']);
+ if (!isset($this->options['role'])) {
+ $this->options['role'] = 'group';
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws Throwable
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ BootstrapAsset::register($this->getView());
+
+ return Html::tag('div', $this->renderButtons(), $this->options);
+ }
+
+ /**
+ * Generates the buttons that compound the group as specified on [[buttons]].
+ *
+ * @throws Throwable
+ *
+ * @return string the rendering result.
+ */
+ protected function renderButtons(): string
+ {
+ $buttons = [];
+ foreach ($this->buttons as $button) {
+ if (is_array($button)) {
+ $visible = ArrayHelper::remove($button, 'visible', true);
+ if ($visible === false) {
+ continue;
+ }
+
+ $button['view'] = $this->getView();
+ if (!isset($button['encodeLabel'])) {
+ $button['encodeLabel'] = $this->encodeLabels;
+ }
+ if (!isset($button['options']['type'])) {
+ ArrayHelper::setValue($button, 'options.type', 'button');
+ }
+ $buttons[] = Button::widget($button);
+ } else {
+ $buttons[] = $button;
+ }
+ }
+
+ return implode("\n", $buttons);
+ }
+}
diff --git a/src/ButtonToolbar.php b/src/ButtonToolbar.php
new file mode 100644
index 0000000..f7f3f30
--- /dev/null
+++ b/src/ButtonToolbar.php
@@ -0,0 +1,126 @@
+ [
+ * [
+ * 'buttons' => [
+ * ['label' => '1', 'options' => ['class' => ['btn-secondary']]],
+ * ['label' => '2', 'options' => ['class' => ['btn-secondary']]],
+ * ['label' => '3', 'options' => ['class' => ['btn-secondary']]],
+ * ['label' => '4', 'options' => ['class' => ['btn-secondary']]]
+ * ],
+ * 'class' => ['mr-2']
+ * ],
+ * [
+ * 'buttons' => [
+ * ['label' => '5', 'options' => ['class' => ['btn-secondary']]],
+ * ['label' => '6', 'options' => ['class' => ['btn-secondary']]],
+ * ['label' => '7', 'options' => ['class' => ['btn-secondary']]]
+ * ],
+ * 'class' => ['mr-2']
+ * ],
+ * [
+ * 'buttons' => [
+ * ['label' => '8', 'options' => ['class' => ['btn-secondary']]]
+ * ]
+ * ]
+ * ]
+ * ]);
+ * ```
+ *
+ * Pressing on the button should be handled via JavaScript. See the following for details:
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/buttons/
+ * @see https://getbootstrap.com/docs/5.1/components/button-group/#button-toolbar
+ *
+ * @author Simon Karlen
+ */
+class ButtonToolbar extends Widget
+{
+ /**
+ * @var array list of buttons groups. Each array element represents a single group
+ * which can be specified as a string or an array of the following structure:
+ *
+ * - buttons: array list of buttons. Either as array or string representation
+ * - options: array optional, the HTML attributes of the button group.
+ * - encodeLabels: bool whether to HTML-encode the button labels.
+ */
+ public array $buttonGroups = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+ Html::addCssClass($this->options, ['widget' => 'btn-toolbar']);
+ if (!isset($this->options['role'])) {
+ $this->options['role'] = 'toolbar';
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws Throwable
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ BootstrapAsset::register($this->getView());
+
+ return Html::tag('div', $this->renderButtonGroups(), $this->options);
+ }
+
+ /**
+ * Generates the button groups that compound the toolbar as specified on [[buttonGroups]].
+ *
+ * @throws Throwable
+ *
+ * @return string the rendering result.
+ */
+ protected function renderButtonGroups(): string
+ {
+ $buttonGroups = [];
+ foreach ($this->buttonGroups as $group) {
+ if (is_array($group)) {
+ $group['view'] = $this->getView();
+
+ if (!isset($group['buttons'])) {
+ continue;
+ }
+
+ $buttonGroups[] = ButtonGroup::widget($group);
+ } else {
+ $buttonGroups[] = $group;
+ }
+ }
+
+ return implode("\n", $buttonGroups);
+ }
+}
diff --git a/src/Carousel.php b/src/Carousel.php
new file mode 100644
index 0000000..942a259
--- /dev/null
+++ b/src/Carousel.php
@@ -0,0 +1,238 @@
+ [
+ * // the item contains only the image
+ * '',
+ * // equivalent to the above
+ * ['content' => ''],
+ * // the item contains both the image and the caption
+ * [
+ * 'content' => '',
+ * 'caption' => '
This is title
This is the caption text
',
+ * 'captionOptions' => ['class' => ['d-none', 'd-md-block']]
+ * 'options' => [...],
+ * ],
+ * ]
+ * ]);
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/carousel/
+ *
+ * @author Antonio Ramirez
+ * @author Simon Karlen
+ */
+class Carousel extends Widget
+{
+ /**
+ * @var array|null the labels for the previous and the next control buttons.
+ * If null, it means the previous and the next control buttons should not be displayed.
+ */
+ public ?array $controls = [
+ 'Previous',
+ 'Next',
+ ];
+ /**
+ * @var bool whether carousel indicators ( tag with anchors to items) should be displayed or not.
+ */
+ public bool $showIndicators = true;
+ /**
+ * @var array list of slides in the carousel. Each array element represents a single
+ * slide with the following structure:
+ *
+ * ```php
+ * [
+ * // required, slide content (HTML), such as an image tag
+ * 'content' => '',
+ * // optional, the caption (HTML) of the slide
+ * 'caption' => '
This is title
This is the caption text
',
+ * // optional the HTML attributes of the slide container
+ * 'options' => [],
+ * ]
+ * ```
+ */
+ public array $items = [];
+ /**
+ * @var bool Animate slides with a fade transition instead of a slide. Defaults to `false`
+ */
+ public bool $crossfade = false;
+ /**
+ * {@inheritdoc}
+ */
+ public array $options = ['data' => ['bs-ride' => 'carousel']];
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws InvalidConfigException
+ */
+ public function init(): void
+ {
+ parent::init();
+ Html::addCssClass($this->options, ['widget' => 'carousel slide']);
+ if ($this->crossfade) {
+ Html::addCssClass($this->options, ['animation' => 'carousel-fade']);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidConfigException
+ */
+ public function run(): string
+ {
+ $this->registerPlugin('carousel');
+
+ return implode("\n", [
+ Html::beginTag('div', $this->options),
+ $this->renderIndicators(),
+ $this->renderItems(),
+ $this->renderControls(),
+ Html::endTag('div'),
+ ]) . "\n";
+ }
+
+ /**
+ * Renders carousel indicators.
+ *
+ * @return string the rendering result
+ */
+ public function renderIndicators(): string
+ {
+ if ($this->showIndicators === false) {
+ return '';
+ }
+ $indicators = [];
+ for ($i = 0, $count = count($this->items); $i < $count; $i++) {
+ $options = [
+ 'data' => [
+ 'bs-target' => '#' . $this->options['id'],
+ 'bs-slide-to' => $i,
+ ],
+ 'type' => 'button',
+ ];
+ if ($i === 0) {
+ Html::addCssClass($options, ['activate' => 'active']);
+ $options['aria']['current'] = 'true';
+ }
+ $indicators[] = Html::tag('button', '', $options);
+ }
+
+ return Html::tag('div', implode("\n", $indicators), ['class' => ['carousel-indicators']]);
+ }
+
+ /**
+ * Renders carousel items as specified on [[items]].
+ *
+ * @throws InvalidConfigException
+ *
+ * @return string the rendering result
+ */
+ public function renderItems(): string
+ {
+ $items = [];
+
+ foreach ($this->items as $i => $iValue) {
+ $items[] = $this->renderItem($iValue, $i);
+ }
+
+ return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']);
+ }
+
+ /**
+ * Renders a single carousel item
+ *
+ * @param array|string $item a single item from [[items]]
+ * @param int $index the item index as the first item should be set to `active`
+ *
+ *@throws Exception
+ * @throws InvalidConfigException if the item is invalid
+ *
+ * @return string the rendering result
+ */
+ public function renderItem(array|string $item, int $index): string
+ {
+ if (is_string($item)) {
+ $content = $item;
+ $caption = null;
+ $options = [];
+ } elseif (isset($item['content'])) {
+ $content = $item['content'];
+ $caption = ArrayHelper::getValue($item, 'caption');
+ if ($caption !== null) {
+ $captionOptions = ArrayHelper::remove($item, 'captionOptions', []);
+ Html::addCssClass($captionOptions, ['widget' => 'carousel-caption']);
+
+ $caption = Html::tag('div', $caption, $captionOptions);
+ }
+ $options = ArrayHelper::getValue($item, 'options', []);
+ } else {
+ throw new InvalidConfigException('The "content" option is required.');
+ }
+
+ Html::addCssClass($options, ['widget' => 'carousel-item']);
+ if ($index === 0) {
+ Html::addCssClass($options, ['activate' => 'active']);
+ }
+
+ return Html::tag('div', $content . "\n" . $caption, $options);
+ }
+
+ /**
+ * Renders previous and next control buttons.
+ *
+ * @throws InvalidConfigException if [[controls]] is invalid.
+ *
+ * @return string The rendered controls
+ */
+ public function renderControls(): string
+ {
+ if (isset($this->controls[0], $this->controls[1])) {
+ return Html::button($this->controls[0], [
+ 'class' => 'carousel-control-prev',
+ 'data' => [
+ 'bs-target' => '#' . $this->options['id'],
+ 'bs-slide' => 'prev',
+ ],
+ 'type' => 'button',
+ ]) . "\n"
+ . Html::button($this->controls[1], [
+ 'class' => 'carousel-control-next',
+ 'data' => [
+ 'bs-target' => '#' . $this->options['id'],
+ 'bs-slide' => 'next',
+ ],
+ 'type' => 'button',
+ ]);
+ }
+ if ($this->controls === false) {
+ return '';
+ }
+ throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.');
+ }
+}
diff --git a/src/Dropdown.php b/src/Dropdown.php
new file mode 100644
index 0000000..b22957f
--- /dev/null
+++ b/src/Dropdown.php
@@ -0,0 +1,184 @@
+
+ * Label
+ * [
+ * ['label' => 'DropdownA', 'url' => '/'],
+ * ['label' => 'DropdownB', 'url' => '#'],
+ * ],
+ * ]);
+ * ?>
+ *
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/dropdowns/
+ *
+ * @author Antonio Ramirez
+ * @author Simon Karlen
+ */
+class Dropdown extends Widget
+{
+ /**
+ * @var array list of menu items in the dropdown. Each array element can be either an HTML string.
+ * or an array representing a single menu with the following structure:
+ *
+ * - label: string, required, the label of the item link.
+ * - encode: bool, optional, whether to HTML-encode item label.
+ * - url: string|array, optional, the URL of the item link. This will be processed by [[\yii\helpers\Url::to()]].
+ * If not set, the item will be treated as a menu header when the item has no sub-menu.
+ * - visible: bool, optional, whether this menu item is visible. Defaults to true.
+ * - disabled: bool, optional, whether this menu item is disabled. Defaults to false.
+ * - linkOptions: array, optional, the HTML attributes of the item link.
+ * - options: array, optional, the HTML attributes of the item.
+ * - active: bool, optional, whether the item should be on active state or not.
+ * - items: array, optional, the submenu items. The structure is the same as this property.
+ * Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it.
+ * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified, it will be
+ * merged with [[submenuOptions]].
+ *
+ * To insert divider use `-`.
+ */
+ public array $items = [];
+ /**
+ * @var bool whether the labels for header items should be HTML-encoded.
+ */
+ public bool $encodeLabels = true;
+ /**
+ * @var array|null the HTML attributes for sub-menu container tags.
+ */
+ public ?array $submenuOptions = [];
+
+ /**
+ * {@inheritDoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+ Html::addCssClass($this->options, ['widget' => 'dropdown-menu']);
+ }
+
+ /**
+ * Renders the widget.
+ *
+ * @throws InvalidConfigException
+ * @throws Throwable
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ BootstrapPluginAsset::register($this->getView());
+ $this->registerClientEvents('dropdown');
+
+ return $this->renderItems($this->items, $this->options);
+ }
+
+ /**
+ * Renders menu items.
+ *
+ * @param array $items the menu items to be rendered
+ * @param array $options the container HTML attributes
+ *
+ * @throws Exception
+ * @throws Throwable
+ * @throws InvalidConfigException if the label option is not specified in one of the items.
+ *
+ * @return string the rendering result.
+ */
+ protected function renderItems(array $items, array $options = []): string
+ {
+ $lines = [];
+ foreach ($items as $item) {
+ if (is_string($item)) {
+ $lines[] = ($item === '-')
+ ? Html::tag('hr', '', ['class' => 'dropdown-divider'])
+ : $item;
+ continue;
+ }
+ if (isset($item['visible']) && !$item['visible']) {
+ continue;
+ }
+ if (!array_key_exists('label', $item)) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $encodeLabel = $item['encode'] ?? $this->encodeLabels;
+ $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
+ $itemOptions = ArrayHelper::getValue($item, 'options', []);
+ $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
+ $active = ArrayHelper::getValue($item, 'active', false);
+ $disabled = ArrayHelper::getValue($item, 'disabled', false);
+
+ Html::addCssClass($linkOptions, ['widget' => 'dropdown-item']);
+ if ($disabled) {
+ ArrayHelper::setValue($linkOptions, 'tabindex', '-1');
+ ArrayHelper::setValue($linkOptions, 'aria.disabled', 'true');
+ Html::addCssClass($linkOptions, ['disable' => 'disabled']);
+ } elseif ($active) {
+ ArrayHelper::setValue($linkOptions, 'aria.current', 'true');
+ Html::addCssClass($linkOptions, ['activate' => 'active']);
+ }
+
+ $url = $item['url'] ?? null;
+ if (empty($item['items'])) {
+ if ($url === null) {
+ $content = Html::tag('h6', $label, ['class' => 'dropdown-header']);
+ } else {
+ $content = Html::a($label, $url, $linkOptions);
+ }
+ $lines[] = $content;
+ } else {
+ $submenuOptions = $this->submenuOptions;
+ if (isset($item['submenuOptions'])) {
+ $submenuOptions = array_merge($submenuOptions, $item['submenuOptions']);
+ }
+ Html::addCssClass($submenuOptions, ['widget' => 'dropdown-submenu dropdown-menu']);
+ Html::addCssClass($linkOptions, ['toggle' => 'dropdown-toggle']);
+
+ $lines[] = Html::beginTag('div', array_merge_recursive(['class' => ['dropdown'], 'aria' => ['expanded' => 'false']], $itemOptions));
+ $lines[] = Html::a($label, $url, array_merge_recursive([
+ 'data' => ['bs-toggle' => 'dropdown'],
+ 'aria' => ['expanded' => 'false'],
+ 'role' => 'button',
+ ], $linkOptions));
+ $lines[] = static::widget([
+ 'items' => $item['items'],
+ 'options' => $submenuOptions,
+ 'submenuOptions' => $submenuOptions,
+ 'encodeLabels' => $this->encodeLabels,
+ ]);
+ $lines[] = Html::endTag('div');
+ }
+ }
+
+ return Html::tag('div', implode("\n", $lines), $options);
+ }
+}
diff --git a/src/Example.php b/src/Example.php
deleted file mode 100644
index 067eeb8..0000000
--- a/src/Example.php
+++ /dev/null
@@ -1,13 +0,0 @@
- [
+ * 'definitions' => [
+ * \yii\widgets\LinkPager::class => \yii\bootstrap5\LinkPager::class,
+ * ],
+ * ],
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/pagination/
+ *
+ * @author Simon Karlen
+ *
+ * @property array $pageRange
+ */
+class LinkPager extends Widget
+{
+ /**
+ * @var Pagination|null the pagination object that this pager is associated with.
+ * You must set this property in order to make LinkPager work.
+ */
+ public Pagination|null $pagination = null;
+ /**
+ * @var array HTML attributes for the pager list tag.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $listOptions = ['class' => ['pagination']];
+ /**
+ * @var array HTML attributes which will be applied to all link containers
+ */
+ public array $linkContainerOptions = ['class' => ['page-item']];
+ /**
+ * @var array HTML attributes for the link in a pager container tag.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $linkOptions = ['class' => ['page-link']];
+ /**
+ * @var string the CSS class for the page button.
+ */
+ public string $pageCssClass = 'page-item';
+ /**
+ * @var string the CSS class for the "first" page button.
+ */
+ public string $firstPageCssClass = 'first';
+ /**
+ * @var string the CSS class for the "last" page button.
+ */
+ public string $lastPageCssClass = 'last';
+ /**
+ * @var string the CSS class for the "previous" page button.
+ */
+ public string $prevPageCssClass = 'prev';
+ /**
+ * @var string the CSS class for the "next" page button.
+ */
+ public string $nextPageCssClass = 'next';
+ /**
+ * @var string the CSS class for the active (currently selected) page button.
+ */
+ public string $activePageCssClass = 'active';
+ /**
+ * @var string the CSS class for the disabled page buttons.
+ */
+ public string $disabledPageCssClass = 'disabled';
+ /**
+ * @var array the options for the disabled tag to be generated inside the disabled list element.
+ * In order to customize the html tag, please use the tag key.
+ *
+ * ```php
+ * $disabledListItemSubTagOptions = ['class' => 'disabled-link'];
+ * ```
+ */
+ public array $disabledListItemSubTagOptions = [];
+ /**
+ * @var int maximum number of page buttons that can be displayed. Defaults to 10.
+ */
+ public int $maxButtonCount = 10;
+ /**
+ * @var bool|string the label for the "next" page button. Note that this will NOT be HTML-encoded.
+ * If this property is false, the "next" page button will not be displayed.
+ */
+ public string|bool $nextPageLabel = '»';
+ /**
+ * @var bool|string the text label for the "previous" page button. Note that this will NOT be HTML-encoded.
+ * If this property is false, the "previous" page button will not be displayed.
+ */
+ public string|bool $prevPageLabel = '«';
+ /**
+ * @var bool|string the text label for the "first" page button. Note that this will NOT be HTML-encoded.
+ * If it's specified as true, page number will be used as label.
+ * Default is false that means the "first" page button will not be displayed.
+ */
+ public string|bool $firstPageLabel = false;
+ /**
+ * @var bool|string the text label for the "last" page button. Note that this will NOT be HTML-encoded.
+ * If it's specified as true, page number will be used as label.
+ * Default is false that means the "last" page button will not be displayed.
+ */
+ public string|bool $lastPageLabel = false;
+ /**
+ * @var bool whether to register link tags in the HTML header for prev, next, first and last page.
+ * Defaults to `false` to avoid conflicts when multiple pagers are used on one page.
+ *
+ * @see https://www.w3.org/TR/html401/struct/links.html#h-12.1.2
+ * @see registerLinkTags()
+ */
+ public bool $registerLinkTags = false;
+ /**
+ * @var bool Hide widget when only one page exist.
+ */
+ public bool $hideOnSinglePage = true;
+ /**
+ * @var bool whether to render current page button as disabled.
+ */
+ public bool $disableCurrentPageButton = false;
+
+ /**
+ * Initializes the pager.
+ *
+ * @throws InvalidConfigException
+ */
+ public function init(): void
+ {
+ parent::init();
+
+ if ($this->pagination === null) {
+ throw new InvalidConfigException('The "pagination" property must be set.');
+ }
+ }
+
+ /**
+ * Executes the widget.
+ * This overrides the parent implementation by displaying the generated page buttons.
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ if ($this->registerLinkTags) {
+ $this->registerLinkTags();
+ }
+ $options = $this->options;
+ $tag = ArrayHelper::remove($options, 'tag', 'nav');
+ $html = Html::beginTag($tag, $options);
+ $html .= $this->renderPageButtons();
+ $html .= Html::endTag($tag);
+
+ return $html;
+ }
+
+ /**
+ * Registers relational link tags in the html header for prev, next, first and last page.
+ * These links are generated using [[\yii\data\Pagination::getLinks()]].
+ *
+ * @see https://www.w3.org/TR/html401/struct/links.html#h-12.1.2
+ */
+ protected function registerLinkTags(): void
+ {
+ $view = $this->getView();
+ foreach ($this->pagination->getLinks() as $rel => $href) {
+ $view->registerLinkTag(['rel' => $rel, 'href' => $href], $rel);
+ }
+ }
+
+ /**
+ * Renders the page buttons.
+ *
+ * @return string the rendering result
+ */
+ protected function renderPageButtons(): string
+ {
+ $pageCount = $this->pagination->getPageCount();
+ if ($pageCount < 2 && $this->hideOnSinglePage) {
+ return '';
+ }
+
+ $buttons = [];
+ $currentPage = $this->pagination->getPage();
+
+ // first page
+ $firstPageLabel = $this->firstPageLabel === true ? '1' : $this->firstPageLabel;
+ if ($firstPageLabel !== false) {
+ $buttons[] = $this->renderPageButton(
+ $firstPageLabel,
+ 0,
+ $this->firstPageCssClass,
+ $currentPage <= 0,
+ false
+ );
+ }
+
+ // prev page
+ if ($this->prevPageLabel !== false) {
+ if (($page = $currentPage - 1) < 0) {
+ $page = 0;
+ }
+ $buttons[] = $this->renderPageButton(
+ $this->prevPageLabel,
+ $page,
+ $this->prevPageCssClass,
+ $currentPage <= 0,
+ false
+ );
+ }
+
+ // internal pages
+ [$beginPage, $endPage] = $this->getPageRange();
+ for ($i = $beginPage; $i <= $endPage; ++$i) {
+ $buttons[] = $this->renderPageButton(
+ (string)($i + 1),
+ $i,
+ '',
+ $this->disableCurrentPageButton && $i === $currentPage,
+ $i === $currentPage
+ );
+ }
+
+ // next page
+ if ($this->nextPageLabel !== false) {
+ if (($page = $currentPage + 1) >= $pageCount - 1) {
+ $page = $pageCount - 1;
+ }
+ $buttons[] = $this->renderPageButton(
+ $this->nextPageLabel,
+ $page,
+ $this->nextPageCssClass,
+ $currentPage >= $pageCount - 1,
+ false
+ );
+ }
+
+ // last page
+ $lastPageLabel = $this->lastPageLabel === true ? $pageCount : $this->lastPageLabel;
+ if ($lastPageLabel !== false) {
+ $buttons[] = $this->renderPageButton(
+ (string)$lastPageLabel,
+ $pageCount - 1,
+ $this->lastPageCssClass,
+ $currentPage >= $pageCount - 1,
+ false
+ );
+ }
+
+ $options = $this->listOptions;
+ $tag = ArrayHelper::remove($options, 'tag', 'ul');
+
+ return Html::tag($tag, implode("\n", $buttons), $options);
+ }
+
+ /**
+ * Renders a page button.
+ * You may override this method to customize the generation of page buttons.
+ *
+ * @param string $label the text label for the button
+ * @param int $page the page number
+ * @param string $class the CSS class for the page button.
+ * @param bool $disabled whether this page button is disabled
+ * @param bool $active whether this page button is active
+ *
+ * @return string the rendering result
+ */
+ protected function renderPageButton(string $label, int $page, string $class, bool $disabled, bool $active): string
+ {
+ $options = $this->linkContainerOptions;
+ $linkWrapTag = ArrayHelper::remove($options, 'tag', 'li');
+ Html::addCssClass($options, $class ?: $this->pageCssClass);
+
+ $linkOptions = $this->linkOptions;
+ $linkOptions['data']['page'] = $page;
+
+ if ($active) {
+ $options['aria'] = ['current' => 'page'];
+ Html::addCssClass($options, $this->activePageCssClass);
+ }
+ if ($disabled) {
+ Html::addCssClass($options, $this->disabledPageCssClass);
+ $disabledItemOptions = $this->disabledListItemSubTagOptions;
+ $linkOptions = ArrayHelper::merge($linkOptions, $disabledItemOptions);
+ $linkOptions['tabindex'] = '-1';
+ }
+
+ return Html::tag($linkWrapTag, Html::a($label, $this->pagination->createUrl($page), $linkOptions), $options);
+ }
+
+ /**
+ * @return array the beginning and end pages that need to be displayed.
+ */
+ protected function getPageRange(): array
+ {
+ $currentPage = $this->pagination->getPage();
+ $pageCount = $this->pagination->getPageCount();
+
+ $beginPage = max(0, $currentPage - (int)($this->maxButtonCount / 2));
+ if (($endPage = $beginPage + $this->maxButtonCount - 1) >= $pageCount) {
+ $endPage = $pageCount - 1;
+ $beginPage = max(0, $endPage - $this->maxButtonCount + 1);
+ }
+
+ return [$beginPage, $endPage];
+ }
+}
diff --git a/src/Modal.php b/src/Modal.php
new file mode 100644
index 0000000..c3723e6
--- /dev/null
+++ b/src/Modal.php
@@ -0,0 +1,328 @@
+ 'Hello world',
+ * 'toggleButton' => ['label' => 'click me'],
+ * ]);
+ *
+ * echo 'Say hello...';
+ *
+ * Modal::end();
+ * ~~~
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/modal/
+ *
+ * @author Antonio Ramirez
+ * @author Qiang Xue
+ * @author Simon Karlen
+ */
+class Modal extends Widget
+{
+ /**
+ * The additional css class of extra large modal
+ */
+ public const SIZE_EXTRA_LARGE = 'modal-xl';
+ /**
+ * The additional css class of large modal
+ */
+ public const SIZE_LARGE = 'modal-lg';
+ /**
+ * The additional css class of small modal
+ */
+ public const SIZE_SMALL = 'modal-sm';
+ /**
+ * The additional css class of default modal
+ */
+ public const SIZE_DEFAULT = '';
+
+ /**
+ * @var string the title content in the modal window.
+ */
+ public string $title;
+ /**
+ * @var array additional title options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $titleOptions = [];
+ /**
+ * @var array additional header options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $headerOptions = [];
+ /**
+ * @var array body options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $bodyOptions = [];
+ /**
+ * @var string|null the footer content in the modal window.
+ */
+ public string|null $footer = null;
+ /**
+ * @var array additional footer options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $footerOptions = [];
+ /**
+ * @var string|null the modal size. Can be [[SIZE_LARGE]] or [[SIZE_SMALL]], or empty for default.
+ */
+ public string|null $size = null;
+ /**
+ * @var array|false the options for rendering the close button tag.
+ * The close button is displayed in the header of the modal window. Clicking
+ * on the button will hide the modal window. If this is false, no close button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button.'
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Modal plugin help](https://getbootstrap.com/javascript/#modals)
+ * for the supported HTML attributes.
+ */
+ public array|false $closeButton = [];
+ /**
+ * @var array|false the options for rendering the toggle button tag.
+ * The toggle button is used to toggle the visibility of the modal window.
+ * If this property is false, no toggle button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to 'Show.'
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Modal plugin help](https://getbootstrap.com/javascript/#modals)
+ * for the supported HTML attributes.
+ */
+ public array|false $toggleButton = false;
+ /**
+ * @var bool whether to center the modal vertically
+ *
+ * When true, the modal-dialog-centered class will be added to the modal-dialog
+ */
+ public bool $centerVertical = false;
+ /**
+ * @var bool whether to make the modal body scrollable
+ *
+ * When true, the modal-dialog-scrollable class will be added to the modal-dialog
+ */
+ public bool $scrollable = false;
+ /**
+ * @var array modal dialog options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $dialogOptions = [];
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws InvalidConfigException
+ */
+ public function init(): void
+ {
+ parent::init();
+
+ $this->initOptions();
+
+ echo $this->renderToggleButton() . "\n";
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo Html::beginTag('div', $this->dialogOptions) . "\n";
+ echo Html::beginTag('div', ['class' => 'modal-content']) . "\n";
+ echo $this->renderHeader() . "\n";
+ echo $this->renderBodyBegin() . "\n";
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run(): void
+ {
+ echo "\n" . $this->renderBodyEnd();
+ echo "\n" . $this->renderFooter();
+ echo "\n" . Html::endTag('div'); // modal-content
+ echo "\n" . Html::endTag('div'); // modal-dialog
+ echo "\n" . Html::endTag('div');
+
+ $this->registerPlugin('modal');
+ }
+
+ /**
+ * Renders the header HTML markup of the modal
+ *
+ * @return string the rendering result
+ */
+ protected function renderHeader(): string
+ {
+ $button = $this->renderCloseButton();
+ if (isset($this->title)) {
+ Html::addCssClass($this->titleOptions, ['widget' => 'modal-title']);
+ $header = Html::tag('h5', $this->title, $this->titleOptions);
+ } else {
+ $header = '';
+ }
+
+ if ($button !== null) {
+ $header .= "\n" . $button;
+ } elseif ($header === '') {
+ return '';
+ }
+ Html::addCssClass($this->headerOptions, ['widget' => 'modal-header']);
+
+ return Html::tag('div', "\n" . $header . "\n", $this->headerOptions);
+ }
+
+ /**
+ * Renders the opening tag of the modal body.
+ *
+ * @return string the rendering result
+ */
+ protected function renderBodyBegin(): string
+ {
+ Html::addCssClass($this->bodyOptions, ['widget' => 'modal-body']);
+
+ return Html::beginTag('div', $this->bodyOptions);
+ }
+
+ /**
+ * Renders the closing tag of the modal body.
+ *
+ * @return string the rendering result
+ */
+ protected function renderBodyEnd(): string
+ {
+ return Html::endTag('div');
+ }
+
+ /**
+ * Renders the HTML markup for the footer of the modal
+ *
+ * @return string|null the rendering result
+ */
+ protected function renderFooter(): string|null
+ {
+ if (isset($this->footer)) {
+ Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']);
+
+ return Html::tag('div', "\n" . $this->footer . "\n", $this->footerOptions);
+ }
+ return null;
+ }
+
+ /**
+ * Renders the toggle button.
+ *
+ * @return string|null the rendering result
+ */
+ protected function renderToggleButton(): string|null
+ {
+ if (($toggleButton = $this->toggleButton) !== false) {
+ $tag = ArrayHelper::remove($toggleButton, 'tag', 'button');
+ $label = ArrayHelper::remove($toggleButton, 'label', 'Show');
+
+ return Html::tag($tag, $label, $toggleButton);
+ }
+ return null;
+ }
+
+ /**
+ * Renders the close button.
+ *
+ * @return string|null the rendering result
+ */
+ protected function renderCloseButton(): string|null
+ {
+ if (($closeButton = $this->closeButton) !== false) {
+ $tag = ArrayHelper::remove($closeButton, 'tag', 'button');
+ $label = ArrayHelper::remove($closeButton, 'label', '');
+ if ($tag === 'button' && !isset($closeButton['type'])) {
+ $closeButton['type'] = 'button';
+ }
+
+ return Html::tag($tag, $label, $closeButton);
+ }
+ return null;
+ }
+
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions(): void
+ {
+ $this->options = array_merge([
+ 'tabindex' => -1,
+ 'aria-hidden' => 'true',
+ ], $this->options);
+ Html::addCssClass($this->options, ['widget' => 'modal fade']);
+
+ if (!empty($this->clientOptions)) {
+ $this->clientOptions = array_merge(['show' => false], $this->clientOptions);
+ }
+
+ $this->titleOptions = array_merge([
+ 'id' => $this->options['id'] . '-label',
+ ], $this->titleOptions);
+ if (!isset($this->options['aria']['label'], $this->options['aria']['labelledby']) && isset($this->title)) {
+ $this->options['aria']['labelledby'] = $this->titleOptions['id'];
+ }
+
+ if ($this->closeButton !== false) {
+ $this->closeButton = array_merge([
+ 'class' => ['widget' => 'btn-close'],
+ 'data' => ['bs-dismiss' => 'modal'],
+ 'aria' => ['label' => Yii::t('yii/bootstrap5', 'Close')],
+ ], $this->closeButton);
+ }
+
+ if ($this->toggleButton !== false) {
+ $this->toggleButton = array_merge([
+ 'data' => ['bs-toggle' => 'modal'],
+ 'type' => 'button',
+ ], $this->toggleButton);
+ if (!isset($this->toggleButton['data']['bs-target']) && !isset($this->toggleButton['href'])) {
+ $this->toggleButton['data']['bs-target'] = '#' . $this->options['id'];
+ }
+ }
+
+ Html::addCssClass($this->dialogOptions, ['widget' => 'modal-dialog']);
+ if (isset($this->size)) {
+ Html::addCssClass($this->dialogOptions, ['size' => $this->size]);
+ }
+ if ($this->centerVertical) {
+ Html::addCssClass($this->dialogOptions, ['align' => 'modal-dialog-centered']);
+ }
+ if ($this->scrollable) {
+ Html::addCssClass($this->dialogOptions, ['scroll' => 'modal-dialog-scrollable']);
+ }
+ }
+}
diff --git a/src/Nav.php b/src/Nav.php
new file mode 100644
index 0000000..441eaf8
--- /dev/null
+++ b/src/Nav.php
@@ -0,0 +1,333 @@
+ [
+ * [
+ * 'label' => 'Home',
+ * 'url' => ['site/index'],
+ * 'linkOptions' => [...],
+ * ],
+ * [
+ * 'label' => 'Dropdown',
+ * 'items' => [
+ * ['label' => 'Level 1 - Dropdown A', 'url' => '#'],
+ * '',
+ * '
Dropdown Header
',
+ * ['label' => 'Level 1 - Dropdown B', 'url' => '#'],
+ * ],
+ * ],
+ * [
+ * 'label' => 'Login',
+ * 'url' => ['site/login'],
+ * 'visible' => Yii::$app->user->isGuest
+ * ],
+ * ],
+ * 'options' => ['class' =>'nav-pills'], // set this to nav-tabs to get tab-styled navigation
+ * ]);
+ * ```
+ *
+ * Note: Multilevel dropdowns beyond Level 1 are not supported in Bootstrap 5.
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/navs/
+ * @see https://getbootstrap.com/docs/5.1/components/dropdowns/
+ *
+ * @author Antonio Ramirez
+ */
+class Nav extends Widget
+{
+ /**
+ * @var array list of items in the nav widget. Each array element represents a single
+ * menu item, which can be either a string or an array with the following structure:
+ *
+ * - label: string, required, the nav item label.
+ * - url: optional, the item's URL. Defaults to "#".
+ * - visible: bool, optional, whether this menu item is visible. Defaults to true.
+ * - disabled: bool, optional, whether this menu item is disabled. Defaults to false.
+ * - linkOptions: array, optional, the HTML attributes of the item's link.
+ * - options: array, optional, the HTML attributes of the item container (LI).
+ * - active: bool, optional, whether the item should be on active state or not.
+ * - dropdownOptions: array, optional, the HTML options that will be passed to the [[Dropdown]] widget.
+ * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget,
+ * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus.
+ * - encode: bool, optional, whether the label will be HTML-encoded. If set, supersedes the $encodeLabels option for only this item.
+ *
+ * If a menu item is a string, it will be rendered directly without HTML encoding.
+ */
+ public array $items = [];
+ /**
+ * @var bool whether the nav items labels should be HTML-encoded.
+ */
+ public bool $encodeLabels = true;
+ /**
+ * @var bool whether to automatically activate items according to whether their route setting
+ * matches the currently requested route.
+ *
+ * @see isItemActive
+ */
+ public bool $activateItems = true;
+ /**
+ * @var bool whether to activate parent menu items when one of the corresponding child menu items is active.
+ */
+ public bool $activateParents = false;
+ /**
+ * @var string|null the route used to determine if a menu item is active or not.
+ * If not set, it will use the route of the current request.
+ *
+ * @see params
+ * @see isItemActive
+ */
+ public string|null $route = null;
+ /**
+ * @var array|null the parameters used to determine if a menu item is active or not.
+ * If not set, it will use `$_GET`.
+ *
+ * @see route
+ * @see isItemActive
+ */
+ public ?array $params = null;
+ /**
+ * @var string name of a class to use for rendering dropdowns within this widget. Defaults to [[Dropdown]].
+ */
+ public string $dropdownClass = Dropdown::class;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+ if ($this->route === null && Yii::$app->controller !== null) {
+ $this->route = Yii::$app->controller->getRoute();
+ }
+ if ($this->params === null) {
+ $this->params = Yii::$app->request->getQueryParams();
+ }
+ Html::addCssClass($this->options, ['widget' => 'nav']);
+ }
+
+ /**
+ * Renders the widget.
+ *
+ * @throws InvalidConfigException|Throwable
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ BootstrapAsset::register($this->getView());
+
+ return $this->renderItems();
+ }
+
+ /**
+ * Renders widget items.
+ *
+ * @throws InvalidConfigException|Throwable
+ *
+ * @return string
+ */
+ public function renderItems(): string
+ {
+ $items = [];
+ foreach ($this->items as $item) {
+ if (isset($item['visible']) && !$item['visible']) {
+ continue;
+ }
+ $items[] = $this->renderItem($item);
+ }
+
+ return Html::tag('ul', implode("\n", $items), $this->options);
+ }
+
+ /**
+ * Renders a widget's item.
+ *
+ * @param array|string $item the item to render.
+ *
+ * @throws InvalidConfigException
+ * @throws Throwable
+ *
+ * @return string the rendering result.
+ */
+ public function renderItem($item): string
+ {
+ if (is_string($item)) {
+ return $item;
+ }
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $encodeLabel = $item['encode'] ?? $this->encodeLabels;
+ $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
+ $options = ArrayHelper::getValue($item, 'options', []);
+ $items = ArrayHelper::getValue($item, 'items');
+ $url = ArrayHelper::getValue($item, 'url', '#');
+ $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
+ $disabled = ArrayHelper::getValue($item, 'disabled', false);
+ $active = $this->isItemActive($item);
+
+ if (empty($items)) {
+ $items = '';
+ Html::addCssClass($options, ['widget' => 'nav-item']);
+ Html::addCssClass($linkOptions, ['widget' => 'nav-link']);
+ } else {
+ $linkOptions['data']['bs-toggle'] = 'dropdown';
+ $linkOptions['role'] = 'button';
+ $linkOptions['aria']['expanded'] = 'false';
+ Html::addCssClass($options, ['widget' => 'dropdown nav-item']);
+ Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle nav-link']);
+ if (is_array($items)) {
+ $items = $this->isChildActive($items, $active);
+ $items = $this->renderDropdown($items, $item);
+ }
+ }
+
+ if ($disabled) {
+ ArrayHelper::setValue($linkOptions, 'tabindex', '-1');
+ ArrayHelper::setValue($linkOptions, 'aria.disabled', 'true');
+ Html::addCssClass($linkOptions, ['disable' => 'disabled']);
+ } elseif ($this->activateItems && $active) {
+ Html::addCssClass($linkOptions, ['activate' => 'active']);
+ }
+
+ return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
+ }
+
+ /**
+ * Renders the given items as a dropdown.
+ * This method is called to create sub-menus.
+ *
+ * @param array $items the given items. Please refer to [[Dropdown::items]] for the array structure.
+ * @param array $parentItem the parent item information. Please refer to [[items]] for the structure of this array.
+ *
+ * @throws Throwable
+ *
+ * @return string the rendering result.
+ */
+ protected function renderDropdown(array $items, array $parentItem): string
+ {
+ /** @var Widget $dropdownClass */
+ $dropdownClass = $this->dropdownClass;
+
+ return $dropdownClass::widget([
+ 'options' => ArrayHelper::getValue($parentItem, 'dropdownOptions', []),
+ 'items' => $items,
+ 'encodeLabels' => $this->encodeLabels,
+ 'clientOptions' => [],
+ 'view' => $this->getView(),
+ ]);
+ }
+
+ /**
+ * Check to see if a child item is actively optionally activating the parent.
+ *
+ * @param array $items @see items
+ * @param bool $active should the parent be active too
+ *
+ * @throws Exception
+ *
+ * @return array
+ *
+ * @see items
+ */
+ protected function isChildActive(array $items, bool &$active): array
+ {
+ foreach ($items as $i => $child) {
+ if (is_array($child) && !ArrayHelper::getValue($child, 'visible', true)) {
+ continue;
+ }
+ if (is_array($child) && $this->isItemActive($child)) {
+ ArrayHelper::setValue($items[$i], 'active', true);
+ if ($this->activateParents) {
+ $active = true;
+ }
+ }
+ $childItems = ArrayHelper::getValue($child, 'items');
+ if (is_array($childItems)) {
+ $activeParent = false;
+ $items[$i]['items'] = $this->isChildActive($childItems, $activeParent);
+ if ($activeParent) {
+ Html::addCssClass($items[$i]['options'], ['activate' => 'active']);
+ $active = true;
+ }
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Checks whether a menu item is active.
+ * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
+ * When the `url` option of a menu item is specified in terms of an array, its first element is treated
+ * as the route for the item, and the rest of the elements are the associated parameters.
+ * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
+ * be considered active.
+ *
+ * @param array $item the menu item to be checked
+ *
+ * @throws Exception
+ *
+ * @return bool whether the menu item is active
+ */
+ protected function isItemActive(array $item): bool
+ {
+ if (!$this->activateItems) {
+ return false;
+ }
+ if (isset($item['active'])) {
+ return (bool)ArrayHelper::getValue($item, 'active', false);
+ }
+ if (isset($item['url'][0]) && is_array($item['url'])) {
+ $route = $item['url'][0];
+ if ($route[0] !== '/' && Yii::$app->controller) {
+ $route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
+ }
+ if (ltrim($route, '/') !== $this->route) {
+ return false;
+ }
+ unset($item['url']['#']);
+ if (count($item['url']) > 1) {
+ $params = $item['url'];
+ unset($params[0]);
+ foreach ($params as $name => $value) {
+ if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] !== $value)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/NavBar.php b/src/NavBar.php
new file mode 100644
index 0000000..ce962af
--- /dev/null
+++ b/src/NavBar.php
@@ -0,0 +1,234 @@
+ 'NavBar Test']);
+ * echo Nav::widget([
+ * 'items' => [
+ * ['label' => 'Home', 'url' => ['/site/index']],
+ * ['label' => 'About', 'url' => ['/site/about']],
+ * ],
+ * 'options' => ['class' => 'navbar-nav'],
+ * ]);
+ * NavBar::end();
+ * ```
+ *
+ * @property array $containerOptions
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/navbar/
+ *
+ * @author Antonio Ramirez
+ * @author Alexander Kochetov
+ */
+class NavBar extends Widget
+{
+ /**
+ * @var array|bool the HTML attributes for the collapse container tag. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div," the name of the container tag.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array|bool $collapseOptions = [];
+ /**
+ * @var array|bool the HTML attributes for the offcanvas container tag.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array|bool $offcanvasOptions = false;
+ /**
+ * @var bool|string the text of the brand or false if it's not used. Note that this is not HTML-encoded.
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/navbar/
+ */
+ public string|bool $brandLabel = false;
+ /**
+ * @var bool|string src of the brand image or false if it's not used. Note that this param will override `$this->brandLabel` param.
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/navbar/
+ */
+ public string|bool $brandImage = false;
+
+ /**
+ * @var array the HTML attributes of the brand image.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $brandImageOptions = [];
+ /**
+ * @var array|bool|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[\yii\helpers\Url::to()]]
+ * and will be used for the "href" attribute of the brand link. Default value is false that means
+ * [[\yii\web\Application::homeUrl]] will be used.
+ * You may set it to `null` if you want to have no link at all.
+ */
+ public string|array|bool|null $brandUrl = false;
+ /**
+ * @var array the HTML attributes of the brand link.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $brandOptions = [];
+ /**
+ * @var string text to show for screen readers for the button to toggle the navbar.
+ */
+ public string $screenReaderToggleText = '';
+ /**
+ * @var string the toggle button content. Defaults to bootstrap5 default ``
+ */
+ public string $togglerContent = '';
+ /**
+ * @var array the HTML attributes of the navbar toggler button.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $togglerOptions = [];
+ /**
+ * @var bool whether the navbar content should be included in an inner div container which by default
+ * adds left and right padding. Set this to false for a 100% width navbar.
+ */
+ public bool $renderInnerContainer = true;
+ /**
+ * @var array the HTML attributes of the inner container.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $innerContainerOptions = [];
+
+ /**
+ * {@inheritDoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+ if (!isset($this->options['class']) || empty($this->options['class'])) {
+ Html::addCssClass($this->options, [
+ 'widget' => 'navbar',
+ 'toggle' => 'navbar-expand-lg',
+ 'navbar-light',
+ 'bg-light',
+ ]);
+ } else {
+ Html::addCssClass($this->options, ['widget' => 'navbar']);
+ }
+ $navOptions = $this->options;
+ $navTag = ArrayHelper::remove($navOptions, 'tag', 'nav');
+ $brand = '';
+ if (!isset($this->innerContainerOptions['class'])) {
+ Html::addCssClass($this->innerContainerOptions, ['panel' => 'container']);
+ }
+ if ($this->collapseOptions !== false && !isset($this->collapseOptions['id'])) {
+ $this->collapseOptions['id'] = "{$this->options['id']}-collapse";
+ } elseif ($this->offcanvasOptions !== false && !isset($this->offcanvasOptions['id'])) {
+ $this->offcanvasOptions['id'] = "{$this->options['id']}-offcanvas";
+ }
+ if ($this->brandImage !== false) {
+ $this->brandLabel = Html::img($this->brandImage, $this->brandImageOptions);
+ }
+ if ($this->brandLabel !== false) {
+ Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']);
+ if ($this->brandUrl === null) {
+ $brand = Html::tag('span', $this->brandLabel, $this->brandOptions);
+ } else {
+ $brand = Html::a(
+ $this->brandLabel,
+ $this->brandUrl === false ? Yii::$app->homeUrl : $this->brandUrl,
+ $this->brandOptions
+ );
+ }
+ }
+
+ echo Html::beginTag($navTag, $navOptions) . "\n";
+ if ($this->renderInnerContainer) {
+ echo Html::beginTag('div', $this->innerContainerOptions) . "\n";
+ }
+ echo $brand . "\n";
+ echo $this->renderToggleButton() . "\n";
+ if ($this->collapseOptions !== false) {
+ Html::addCssClass($this->collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
+ $collapseOptions = $this->collapseOptions;
+ $collapseTag = ArrayHelper::remove($collapseOptions, 'tag', 'div');
+ echo Html::beginTag($collapseTag, $collapseOptions) . "\n";
+ } elseif ($this->offcanvasOptions !== false) {
+ Offcanvas::begin($this->offcanvasOptions);
+ }
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run(): void
+ {
+ if ($this->collapseOptions !== false) {
+ $tag = ArrayHelper::remove($this->collapseOptions, 'tag', 'div');
+ echo Html::endTag($tag) . "\n";
+ } elseif ($this->offcanvasOptions !== false) {
+ Offcanvas::end();
+ }
+ if ($this->renderInnerContainer) {
+ echo Html::endTag('div') . "\n";
+ }
+ $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
+ echo Html::endTag($tag);
+ BootstrapPluginAsset::register($this->getView());
+ }
+
+ /**
+ * Renders collapsible toggle button.
+ *
+ * @return string the rendering toggle button.
+ */
+ protected function renderToggleButton(): string
+ {
+ if ($this->collapseOptions === false && $this->offcanvasOptions === false) {
+ return '';
+ }
+
+ $aria = '';
+ $bsData = [];
+ $options = $this->togglerOptions;
+ Html::addCssClass($options, ['widget' => 'navbar-toggler']);
+ if ($this->offcanvasOptions !== false) {
+ $bsData = ['bs-toggle' => 'offcanvas', 'bs-target' => '#' . $this->offcanvasOptions['id']];
+ $aria = $this->offcanvasOptions['id'];
+ } elseif ($this->collapseOptions !== false) {
+ $bsData = ['bs-toggle' => 'collapse', 'bs-target' => '#' . $this->collapseOptions['id']];
+ $aria = $this->collapseOptions['id'];
+ }
+
+ return Html::button(
+ $this->togglerContent,
+ ArrayHelper::merge($options, [
+ 'type' => 'button',
+ 'data' => $bsData,
+ 'aria' => [
+ 'controls' => $aria,
+ 'expanded' => 'false',
+ 'label' => $this->screenReaderToggleText ?: Yii::t('yii/bootstrap5', 'Toggle navigation'),
+ ],
+ ])
+ );
+ }
+}
diff --git a/src/Offcanvas.php b/src/Offcanvas.php
new file mode 100644
index 0000000..e3177c7
--- /dev/null
+++ b/src/Offcanvas.php
@@ -0,0 +1,268 @@
+ Offcanvas::PLACEMENT_END,
+ * 'backdrop' => true,
+ * 'scrolling' => true
+ * ]);
+ *
+ * Nav::widget([...]);
+ *
+ * Offcanvas::end();
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/offcanvas/
+ *
+ * @author Simon Karlen
+ */
+class Offcanvas extends Widget
+{
+ public const PLACEMENT_START = 'start';
+ public const PLACEMENT_END = 'end';
+ public const PLACEMENT_TOP = 'top';
+ public const PLACEMENT_BOTTOM = 'bottom';
+
+ /**
+ * @var string Where to place the offcanvas. Can be of of the [[PLACEMENT_*]] constants.
+ */
+ public string $placement = self::PLACEMENT_START;
+ /**
+ * @var bool Whether to enable backdrop or not. Defaults to `true`.
+ */
+ public bool $backdrop = true;
+ /**
+ * @var bool Whether to enable body scrolling or not. Defaults to `false`.
+ */
+ public bool $scrolling = false;
+ /**
+ * @var string The title content in the offcanvas container.
+ */
+ public string $title;
+ /**
+ * @var array|false the options for rendering the close button tag.
+ * The close button is displayed in the header of the offcanvas container. Clicking
+ * on the button will hide the offcanvas container. If this is false, no close button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button.'
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Offcanvas plugin help](https://getbootstrap.com/docs/5.1/components/offcanvas/)
+ * for the supported HTML attributes.
+ */
+ public array|false $closeButton = [];
+ /**
+ * @var array|false the options for rendering the toggle button tag.
+ * The toggle button is used to toggle the visibility of the modal window.
+ * If this property is false, no toggle button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to 'Show.'
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Modal plugin help](https://getbootstrap.com/javascript/#modals)
+ * for the supported HTML attributes.
+ */
+ public array|false $toggleButton = false;
+ /**
+ * @var array Additional header options.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $headerOptions = [];
+ /**
+ * @var array Additional title options.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'h5.'
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $titleOptions = [];
+ /**
+ * @var array Additional body options.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $bodyOptions = [];
+
+ /**
+ * {@inheritDoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+
+ $this->initOptions();
+
+ echo $this->renderToggleButton() . "\n";
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderHeader() . "\n";
+ echo $this->renderBodyBegin() . "\n";
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run(): void
+ {
+ echo "\n" . $this->renderBodyEnd();
+ echo "\n" . Html::endTag('div');
+
+ $this->registerPlugin('offcanvas');
+ }
+
+ /**
+ * Renders the header HTML markup of the modal
+ *
+ * @return string the rendering result
+ */
+ protected function renderHeader(): string
+ {
+ $button = $this->renderCloseButton();
+ if (isset($this->title)) {
+ $tag = ArrayHelper::remove($this->titleOptions, 'tag', 'h5');
+ Html::addCssClass($this->titleOptions, ['widget' => 'offcanvas-title']);
+ $header = Html::tag($tag, $this->title, $this->titleOptions);
+ } else {
+ $header = '';
+ }
+
+ if ($button !== null) {
+ $header .= "\n" . $button;
+ } elseif ($header === '') {
+ return '';
+ }
+ Html::addCssClass($this->headerOptions, ['widget' => 'offcanvas-header']);
+
+ return Html::tag('div', "\n" . $header . "\n", $this->headerOptions);
+ }
+
+ /**
+ * Renders the opening tag of the modal body.
+ *
+ * @return string the rendering result
+ */
+ protected function renderBodyBegin(): string
+ {
+ Html::addCssClass($this->bodyOptions, ['widget' => 'offcanvas-body']);
+
+ return Html::beginTag('div', $this->bodyOptions);
+ }
+
+ /**
+ * Renders the closing tag of the modal body.
+ *
+ * @return string the rendering result
+ */
+ protected function renderBodyEnd(): string
+ {
+ return Html::endTag('div');
+ }
+
+ /**
+ * Renders the toggle button.
+ *
+ * @return string|null the rendering result
+ */
+ protected function renderToggleButton(): string|null
+ {
+ if (($toggleButton = $this->toggleButton) !== false) {
+ $tag = ArrayHelper::remove($toggleButton, 'tag', 'button');
+ $label = ArrayHelper::remove($toggleButton, 'label', Yii::t('yii/bootstrap5', 'Show'));
+
+ return Html::tag($tag, $label, $toggleButton);
+ }
+ return null;
+ }
+
+ /**
+ * Renders the close button.
+ *
+ * @return string|null the rendering result
+ */
+ protected function renderCloseButton(): string|null
+ {
+ if (($closeButton = $this->closeButton) !== false) {
+ $tag = ArrayHelper::remove($closeButton, 'tag', 'button');
+ $label = ArrayHelper::remove($closeButton, 'label', '');
+ if ($tag === 'button' && !isset($closeButton['type'])) {
+ $closeButton['type'] = 'button';
+ }
+
+ return Html::tag($tag, $label, $closeButton);
+ }
+ return null;
+ }
+
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions(): void
+ {
+ $this->options = array_merge([
+ 'tabindex' => -1,
+ 'data' => [
+ 'bs-backdrop' => $this->backdrop ? 'true' : 'false',
+ 'bs-scroll' => $this->scrolling ? 'true' : 'false',
+ ],
+ ], $this->options);
+ Html::addCssClass($this->options, ['widget' => 'offcanvas offcanvas-' . $this->placement]);
+
+ $this->titleOptions = array_merge([
+ 'id' => $this->options['id'] . '-label',
+ ], $this->titleOptions);
+ if (!isset($this->options['aria']['label'], $this->options['aria']['labelledby']) && isset($this->title)) {
+ $this->options['aria']['labelledby'] = $this->titleOptions['id'];
+ }
+
+ if ($this->closeButton !== false) {
+ $this->closeButton = array_merge([
+ 'class' => ['widget' => 'btn-close text-reset'],
+ 'data' => ['bs-dismiss' => 'offcanvas'],
+ 'aria' => ['label' => Yii::t('yii/bootstrap5', 'Close')],
+ ], $this->closeButton);
+ }
+
+ if ($this->toggleButton !== false) {
+ $this->toggleButton = array_merge([
+ 'data' => ['bs-toggle' => 'offcanvas'],
+ 'type' => 'button',
+ 'aria' => ['controls' => $this->options['id']],
+ ], $this->toggleButton);
+ if (!isset($this->toggleButton['data']['bs-target']) && !isset($this->toggleButton['href'])) {
+ $this->toggleButton['data']['bs-target'] = '#' . $this->options['id'];
+ }
+ }
+ }
+}
diff --git a/src/Popover.php b/src/Popover.php
new file mode 100644
index 0000000..c72f5b4
--- /dev/null
+++ b/src/Popover.php
@@ -0,0 +1,207 @@
+ 'Hello world',
+ * 'toggleButton' => ['label' => 'click me'],
+ * ]);
+ *
+ * echo 'Say hello...';
+ *
+ * Popover::end();
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/popovers/
+ *
+ * @author Simon Karlen
+ */
+class Popover extends Widget
+{
+ public const PLACEMENT_AUTO = 'auto';
+ public const PLACEMENT_TOP = 'top';
+ public const PLACEMENT_BOTTOM = 'bottom';
+ public const PLACEMENT_LEFT = 'left';
+ public const PLACEMENT_RIGHT = 'right';
+ public const TRIGGER_CLICK = 'click';
+ public const TRIGGER_HOVER = 'hover';
+ public const TRIGGER_FOCUS = 'focus';
+ public const TRIGGER_MANUAL = 'manual';
+
+ /**
+ * @var string|null the tile content in the popover.
+ */
+ public string|null $title = null;
+ /**
+ * @var array additional header options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $headerOptions = [];
+ /**
+ * @var array body options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $bodyOptions = [];
+ /**
+ * @var array arrow options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $arrowOptions = [];
+ /**
+ * @var string How to position the popover - [[PLACEMENT_AUTO]] | [[PLACEMENT_TOP]] | [[PLACEMENT_BOTTOM]] |
+ * [[PLACEMENT_LEFT]] | [[PLACEMENT_RIGHT]]. When auto is specified, it will dynamically reorient the popover.
+ */
+ public string $placement = self::PLACEMENT_AUTO;
+ /**
+ * @var array|false the options for rendering the toggle button tag.
+ * The toggle button is used to toggle the visibility of the popover.
+ * If this property is false, no toggle button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to 'Show.'
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Popover plugin help](https://getbootstrap.com/docs/5.1/components/popovers/)
+ * for the supported HTML attributes.
+ */
+ public array|false $toggleButton = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+
+ $this->initOptions();
+
+ ob_start();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function run()
+ {
+ $content = ob_get_clean();
+
+ if (!empty($content)) {
+ $this->clientOptions['content'] = $content;
+ }
+ $html = $this->renderToggleButton();
+
+ $this->registerPlugin('popover');
+
+ return $html;
+ }
+
+ /**
+ * Renders the arrow HTML markup of the popover
+ *
+ * @return string the rendering result
+ */
+ protected function renderArrow(): string
+ {
+ Html::addCssClass($this->arrowOptions, ['widget' => 'popover-arrow']);
+
+ return Html::tag('div', '', $this->arrowOptions);
+ }
+
+ /**
+ * Renders the header HTML markup of the popover
+ *
+ * @return string the rendering result
+ */
+ protected function renderHeader(): string
+ {
+ Html::addCssClass($this->headerOptions, ['widget' => 'popover-header']);
+
+ return Html::tag('h3', '', $this->headerOptions);
+ }
+
+ /**
+ * Renders the opening tag of the modal body.
+ *
+ * @return string the rendering result
+ */
+ protected function renderBody(): string
+ {
+ Html::addCssClass($this->bodyOptions, ['widget' => 'popover-body']);
+
+ return Html::tag('div', '', $this->bodyOptions);
+ }
+
+ /**
+ * Renders the toggle button.
+ *
+ * @return string|null the rendering result
+ */
+ protected function renderToggleButton(): string|null
+ {
+ if (($toggleButton = $this->toggleButton) !== false) {
+ $tag = ArrayHelper::remove($toggleButton, 'tag', 'button');
+ $label = ArrayHelper::remove($toggleButton, 'label', Yii::t('yii/bootstrap5', 'Show'));
+ $toggleButton['id'] = $this->options['id'];
+
+ return Html::tag($tag, $label, $toggleButton);
+ }
+ return null;
+ }
+
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions(): void
+ {
+ $options = array_merge([
+ 'role' => 'tooltip',
+ ], $this->options, ['id' => $this->options['id'] . '-popover']);
+ Html::addCssClass($options, ['widget' => 'popover']);
+ $template = Html::beginTag('div', $options);
+ $template .= $this->renderArrow();
+ $template .= $this->renderHeader();
+ $template .= $this->renderBody();
+ $template .= Html::endTag('div');
+
+ $this->clientOptions = array_merge([
+ 'template' => $template,
+ 'placement' => $this->placement,
+ 'title' => $this->title,
+ 'sanitize' => false,
+ 'html' => true,
+ ], $this->clientOptions);
+
+ if ($this->toggleButton !== false) {
+ $this->toggleButton = array_merge([
+ 'type' => 'button',
+ ], $this->toggleButton);
+ }
+ }
+}
diff --git a/src/Progress.php b/src/Progress.php
new file mode 100644
index 0000000..6ed07e7
--- /dev/null
+++ b/src/Progress.php
@@ -0,0 +1,201 @@
+ 60,
+ * 'label' => 'test'
+ * ]);
+ * // or
+ * echo Progress::widget([
+ * 'bars' => [
+ * ['percent' => 60, 'label' => 'test']
+ * ]
+ * ]);
+ *
+ * // styled
+ * echo Progress::widget([
+ * 'percent' => 65,
+ * 'barOptions' => ['class' => 'bg-danger']
+ * ]);
+ * // or
+ * echo Progress::widget([
+ * 'bars' => [
+ * ['percent' => 65, 'options' => ['class' => 'bg-danger']]
+ * ]
+ * ]);
+ *
+ * // striped
+ * echo Progress::widget([
+ * 'percent' => 70,
+ * 'barOptions' => ['class' => ['bg-warning', 'progress-bar-striped']]
+ * ]);
+ * // or
+ * echo Progress::widget([
+ * 'bars' => [
+ * ['percent' => 70, 'options' => ['class' => ['bg-warning', 'progress-bar-striped']]]
+ * ]
+ * ]);
+ *
+ * // striped animated
+ * echo Progress::widget([
+ * 'percent' => 70,
+ * 'barOptions' => ['class' => ['bg-success', 'progress-bar-animated', 'progress-bar-striped']]
+ * ]);
+ * // or
+ * echo Progress::widget([
+ * 'bars' => [
+ * ['percent' => 70, 'options' => ['class' => ['bg-success', 'progress-bar-animated', 'progress-bar-striped']]]
+ * ]
+ * ]);
+ *
+ * // stacked bars
+ * echo Progress::widget([
+ * 'bars' => [
+ * ['percent' => 30, 'options' => ['class' => 'bg-danger']],
+ * ['percent' => 30, 'label' => 'test', 'options' => ['class' => 'bg-success']],
+ * ['percent' => 35, 'options' => ['class' => 'bg-warning']],
+ * ]
+ * ]);
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/progress/
+ *
+ * @author Antonio Ramirez
+ * @author Alexander Makarov
+ * @author Simon Karlen
+ */
+class Progress extends Widget
+{
+ /**
+ * @var string the button label. This property will only be considered if [[bars]] is empty
+ */
+ public string $label = '';
+ /**
+ * @var int the amount of progress as a percentage. This property will only be considered if [[bars]] is empty
+ */
+ public int $percent = 0;
+ /**
+ * @var array the HTML attributes of the bar. This property will only be considered if [[bars]] is empty
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $barOptions = [];
+ /**
+ * @var array a set of bars that are stacked together to form a single progress bar.
+ * Each bar is an array of the following structure:
+ *
+ * ```php
+ * [
+ * // required, the amount of progress as a percentage.
+ * 'percent' => 30,
+ * // optional, the label to be displayed on the bar
+ * 'label' => '30%',
+ * // optional, array, additional HTML attributes for the bar tag
+ * 'options' => [],
+ * ]
+ * ```
+ */
+ public array $bars;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+
+ Html::addCssClass($this->options, ['widget' => 'progress']);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidConfigException
+ */
+ public function run(): string
+ {
+ BootstrapAsset::register($this->getView());
+
+ return $this->renderProgress();
+ }
+
+ /**
+ * Renders the progress.
+ *
+ * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
+ * @throws Exception
+ *
+ * @return string the rendering result.
+ */
+ protected function renderProgress(): string
+ {
+ $out = Html::beginTag('div', $this->options) . "\n";
+ if (empty($this->bars)) {
+ $this->bars = [
+ ['label' => $this->label, 'percent' => $this->percent, 'options' => $this->barOptions],
+ ];
+ }
+ $bars = [];
+ foreach ($this->bars as $bar) {
+ $label = ArrayHelper::getValue($bar, 'label', '');
+ if (!isset($bar['percent'])) {
+ throw new InvalidConfigException("The 'percent' option is required.");
+ }
+ $options = ArrayHelper::getValue($bar, 'options', []);
+ $bars[] = $this->renderBar($bar['percent'], $label, $options);
+ }
+ $out .= implode("\n", $bars) . "\n";
+ $out .= Html::endTag('div');
+
+ return $out;
+ }
+
+ /**
+ * Generates a progress bar.
+ *
+ * @param int $percent the percentage of the bar
+ * @param string $label optional, the label to display at the bar
+ * @param array $options the HTML attributes of the bar
+ *
+ * @return string the rendering result.
+ */
+ protected function renderBar(int $percent, string $label = '', array $options = []): string
+ {
+ $options = array_merge($options, [
+ 'role' => 'progressbar',
+ 'aria' => [
+ 'valuenow' => $percent,
+ 'valuemin' => 0,
+ 'valuemax' => 100,
+ ],
+ ]);
+ Html::addCssClass($options, ['widget' => 'progress-bar']);
+ Html::addCssStyle($options, ['width' => $percent . '%']);
+
+ return Html::tag('div', $label, $options);
+ }
+}
diff --git a/src/Tabs.php b/src/Tabs.php
new file mode 100644
index 0000000..7f45040
--- /dev/null
+++ b/src/Tabs.php
@@ -0,0 +1,289 @@
+ [
+ * [
+ * 'label' => 'One',
+ * 'content' => 'Anim pariatur cliche...',
+ * 'active' => true
+ * ],
+ * [
+ * 'label' => 'Two',
+ * 'content' => 'Anim pariatur cliche...',
+ * 'headerOptions' => [...],
+ * 'options' => ['id' => 'myveryownID'],
+ * ],
+ * [
+ * 'label' => 'Example',
+ * 'url' => 'http://www.example.com',
+ * ],
+ * [
+ * 'label' => 'Dropdown',
+ * 'items' => [
+ * [
+ * 'label' => 'DropdownA',
+ * 'content' => 'DropdownA, Anim pariatur cliche...',
+ * ],
+ * [
+ * 'label' => 'DropdownB',
+ * 'content' => 'DropdownB, Anim pariatur cliche...',
+ * ],
+ * [
+ * 'label' => 'External Link',
+ * 'url' => 'http://www.example.com',
+ * ],
+ * ],
+ * ],
+ * ],
+ * ]);
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/navs/#tabs
+ * @see https://getbootstrap.com/docs/5.1/components/card/#navigation
+ *
+ * @author Antonio Ramirez
+ * @author Simon Karlen
+ */
+class Tabs extends Widget
+{
+ /**
+ * @var array list of tabs in the tabs widget. Each array element represents a single
+ * tab with the following structure:
+ *
+ * - label: string, required, the tab header label.
+ * - encode: bool, optional, whether this label should be HTML-encoded. This param will override
+ * global `$this->encodeLabels` param.
+ * - headerOptions: array, optional, the HTML attributes of the tab header.
+ * - linkOptions: array, optional, the HTML attributes of the tab header link tags.
+ * - content: string, optional, the content (HTML) of the tab pane.
+ * - url: string, optional, an external URL. When this is specified, clicking on this tab will bring
+ * the browser to this URL. This option is available since version 2.0.4.
+ * - options: array, optional, the HTML attributes of the tab pane container.
+ * - active: bool, optional, whether this item tab header and pane should be active. If no item is marked as
+ * 'active' explicitly - the first one will be activated.
+ * - visible: bool, optional, whether the item tab header and pane should be visible or not. Defaults to true.
+ * - disabled: bool, optional, whether the item tab header and pane should be disabled or not. Defaults to false.
+ * - items: array, optional, can be used instead of `content` to specify a dropdown items
+ * configuration array. Each item can hold three extra keys, besides the above ones:
+ * * active: bool, optional, whether the item tab header and pane should be visible or not.
+ * * content: string, required if `items` is not set. The content (HTML) of the tab pane.
+ * * options: optional, array, the HTML attributes of the tab content container.
+ */
+ public array $items = [];
+ /**
+ * @var array list of HTML attributes for the item container tags. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div," the tag name of the item container tags.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $itemOptions = [];
+ /**
+ * @var array list of HTML attributes for the header container tags. This will be overwritten
+ * by the "headerOptions" set in individual [[items]].
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $headerOptions = [];
+ /**
+ * @var array list of HTML attributes for the tab header link tags. This will be overwritten
+ * by the "linkOptions" set in individual [[items]].
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $linkOptions = [];
+ /**
+ * @var bool whether the labels for header items should be HTML-encoded.
+ */
+ public bool $encodeLabels = true;
+ /**
+ * @var string specifies the Bootstrap tab styling.
+ */
+ public string $navType = 'nav-tabs';
+ /**
+ * @var bool whether to render the `tab-content` container and its content. You may set this property
+ * to be false so that you can manually render `tab-content` yourself in case your tab contents are complex.
+ */
+ public bool $renderTabContent = true;
+ /**
+ * @var array list of HTML attributes for the `tab-content` container. This will always contain the CSS class `tab-content`.
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $tabContentOptions = [];
+ /**
+ * @var string name of a class to use for rendering dropdowns withing this widget. Defaults to [[Dropdown]].
+ */
+ public string $dropdownClass = Dropdown::class;
+
+ /**
+ * @var array Tab panes (contents)
+ */
+ protected array $panes = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+ Html::addCssClass($this->options, ['widget' => 'nav', $this->navType]);
+ Html::addCssClass($this->tabContentOptions, ['panel' => 'tab-content']);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidConfigException
+ * @throws Throwable
+ */
+ public function run(): string
+ {
+ $this->registerPlugin('tab');
+ $this->prepareItems($this->items);
+
+ return Nav::widget([
+ 'dropdownClass' => $this->dropdownClass,
+ 'options' => ArrayHelper::merge(['role' => 'tablist'], $this->options),
+ 'items' => $this->items,
+ 'encodeLabels' => $this->encodeLabels,
+ ]) . $this->renderPanes($this->panes);
+ }
+
+ /**
+ * Renders tab panes.
+ *
+ * @param array $panes
+ *
+ * @return string the rendering result.
+ */
+ public function renderPanes(array $panes): string
+ {
+ return $this->renderTabContent
+ ? "\n" . Html::tag('div', implode("\n", $panes), $this->tabContentOptions)
+ : '';
+ }
+
+ /**
+ * Renders tab items as specified on [[items]].
+ *
+ * @param array $items
+ * @param string $prefix
+ *
+ * @throws InvalidConfigException
+ * @throws Exception
+ */
+ protected function prepareItems(array &$items, string $prefix = ''): void
+ {
+ if (!$this->hasActiveTab()) {
+ $this->activateFirstVisibleTab();
+ }
+
+ foreach ($items as $n => $item) {
+ $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
+ $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . $prefix . '-tab' . $n);
+ unset($items[$n]['options']['id']); // @see https://github.com/yiisoft/yii2-bootstrap4/issues/108#issuecomment-465219339
+
+ if (!ArrayHelper::remove($item, 'visible', true)) {
+ continue;
+ }
+ if (!array_key_exists('label', $item)) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+
+ $selected = ArrayHelper::getValue($item, 'active', false);
+ $disabled = ArrayHelper::getValue($item, 'disabled', false);
+ $headerOptions = ArrayHelper::getValue($item, 'headerOptions', $this->headerOptions);
+ if (isset($item['items'])) {
+ $this->prepareItems($items[$n]['items'], '-dd' . $n);
+ continue;
+ }
+ ArrayHelper::setValue($items[$n], 'options', $headerOptions);
+ if (!isset($item['url'])) {
+ ArrayHelper::setValue($items[$n], 'url', '#' . $options['id']);
+ ArrayHelper::setValue($items[$n], 'linkOptions.data.bs-toggle', 'tab');
+ ArrayHelper::setValue($items[$n], 'linkOptions.role', 'tab');
+ ArrayHelper::setValue($items[$n], 'linkOptions.aria.controls', $options['id']);
+ if (!$disabled) {
+ ArrayHelper::setValue($items[$n], 'linkOptions.aria.selected', $selected ? 'true' : 'false');
+ }
+ } else {
+ continue;
+ }
+
+
+ Html::addCssClass($options, ['widget' => 'tab-pane']);
+ if ($selected) {
+ Html::addCssClass($options, ['activate' => 'active']);
+ }
+
+ if ($this->renderTabContent) {
+ $tag = ArrayHelper::remove($options, 'tag', 'div');
+ $this->panes[] = Html::tag($tag, $item['content'] ?? '', $options);
+ }
+ }
+ }
+
+ /**
+ * Check for active tab
+ *
+ * @return bool if there's active tab defined
+ */
+ protected function hasActiveTab(): bool
+ {
+ foreach ($this->items as $item) {
+ if (isset($item['active']) && $item['active'] === true) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the first visible tab as active.
+ *
+ * This method activates the first tab that is visible and
+ * not explicitly set to inactive (`'active' => false`).
+ *
+ * @throws Exception
+ */
+ protected function activateFirstVisibleTab(): void
+ {
+ foreach ($this->items as $i => $item) {
+ $active = ArrayHelper::getValue($item, 'active');
+ $visible = ArrayHelper::getValue($item, 'visible', true);
+ $disabled = ArrayHelper::getValue($item, 'disabled', false);
+ if ($visible && $active !== false && $disabled !== true) {
+ $this->items[$i]['active'] = true;
+
+ return;
+ }
+ }
+ }
+}
diff --git a/src/Toast.php b/src/Toast.php
new file mode 100644
index 0000000..ffe0d83
--- /dev/null
+++ b/src/Toast.php
@@ -0,0 +1,235 @@
+ 'Hello world!',
+ * 'dateTime' => 'now',
+ * 'body' => 'Say hello...',
+ * ]);
+ * ```
+ *
+ * The following example will show the content enclosed between the [[begin()]]
+ * and [[end()]] calls within the toast box:
+ *
+ * ```php
+ * Toast::begin([
+ * 'title' => 'Hello world!',
+ * 'dateTime' => 'now'
+ * ]);
+ *
+ * echo 'Say hello...';
+ *
+ * Toast::end();
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/toasts/
+ *
+ * @author Simon Karlen
+ */
+class Toast extends Widget
+{
+ /**
+ * @var string|null the body content in the alert component. Note that anything between
+ * the [[begin()]] and [[end()]] calls of the Toast widget will also be treated
+ * as the body content, and will be rendered before this.
+ */
+ public ?string $body = null;
+ /**
+ * @var string|null The title content in the toast.
+ */
+ public ?string $title = null;
+ /**
+ * @var DateInterval|DateTime|DateTimeInterface|false|int|string The date time the toast message references to.
+ * This will be formatted as relative time (via a formatter component).
+ * It will be omitted if set to `false` (default).
+ */
+ public string|int|false|DateTime|DateTimeInterface|DateInterval $dateTime = false;
+ /**
+ * @var array|false the options for rendering the close button tag.
+ * The close button is displayed in the header of the toast. Clicking on the button will hide the toast.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to '×'.
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Toast documentation](https://getbootstrap.com/docs/5.1/components/toasts/)
+ * for the supported HTML attributes.
+ */
+ public array|false $closeButton = [];
+ /**
+ * @var array additional title options
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'strong.'
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $titleOptions = [];
+ /**
+ * @var array additional date time part options
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'small.'
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $dateTimeOptions = [];
+ /**
+ * @var array additional header options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $headerOptions = [];
+ /**
+ * @var array body options
+ *
+ * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+ */
+ public array $bodyOptions = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+
+ $this->initOptions();
+
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderHeader() . "\n";
+ echo $this->renderBodyBegin() . "\n";
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run(): void
+ {
+ echo "\n" . $this->renderBodyEnd();
+ echo "\n" . Html::endTag('div');
+
+ $this->registerPlugin('toast');
+ }
+
+ /**
+ * Renders the header HTML markup of the modal
+ *
+ * @return string the rendering result
+ */
+ protected function renderHeader(): string
+ {
+ $button = $this->renderCloseButton();
+ $tag = ArrayHelper::remove($this->titleOptions, 'tag', 'strong');
+ Html::addCssClass($this->titleOptions, ['widget' => 'me-auto']);
+ $title = Html::tag($tag, $this->title ?? '', $this->titleOptions);
+
+ if ($this->dateTime !== false) {
+ $tag = ArrayHelper::remove($this->dateTimeOptions, 'tag', 'small');
+ Html::addCssClass($this->dateTimeOptions, ['widget' => 'text-muted']);
+ $title .= "\n" . Html::tag($tag, Yii::$app->formatter->asRelativeTime($this->dateTime), $this->dateTimeOptions);
+ }
+
+ $title .= "\n" . $button;
+
+ Html::addCssClass($this->headerOptions, ['widget' => 'toast-header']);
+
+ return Html::tag('div', "\n" . $title . "\n", $this->headerOptions);
+ }
+
+ /**
+ * Renders the opening tag of the toast body.
+ *
+ * @return string the rendering result
+ */
+ protected function renderBodyBegin(): string
+ {
+ Html::addCssClass($this->bodyOptions, ['widget' => 'toast-body']);
+
+ return Html::beginTag('div', $this->bodyOptions);
+ }
+
+ /**
+ * Renders the toast body and the close button (if any).
+ *
+ * @return string the rendering result
+ */
+ protected function renderBodyEnd(): string
+ {
+ return $this->body . "\n" . Html::endTag('div');
+ }
+
+ /**
+ * Renders the close button.
+ *
+ * @return string|null the rendering result
+ */
+ protected function renderCloseButton(): string|null
+ {
+ if (($closeButton = $this->closeButton) !== false) {
+ $tag = ArrayHelper::remove($closeButton, 'tag', 'button');
+ $label = ArrayHelper::remove($closeButton, 'label', '');
+ if ($tag === 'button' && !isset($closeButton['type'])) {
+ $closeButton['type'] = 'button';
+ }
+
+ return Html::tag($tag, $label, $closeButton);
+ }
+ return null;
+ }
+
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions(): void
+ {
+ Html::addCssClass($this->options, ['widget' => 'toast']);
+
+ if ($this->closeButton !== false) {
+ $this->closeButton = array_merge([
+ 'class' => ['widget' => 'btn-close'],
+ 'data' => ['bs-dismiss' => 'toast'],
+ 'aria' => ['label' => Yii::t('yii/bootstrap5', 'Close')],
+ ], $this->closeButton);
+ }
+
+ if (!isset($this->options['role'])) {
+ $this->options['role'] = 'alert';
+ }
+ if (!isset($this->options['aria']['live'])) {
+ $this->options['aria'] = [
+ 'live' => 'assertive',
+ 'atomic' => 'true',
+ ];
+ }
+ }
+}
diff --git a/src/ToggleButtonGroup.php b/src/ToggleButtonGroup.php
new file mode 100644
index 0000000..d493743
--- /dev/null
+++ b/src/ToggleButtonGroup.php
@@ -0,0 +1,148 @@
+field($model, 'item_id')->widget(\yii\bootstrap5\ToggleButtonGroup::class, [
+ * 'type' => \yii\bootstrap5\ToggleButtonGroup::TYPE_CHECKBOX
+ * 'items' => [
+ * 'fooValue' => 'BarLabel',
+ * 'barValue' => 'BazLabel'
+ * ]
+ * ]) ?>
+ * ```
+ *
+ * @see https://getbootstrap.com/docs/5.1/components/buttons/#checkbox-and-radio-buttons
+ *
+ * @author Paul Klimov
+ * @author Simon Karlen
+ */
+class ToggleButtonGroup extends InputWidget
+{
+ /**
+ * Checkbox type
+ */
+ public const TYPE_CHECKBOX = 'checkbox';
+ /**
+ * Radio type
+ */
+ public const TYPE_RADIO = 'radio';
+
+ /**
+ * @var string input type, can be [[TYPE_CHECKBOX]] or [[TYPE_RADIO]]
+ */
+ public string $type;
+ /**
+ * @var array the data item used to generate the checkboxes.
+ * The array values are the labels, while the array keys are the corresponding checkbox or radio values.
+ */
+ public array $items = [];
+ /**
+ * @var array the HTML attributes for the label (button) tag.
+ *
+ * @see Html::checkbox()
+ * @see Html::radio()
+ */
+ public array $labelOptions = [
+ 'class' => ['btn', 'btn-outline-secondary'],
+ ];
+ /**
+ * @var bool whether the item labels should be HTML-encoded.
+ */
+ public bool $encodeLabels = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init(): void
+ {
+ parent::init();
+ $this->registerPlugin('button');
+ Html::addCssClass($this->options, ['widget' => 'btn-group']);
+ $this->options['role'] = 'group';
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws Exception
+ * @throws InvalidConfigException
+ *
+ * @return string
+ */
+ public function run(): string
+ {
+ if (!isset($this->options['item'])) {
+ $this->options['item'] = [$this, 'renderItem'];
+ }
+ switch ($this->type) {
+ case 'checkbox':
+ if ($this->hasModel()) {
+ return Html::activeCheckboxList($this->model, $this->attribute, $this->items, $this->options);
+ }
+ return Html::checkboxList($this->name, $this->value, $this->items, $this->options);
+
+ case 'radio':
+ if ($this->hasModel()) {
+ return Html::activeRadioList($this->model, $this->attribute, $this->items, $this->options);
+ }
+ return Html::radioList($this->name, $this->value, $this->items, $this->options);
+
+ default:
+ throw new InvalidConfigException("Unsupported type '$this->type'");
+ }
+ }
+
+ /**
+ * Default callback for checkbox/radio list item rendering.
+ *
+ * @param int $index item index.
+ * @param string $label item label.
+ * @param string $name input name.
+ * @param bool $checked whether the value is checked or not.
+ * @param string $value input value.
+ *
+ * @return string generated HTML.
+ *
+ * @see Html::checkbox()
+ * @see Html::radio()
+ */
+ public function renderItem(int $index, string $label, string $name, bool $checked, string $value): string
+ {
+ unset($index);
+ $labelOptions = $this->labelOptions;
+ $labelOptions['wrapInput'] = false;
+ Html::addCssClass($labelOptions, ['widget' => 'btn']);
+ $type = $this->type;
+ if ($this->encodeLabels) {
+ $label = Html::encode($label);
+ }
+ $options = [
+ 'label' => $label,
+ 'labelOptions' => $labelOptions,
+ 'autocomplete' => 'off',
+ 'value' => $value,
+ ];
+ Html::addCssClass($options, ['widget' => 'btn-check']);
+
+ return Html::$type($name, $checked, $options);
+ }
+}
diff --git a/src/Widget.php b/src/Widget.php
new file mode 100644
index 0000000..644e664
--- /dev/null
+++ b/src/Widget.php
@@ -0,0 +1,27 @@
+
+ */
+final class TranslationBootstrap implements BootstrapInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function bootstrap($app)
+ {
+ $app->getI18n()->translations['yii/bootstrap5'] = [
+ 'class' => GettextMessageSource::class,
+ 'sourceLanguage' => 'en-US',
+ 'basePath' => '@yii/bootstrap5/messages',
+ ];
+ }
+}
diff --git a/src/messages/af/messages.mo b/src/messages/af/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/af/messages.po b/src/messages/af/messages.po
new file mode 100644
index 0000000..740462b
--- /dev/null
+++ b/src/messages/af/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: af\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/ar/messages.mo b/src/messages/ar/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/ar/messages.po b/src/messages/ar/messages.po
new file mode 100644
index 0000000..7bdd518
--- /dev/null
+++ b/src/messages/ar/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ar\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/az/messages.mo b/src/messages/az/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/az/messages.po b/src/messages/az/messages.po
new file mode 100644
index 0000000..846c5e3
--- /dev/null
+++ b/src/messages/az/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: az\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/be/messages.mo b/src/messages/be/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/be/messages.po b/src/messages/be/messages.po
new file mode 100644
index 0000000..ed86866
--- /dev/null
+++ b/src/messages/be/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: be\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/bg/messages.mo b/src/messages/bg/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/bg/messages.po b/src/messages/bg/messages.po
new file mode 100644
index 0000000..caa6282
--- /dev/null
+++ b/src/messages/bg/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: bg\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/bs/messages.mo b/src/messages/bs/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/bs/messages.po b/src/messages/bs/messages.po
new file mode 100644
index 0000000..86a41c9
--- /dev/null
+++ b/src/messages/bs/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: bs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/ca/messages.mo b/src/messages/ca/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/ca/messages.po b/src/messages/ca/messages.po
new file mode 100644
index 0000000..3833670
--- /dev/null
+++ b/src/messages/ca/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ca\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/config.php b/src/messages/config.php
new file mode 100644
index 0000000..8650e51
--- /dev/null
+++ b/src/messages/config.php
@@ -0,0 +1,65 @@
+ __DIR__ . '/..',
+ // string, required, root directory containing message translations.
+ 'messagePath' => __DIR__,
+ // array, required, list of language codes that the extracted messages
+ // should be translated to. For example, ['zh-CN', 'de'].
+ 'languages' => ['af', 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de-CH', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hi', 'hr', 'hu', 'hy', 'id', 'it', 'ja', 'ka', 'kk', 'ko', 'kz', 'lt', 'lv', 'ms', 'nb-NO', 'nl', 'pl', 'pt', 'pt-BR', 'ro', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'tg', 'th', 'tr', 'uk', 'uz', 'vi', 'zh-CN', 'zh-TW'],
+ // string, the name of the function for translating messages.
+ // Defaults to 'Yii::t'. This is used as a mark to find the messages to be
+ // translated. You may use a string for single function name or an array for
+ // multiple function names.
+ 'translator' => 'Yii::t',
+ // boolean, whether to sort messages by keys when merging new messages
+ // with the existing ones. Defaults to false, which means the new (untranslated)
+ // messages will be separated from the old (translated) ones.
+ 'sort' => false,
+ // boolean, whether the message file should be overwritten with the merged messages
+ 'overwrite' => true,
+ // boolean, whether to remove messages that no longer appear in the source code.
+ // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks.
+ 'removeUnused' => true,
+ // boolean, whether to mark messages that no longer appear in the source code.
+ // Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks.
+ 'markUnused' => false,
+ // array, list of patterns that specify which files/directories should NOT be processed.
+ // If empty or not set, all files/directories will be processed.
+ // A path matches a pattern if it contains the pattern string at its end. For example,
+ // '/a/b' will match all files and directories ending with '/a/b';
+ // the '*.svn' will match all files and directories whose name ends with '.svn'.
+ // and the '.svn' will match all files and directories named exactly '.svn'.
+ // Note, the '/' characters in a pattern matches both '/' and '\'.
+ // See helpers/FileHelper::findFiles() description for more details on pattern matching rules.
+ 'except' => [
+ '.svn',
+ '.git',
+ '.gitignore',
+ '.gitkeep',
+ '.hgignore',
+ '.hgkeep',
+ '/messages',
+ ],
+ // array, list of patterns that specify which files (not directories) should be processed.
+ // If empty or not set, all files will be processed.
+ // Please refer to "except" for details about the patterns.
+ // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
+ 'only' => ['*.php'],
+ 'phpFileHeader' => '',
+ // Generated file format. Can be "php", "db" or "po".
+ 'format' => 'po',
+ // Connection component ID for "db" format.
+ //'db' => 'db',
+ //DocBlock used for messages array in generated PHP file. If `null`, default DocBlock will be used.
+ 'phpDocBlock' => null,
+];
diff --git a/src/messages/cs/messages.mo b/src/messages/cs/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/cs/messages.po b/src/messages/cs/messages.po
new file mode 100644
index 0000000..b7763b2
--- /dev/null
+++ b/src/messages/cs/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/da/messages.mo b/src/messages/da/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/da/messages.po b/src/messages/da/messages.po
new file mode 100644
index 0000000..0ba08b2
--- /dev/null
+++ b/src/messages/da/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: da\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/de-CH/messages.mo b/src/messages/de-CH/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..2af4e474c73e8ac1dbd709a293f856d1ca119dba
GIT binary patch
literal 326
zcmca7#4?ou2v~tw28dli93aR6Vo@k90;HROSQv<>1F;+suLfcTAU*}eY(V@Ph`E6H
z6A*I%@n0YYsbgb=s8a>fm6@6PN%{FD#U(|F1*R-cr6nc#dFZ0fIr+t@=z_r+`Q_+h
zA^GX)IjIURMfn9O`Q;eqK&A5%%QDjwOEQ5LLG24p&dAA3EiO*YV{pu?O3h3MGGK-%
aBtk_L^cBidi;7E9le2-+esDu@C;$KlSy1F;+suLfcTAU*}eY(V@Ph`E6H
z6A*I%@n0YYsbgb=s8a>fm6@6PN%{FD#U(|F1*R-cr6nc#dFZ0fIr+t@=z_r+`Q_+h
zA^GX)IjIURMfn9O`Q;eqK&A5%%QDjwOEQ5LLG24p&dAA3Jv=`(kHImoDm615$bcE5
akO&n~&{rr+Eh;WaP0j{N`@s#tp#T6Z^;uv5
literal 0
HcmV?d00001
diff --git a/src/messages/de/messages.po b/src/messages/de/messages.po
new file mode 100644
index 0000000..5d5807f
--- /dev/null
+++ b/src/messages/de/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr "Button"
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr "Schließen"
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr "Anzeigen"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr "Dropdown anzeigen / verstecken"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr "Navigation anzeigen / verstecken"
+
diff --git a/src/messages/el/messages.mo b/src/messages/el/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/el/messages.po b/src/messages/el/messages.po
new file mode 100644
index 0000000..e62d201
--- /dev/null
+++ b/src/messages/el/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/es/messages.mo b/src/messages/es/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/es/messages.po b/src/messages/es/messages.po
new file mode 100644
index 0000000..4d8cc64
--- /dev/null
+++ b/src/messages/es/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/et/messages.mo b/src/messages/et/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/et/messages.po b/src/messages/et/messages.po
new file mode 100644
index 0000000..feb4e89
--- /dev/null
+++ b/src/messages/et/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: et\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/fa/messages.mo b/src/messages/fa/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/fa/messages.po b/src/messages/fa/messages.po
new file mode 100644
index 0000000..34aedda
--- /dev/null
+++ b/src/messages/fa/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: fa\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/fi/messages.mo b/src/messages/fi/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/fi/messages.po b/src/messages/fi/messages.po
new file mode 100644
index 0000000..cd95b4f
--- /dev/null
+++ b/src/messages/fi/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: fi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/fr/messages.mo b/src/messages/fr/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..e1fe9cbb412621f357648d6ab4fdaca9160030e6
GIT binary patch
literal 309
zcmZY2O$x#=5Cz~WRb2bGQ6x8T7s!*8
z%L9=qjOO{u^{g?JwYwWdg=jZ-36<^Mp5j;u=4;ABD*yJ>aPp$zOy9Due9GhD#Xs936M)Vi%;8woES#GIz&Ce04^|rD_FQe7aq`oC-mS27MhK3?iBUFJllw9
zrYY^`EiYG%o``@R@eXl
literal 0
HcmV?d00001
diff --git a/src/messages/it/messages.po b/src/messages/it/messages.po
new file mode 100644
index 0000000..ff2cea5
--- /dev/null
+++ b/src/messages/it/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr "Bottone"
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr "Chiudi"
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr "Indicare"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr "Attiva/Disattiva lista a tendina"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr "Attiva/Disattiva navigazione"
+
diff --git a/src/messages/ja/messages.mo b/src/messages/ja/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..4986299247815789570feef4d21262021c0552d6
GIT binary patch
literal 247
zcmca7#4?ou2v~qv28eAy93Y4RVo@k90;G$8SQv=gfmjZRX9F=O5buK0M}RaN5T65L
zMIgQk#Fd$u`bqiuCB-F0i3O%CPNgL!`FZG~&N=zTspx{i8TsYtVj=nI={cziE=BnT
zDf#7j43C@VJZ{?mxOp?f%W0jD8)rOj>SlPcaK-Z_yMR)ikDJ#%Zf<_uJpFNV!{er9
UKxL1cCOvNMc-*w%ar0^h0Bg}uvH$=8
literal 0
HcmV?d00001
diff --git a/src/messages/ja/messages.po b/src/messages/ja/messages.po
new file mode 100644
index 0000000..299c01b
--- /dev/null
+++ b/src/messages/ja/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr "ボタン"
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr "閉じる"
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr "表示"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr "ドロップダウンをトグル"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/ka/messages.mo b/src/messages/ka/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/ka/messages.po b/src/messages/ka/messages.po
new file mode 100644
index 0000000..2ebb899
--- /dev/null
+++ b/src/messages/ka/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ka\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/kk/messages.mo b/src/messages/kk/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/kk/messages.po b/src/messages/kk/messages.po
new file mode 100644
index 0000000..6890ad6
--- /dev/null
+++ b/src/messages/kk/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: kk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/ko/messages.mo b/src/messages/ko/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/ko/messages.po b/src/messages/ko/messages.po
new file mode 100644
index 0000000..90cbc96
--- /dev/null
+++ b/src/messages/ko/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ko\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/kz/messages.mo b/src/messages/kz/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/kz/messages.po b/src/messages/kz/messages.po
new file mode 100644
index 0000000..6a1fb6e
--- /dev/null
+++ b/src/messages/kz/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: kz\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/lt/messages.mo b/src/messages/lt/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/lt/messages.po b/src/messages/lt/messages.po
new file mode 100644
index 0000000..be16c5e
--- /dev/null
+++ b/src/messages/lt/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: lt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/lv/messages.mo b/src/messages/lv/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/lv/messages.po b/src/messages/lv/messages.po
new file mode 100644
index 0000000..de2a0c3
--- /dev/null
+++ b/src/messages/lv/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: lv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/ms/messages.mo b/src/messages/ms/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/ms/messages.po b/src/messages/ms/messages.po
new file mode 100644
index 0000000..bbf71fa
--- /dev/null
+++ b/src/messages/ms/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ms\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/nb-NO/messages.mo b/src/messages/nb-NO/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/nb-NO/messages.po b/src/messages/nb-NO/messages.po
new file mode 100644
index 0000000..76b1250
--- /dev/null
+++ b/src/messages/nb-NO/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: nb_NO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/nl/messages.mo b/src/messages/nl/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/nl/messages.po b/src/messages/nl/messages.po
new file mode 100644
index 0000000..7b3a9e6
--- /dev/null
+++ b/src/messages/nl/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/pl/messages.mo b/src/messages/pl/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/pl/messages.po b/src/messages/pl/messages.po
new file mode 100644
index 0000000..2cc1256
--- /dev/null
+++ b/src/messages/pl/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/pt-BR/messages.mo b/src/messages/pt-BR/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/pt-BR/messages.po b/src/messages/pt-BR/messages.po
new file mode 100644
index 0000000..fc53756
--- /dev/null
+++ b/src/messages/pt-BR/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/pt/messages.mo b/src/messages/pt/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/pt/messages.po b/src/messages/pt/messages.po
new file mode 100644
index 0000000..7b0436e
--- /dev/null
+++ b/src/messages/pt/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/ro/messages.mo b/src/messages/ro/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/ro/messages.po b/src/messages/ro/messages.po
new file mode 100644
index 0000000..6433185
--- /dev/null
+++ b/src/messages/ro/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ro\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/ru/messages.mo b/src/messages/ru/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..e7f1070e66ebcb90dffe7af52e94661e101ed8c8
GIT binary patch
literal 364
zcmca7#4?ou2v~tw28dli93aR6Vo@k90;HROSQv<>1F;+suLfcTAU*}eJV5*!i1~o{
z2M`MYF*_pzgC>xcfYMGtx-v6UKPf-Iq`0IgvA~qYskEddKM!5hIVZn36!u|`pE^J`9F#W=Y3%f2h
zT`^;ktzXP8jvvn
literal 0
HcmV?d00001
diff --git a/src/messages/ru/messages.po b/src/messages/ru/messages.po
new file mode 100644
index 0000000..cc9a30e
--- /dev/null
+++ b/src/messages/ru/messages.po
@@ -0,0 +1,31 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr "Кнопка"
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr "Закрыть"
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr "Показать"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr "Переключить навигацию"
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr "Переключить навигацию"
diff --git a/src/messages/sk/messages.mo b/src/messages/sk/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/sk/messages.po b/src/messages/sk/messages.po
new file mode 100644
index 0000000..0ff2ce8
--- /dev/null
+++ b/src/messages/sk/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: sk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/sl/messages.mo b/src/messages/sl/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/sl/messages.po b/src/messages/sl/messages.po
new file mode 100644
index 0000000..cb361de
--- /dev/null
+++ b/src/messages/sl/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: sl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/sr-Latn/messages.mo b/src/messages/sr-Latn/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/sr-Latn/messages.po b/src/messages/sr-Latn/messages.po
new file mode 100644
index 0000000..ebd5e5a
--- /dev/null
+++ b/src/messages/sr-Latn/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: sr_Latn\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/sr/messages.mo b/src/messages/sr/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/sr/messages.po b/src/messages/sr/messages.po
new file mode 100644
index 0000000..2bb7420
--- /dev/null
+++ b/src/messages/sr/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: sr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/sv/messages.mo b/src/messages/sv/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/sv/messages.po b/src/messages/sv/messages.po
new file mode 100644
index 0000000..7e853a8
--- /dev/null
+++ b/src/messages/sv/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/tg/messages.mo b/src/messages/tg/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/tg/messages.po b/src/messages/tg/messages.po
new file mode 100644
index 0000000..c770d5f
--- /dev/null
+++ b/src/messages/tg/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: tg\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/th/messages.mo b/src/messages/th/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/th/messages.po b/src/messages/th/messages.po
new file mode 100644
index 0000000..c726a9e
--- /dev/null
+++ b/src/messages/th/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: th\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/tr/messages.mo b/src/messages/tr/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/tr/messages.po b/src/messages/tr/messages.po
new file mode 100644
index 0000000..52e57c6
--- /dev/null
+++ b/src/messages/tr/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/uk/messages.mo b/src/messages/uk/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/uk/messages.po b/src/messages/uk/messages.po
new file mode 100644
index 0000000..f70cb4e
--- /dev/null
+++ b/src/messages/uk/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: uk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/uz/messages.mo b/src/messages/uz/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/uz/messages.po b/src/messages/uz/messages.po
new file mode 100644
index 0000000..66fef00
--- /dev/null
+++ b/src/messages/uz/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: uz\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/vi/messages.mo b/src/messages/vi/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/vi/messages.po b/src/messages/vi/messages.po
new file mode 100644
index 0000000..2954c91
--- /dev/null
+++ b/src/messages/vi/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: vi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/zh-CN/messages.mo b/src/messages/zh-CN/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/zh-CN/messages.po b/src/messages/zh-CN/messages.po
new file mode 100644
index 0000000..5659bf3
--- /dev/null
+++ b/src/messages/zh-CN/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/src/messages/zh-TW/messages.mo b/src/messages/zh-TW/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..7d18eb0e965dfcc5b69f70bcbeb99c4607e599d7
GIT binary patch
literal 28
Vcmca7#4?ou3S@vZ2!jBK1^_I-0p0)r
literal 0
HcmV?d00001
diff --git a/src/messages/zh-TW/messages.po b/src/messages/zh-TW/messages.po
new file mode 100644
index 0000000..391f1ac
--- /dev/null
+++ b/src/messages/zh-TW/messages.po
@@ -0,0 +1,32 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgctxt "yii/bootstrap5"
+msgid "Button"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Close"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Show"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle Dropdown"
+msgstr ""
+
+msgctxt "yii/bootstrap5"
+msgid "Toggle navigation"
+msgstr ""
+
diff --git a/tests/AccordionTest.php b/tests/AccordionTest.php
new file mode 100644
index 0000000..9d9cd55
--- /dev/null
+++ b/tests/AccordionTest.php
@@ -0,0 +1,331 @@
+ [
+ [
+ 'label' => 'Collapsible Group Item #1',
+ 'content' => [
+ 'test content1',
+ 'test content2',
+ ],
+ ],
+ [
+ 'label' => 'Collapsible Group Item #2',
+ 'content' => 'Das ist das Haus vom Nikolaus',
+ 'contentOptions' => [
+ 'class' => 'testContentOptions',
+ ],
+ 'options' => [
+ 'class' => 'testClass',
+ 'id' => 'testId',
+ ],
+ 'footer' => 'Footer',
+ ],
+ [
+ 'label' => '