Init. v1.0.0
This commit is contained in:
commit
c92be88adf
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
# phpstorm project files
|
||||
.idea
|
||||
|
||||
# netbeans project files
|
||||
nbproject
|
||||
|
||||
# zend studio for eclipse project files
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# windows thumbnail cache
|
||||
Thumbs.db
|
||||
|
||||
# composer
|
||||
composer.phar
|
||||
/vendor
|
||||
/composer.lock
|
||||
|
||||
# Mac DS_Store Files
|
||||
.DS_Store
|
||||
|
||||
# phpunit itself is not needed
|
||||
phpunit.phar
|
||||
# local phpunit config
|
||||
/phpunit.xml
|
||||
|
||||
/tests/bootstrap.local.php
|
||||
/tests/runtime
|
||||
/tests/data/config.local.php
|
||||
/tests/docker
|
||||
/tests/dockerids
|
||||
|
||||
# nodejs
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
.phpunit.result.cache
|
49
composer.json
Normal file
49
composer.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "mylistryx/yii2-bootstrap5",
|
||||
"description": "The Twitter Bootstrap v5 extension for the Yii framework",
|
||||
"version": "1.0.0",
|
||||
"keywords": [
|
||||
"yii2",
|
||||
"bootstrap",
|
||||
"bootstrap5"
|
||||
],
|
||||
"type": "yii2-extension",
|
||||
"license": "BSD-3-Clause",
|
||||
"support": {
|
||||
"source": "https://github.com/mylistryx/yii2-bootstrap45"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sergey Zhukovskiy",
|
||||
"email": "mylistryx@gmail.com",
|
||||
"homepage": "https://net23.ru/"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"php": "~7.4.0",
|
||||
"yiisoft/yii2": "~2.0",
|
||||
"npm-asset/bootstrap": "^5.0.0",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"yiisoft/yii2-coding-standards": "~2.0",
|
||||
"cweagans/composer-patches": "^1.7"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"yii\\bootstrap5\\": "src"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
248
src/Accordion.php
Normal file
248
src/Accordion.php
Normal file
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Accordion renders an accordion bootstrap javascript component.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* echo Accordion::widget([
|
||||
* 'items' => [
|
||||
* // equivalent to the above
|
||||
* [
|
||||
* 'label' => 'Collapsible Group Item #1',
|
||||
* 'content' => 'Anim pariatur cliche...',
|
||||
* // open its content by default
|
||||
* 'contentOptions' => ['class' => 'in']
|
||||
* ],
|
||||
* // another group item
|
||||
* [
|
||||
* 'label' => 'Collapsible Group Item #1',
|
||||
* 'content' => 'Anim pariatur cliche...',
|
||||
* 'contentOptions' => [...],
|
||||
* 'options' => [...],
|
||||
* 'expand' => true,
|
||||
* ],
|
||||
* // if you want to swap out .card-block with .list-group, you may use the following
|
||||
* [
|
||||
* 'label' => 'Collapsible Group Item #1',
|
||||
* 'content' => [
|
||||
* 'Anim pariatur cliche...',
|
||||
* 'Anim pariatur cliche...'
|
||||
* ],
|
||||
* 'contentOptions' => [...],
|
||||
* 'options' => [...],
|
||||
* 'footer' => 'Footer' // the footer label in list-group
|
||||
* ],
|
||||
* ]
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @see https://getbootstrap.com/docs/5.0/components/collapse/#accordion-example
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
class Accordion extends Widget
|
||||
{
|
||||
/**
|
||||
* @var array list of groups in the collapse widget. Each array element represents a single
|
||||
* group with the following structure:
|
||||
*
|
||||
* - label: string, required, the group header label.
|
||||
* - encode: bool, optional, whether this label should be HTML-encoded. This param will override
|
||||
* global `$this->encodeLabels` param.
|
||||
* - content: array|string|object, required, the content (HTML) of the group
|
||||
* - options: array, optional, the HTML attributes of the group
|
||||
* - contentOptions: optional, the HTML attributes of the group's content
|
||||
*
|
||||
* Since version 2.0.7 you may also specify this property as key-value pairs, where the key refers to the
|
||||
* `label` and the value refers to `content`. If value is a string it is interpreted as label. If it is
|
||||
* an array, it is interpreted as explained above.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* echo Accordion::widget([
|
||||
* 'items' => [
|
||||
* 'Introduction' => 'This is the first collapsable menu',
|
||||
* 'Second panel' => [
|
||||
* 'content' => 'This is the second collapsable menu',
|
||||
* ],
|
||||
* [
|
||||
* 'label' => 'Third panel',
|
||||
* 'content' => 'This is the third collapsable menu',
|
||||
* ],
|
||||
* ]
|
||||
* ])
|
||||
* ```
|
||||
*/
|
||||
public array $items = [];
|
||||
/**
|
||||
* @var bool whether the labels for header items should be HTML-encoded.
|
||||
*/
|
||||
public bool $encodeLabels = true;
|
||||
/**
|
||||
* @var bool whether to close other items if an item is opened. Defaults to `true` which causes an
|
||||
* accordion effect. Set this to `false` to allow keeping multiple items open at once.
|
||||
*/
|
||||
public bool $autoCloseItems = true;
|
||||
/**
|
||||
* @var array the HTML options for the item toggle tag. Key 'tag' might be used here for the tag name specification.
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* [
|
||||
* 'tag' => 'div',
|
||||
* 'class' => 'custom-toggle',
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
public array $itemToggleOptions = [];
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function run(): string
|
||||
{
|
||||
$this->registerPlugin('collapse');
|
||||
Html::addCssClass($this->options, ['widget' => 'accordion']);
|
||||
return implode("\n", [
|
||||
Html::beginTag('div', $this->options),
|
||||
$this->renderItems(),
|
||||
Html::endTag('div'),
|
||||
]) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders collapsible items as specified on [[items]].
|
||||
* @throws InvalidConfigException if label isn't specified
|
||||
* @return string the rendering result
|
||||
*/
|
||||
public function renderItems(): string
|
||||
{
|
||||
$items = [];
|
||||
$index = 0;
|
||||
$expanded = array_search(true, ArrayHelper::getColumn(ArrayHelper::toArray($this->items), 'expand', true));
|
||||
foreach ($this->items as $key => $item) {
|
||||
if (!is_array($item)) {
|
||||
$item = ['content' => $item];
|
||||
}
|
||||
// BC compatibility: expand first item if none is expanded
|
||||
if ($expanded === false && $index === 0) {
|
||||
$item['expand'] = true;
|
||||
}
|
||||
if (!array_key_exists('label', $item)) {
|
||||
if (is_int($key)) {
|
||||
throw new InvalidConfigException("The 'label' option is required.");
|
||||
} else {
|
||||
$item['label'] = $key;
|
||||
}
|
||||
}
|
||||
$header = ArrayHelper::remove($item, 'label');
|
||||
$options = ArrayHelper::getValue($item, 'options', []);
|
||||
Html::addCssClass($options, ['panel' => 'card']);
|
||||
$items[] = Html::tag('div', $this->renderItem($header, $item, $index++), $options);
|
||||
}
|
||||
|
||||
return implode("\n", $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single collapsible item group
|
||||
* @param string $header a label of the item group [[items]]
|
||||
* @param array $item a single item from [[items]]
|
||||
* @param int $index the item index as each item group content must have an id
|
||||
* @return string the rendering result
|
||||
* @throws InvalidConfigException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function renderItem(string $header, array $item, int $index): string
|
||||
{
|
||||
if (array_key_exists('content', $item)) {
|
||||
$id = $this->options['id'] . '-collapse' . $index;
|
||||
$expand = ArrayHelper::remove($item, 'expand', false);
|
||||
$options = ArrayHelper::getValue($item, 'contentOptions', []);
|
||||
$options['id'] = $id;
|
||||
Html::addCssClass($options, ['widget' => 'collapse']);
|
||||
|
||||
// check if accordion expanded, if true add show class
|
||||
if ($expand) {
|
||||
Html::addCssClass($options, ['visibility' => 'show']);
|
||||
}
|
||||
|
||||
if (!isset($options['aria-label'], $options['aria-labelledby'])) {
|
||||
$options['aria-labelledby'] = $options['id'] . '-heading';
|
||||
}
|
||||
|
||||
$encodeLabel = $item['encode'] ?? $this->encodeLabels;
|
||||
if ($encodeLabel) {
|
||||
$header = Html::encode($header);
|
||||
}
|
||||
|
||||
$itemToggleOptions = array_merge([
|
||||
'tag' => 'button',
|
||||
'type' => 'button',
|
||||
'data-toggle' => 'collapse',
|
||||
'data-target' => '#' . $options['id'],
|
||||
'aria-expanded' => $expand ? 'true' : 'false',
|
||||
'aria-controls' => $options['id'],
|
||||
], $this->itemToggleOptions);
|
||||
|
||||
$itemToggleTag = ArrayHelper::remove($itemToggleOptions, 'tag', 'button');
|
||||
if ($itemToggleTag === 'a') {
|
||||
ArrayHelper::remove($itemToggleOptions, 'data-target');
|
||||
$headerToggle = Html::a($header, '#' . $id, $itemToggleOptions) . "\n";
|
||||
} else {
|
||||
Html::addCssClass($itemToggleOptions, ['feature' => 'btn-link']);
|
||||
$headerToggle = Button::widget([
|
||||
'label' => $header,
|
||||
'encodeLabel' => false,
|
||||
'options' => $itemToggleOptions,
|
||||
]) . "\n";
|
||||
}
|
||||
|
||||
$header = Html::tag('h5', $headerToggle, ['class' => 'mb-0']);
|
||||
|
||||
if (is_string($item['content']) || is_numeric($item['content']) || is_object($item['content'])) {
|
||||
$content = Html::tag('div', $item['content'], ['class' => 'card-body']) . "\n";
|
||||
} elseif (is_array($item['content'])) {
|
||||
$content = Html::ul($item['content'], [
|
||||
'class' => 'list-group',
|
||||
'itemOptions' => [
|
||||
'class' => 'list-group-item',
|
||||
],
|
||||
'encode' => false,
|
||||
]) . "\n";
|
||||
} else {
|
||||
throw new InvalidConfigException('The "content" option should be a string, array or object.');
|
||||
}
|
||||
} else {
|
||||
throw new InvalidConfigException('The "content" option is required.');
|
||||
}
|
||||
$group = [];
|
||||
|
||||
if ($this->autoCloseItems) {
|
||||
$options['data-parent'] = '#' . $this->options['id'];
|
||||
}
|
||||
|
||||
$group[] = Html::tag('div', $header, ['class' => 'card-header', 'id' => $options['id'] . '-heading']);
|
||||
$group[] = Html::beginTag('div', $options);
|
||||
$group[] = $content;
|
||||
if (isset($item['footer'])) {
|
||||
$group[] = Html::tag('div', $item['footer'], ['class' => 'card-footer']);
|
||||
}
|
||||
$group[] = Html::endTag('div');
|
||||
|
||||
return implode("\n", $group);
|
||||
}
|
||||
}
|
558
src/ActiveField.php
Normal file
558
src/ActiveField.php
Normal file
@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* A Bootstrap 5 enhanced version of [[\yii\widgets\ActiveField]].
|
||||
*
|
||||
* This class adds some useful features to [[\yii\widgets\ActiveField|ActiveField]] to render all
|
||||
* sorts of Bootstrap 5 form fields in different form layouts:
|
||||
*
|
||||
* - [[inputTemplate]] is an optional template to render complex inputs, for example input groups
|
||||
* - [[horizontalCssClasses]] defines the CSS grid classes to add to label, wrapper, error and hint
|
||||
* in horizontal forms
|
||||
* - [[inline]]/[[inline()]] is used to render inline [[checkboxList()]] and [[radioList()]]
|
||||
* - [[enableError]] can be set to `false` to disable to the error
|
||||
* - [[enableLabel]] can be set to `false` to disable to the label
|
||||
* - [[label()]] can be used with a `bool` argument to enable/disable the label
|
||||
*
|
||||
* There are also some new placeholders that you can use in the [[template]] configuration:
|
||||
*
|
||||
* - `{beginLabel}`: the opening label tag
|
||||
* - `{labelTitle}`: the label title for use with `{beginLabel}`/`{endLabel}`
|
||||
* - `{endLabel}`: the closing label tag
|
||||
* - `{beginWrapper}`: the opening wrapper tag
|
||||
* - `{endWrapper}`: the closing wrapper tag
|
||||
*
|
||||
* The wrapper tag is only used for some layouts and form elements.
|
||||
*
|
||||
* Note that some elements use slightly different defaults for [[template]] and other options.
|
||||
* You may want to override those predefined templates for checkboxes, radio buttons, checkboxLists
|
||||
* and radioLists in the [[\yii\widgets\ActiveForm::fieldConfig|fieldConfig]] of the
|
||||
* [[\yii\widgets\ActiveForm]]:
|
||||
*
|
||||
* - [[checkTemplate]] the default template for checkboxes and radios
|
||||
* - [[radioTemplate]] the template for radio buttons in default layout
|
||||
* - [[checkHorizontalTemplate]] the template for checkboxes in horizontal layout
|
||||
* - [[radioHorizontalTemplate]] the template for radio buttons in horizontal layout
|
||||
* - [[checkEnclosedTemplate]] the template for checkboxes and radios enclosed by label
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* use yii\bootstrap5\ActiveForm;
|
||||
*
|
||||
* $form = ActiveForm::begin(['layout' => 'horizontal']);
|
||||
*
|
||||
* // Form field without label
|
||||
* echo $form->field($model, 'demo', [
|
||||
* 'inputOptions' => [
|
||||
* 'placeholder' => $model->getAttributeLabel('demo'),
|
||||
* ],
|
||||
* ])->label(false);
|
||||
*
|
||||
* // Inline radio list
|
||||
* echo $form->field($model, 'demo')->inline()->radioList($items);
|
||||
*
|
||||
* // Control sizing in horizontal mode
|
||||
* echo $form->field($model, 'demo', [
|
||||
* 'horizontalCssClasses' => [
|
||||
* 'wrapper' => 'col-sm-2',
|
||||
* ]
|
||||
* ]);
|
||||
*
|
||||
* // With 'default' layout you would use 'template' to size a specific field:
|
||||
* echo $form->field($model, 'demo', [
|
||||
* 'template' => '{label} <div class="row"><div class="col-sm-4">{input}{error}{hint}</div></div>'
|
||||
* ]);
|
||||
*
|
||||
* // Input group
|
||||
* echo $form->field($model, 'demo', [
|
||||
* 'inputTemplate' => '<div class="input-group"><div class="input-group-prepend">
|
||||
* <span class="input-group-text">@</span>
|
||||
* </div>{input}</div>',
|
||||
* ]);
|
||||
*
|
||||
* ActiveForm::end();
|
||||
* ```
|
||||
*
|
||||
* @property-read ActiveForm $form
|
||||
*
|
||||
* @see ActiveForm
|
||||
* @see https://getbootstrap.com/docs/5.0/components/forms/
|
||||
*
|
||||
* @author Michael Härtl <haertl.mike@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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' => 'form-group']];
|
||||
/**
|
||||
* {@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.
|
||||
* @since 2.0.7
|
||||
*/
|
||||
public array $checkOptions = [
|
||||
'class' => ['widget' => 'custom-control-input'],
|
||||
'labelOptions' => [
|
||||
'class' => ['widget' => 'custom-control-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.
|
||||
* @since 2.0.7
|
||||
*/
|
||||
public array $radioOptions = [
|
||||
'class' => ['widget' => 'custom-control-input'],
|
||||
'labelOptions' => [
|
||||
'class' => ['widget' => 'custom-control-label'],
|
||||
],
|
||||
];
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $errorOptions = ['class' => 'invalid-feedback'];
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $labelOptions = [];
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $hintOptions = ['class' => ['widget' => 'form-text', 'text-muted'], 'tag' => 'small'];
|
||||
/**
|
||||
* @var null|array 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 $horizontalCssClasses = [];
|
||||
/**
|
||||
* @var string the template for checkboxes in default layout
|
||||
*/
|
||||
public string $checkTemplate = "<div class=\"custom-control custom-checkbox\">\n{input}\n{label}\n{error}\n{hint}\n</div>";
|
||||
/**
|
||||
* @var string the template for radios in default layout
|
||||
* @since 2.0.5
|
||||
*/
|
||||
public string $radioTemplate = "<div class=\"custom-control custom-radio\">\n{input}\n{label}\n{error}\n{hint}\n</div>";
|
||||
/**
|
||||
* @var string the template for checkboxes and radios in horizontal layout
|
||||
*/
|
||||
public string $checkHorizontalTemplate = "{beginWrapper}\n<div class=\"custom-control custom-checkbox\">\n{input}\n{label}\n{error}\n{hint}\n</div>\n{endWrapper}";
|
||||
/**
|
||||
* @var string the template for checkboxes and radios in horizontal layout
|
||||
* @since 2.0.5
|
||||
*/
|
||||
public string $radioHorizontalTemplate = "{beginWrapper}\n<div class=\"custom-control custom-radio\">\n{input}\n{label}\n{error}\n{hint}\n</div>\n{endWrapper}";
|
||||
/**
|
||||
* @var string the `enclosed by label` template for checkboxes and radios in default layout
|
||||
*/
|
||||
public string $checkEnclosedTemplate = "<div class=\"form-check\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n</div>";
|
||||
/**
|
||||
* @var bool whether to render the error. Default is `true` except for layout `inline`.
|
||||
*/
|
||||
public bool $enableError = true;
|
||||
/**
|
||||
* @var bool whether to render the label. Default is `true`.
|
||||
*/
|
||||
public bool $enableLabel = true;
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
$layoutConfig = $this->createLayoutConfig($config);
|
||||
$config = ArrayHelper::merge($layoutConfig, $config);
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render($content = null): string
|
||||
{
|
||||
if ($content === null) {
|
||||
if (!isset($this->parts['{beginWrapper}'])) {
|
||||
$options = $this->wrapperOptions;
|
||||
$tag = ArrayHelper::remove($options, 'tag', 'div');
|
||||
$this->parts['{beginWrapper}'] = Html::beginTag($tag, $options);
|
||||
$this->parts['{endWrapper}'] = Html::endTag($tag);
|
||||
}
|
||||
if ($this->enableLabel === false) {
|
||||
$this->parts['{label}'] = '';
|
||||
$this->parts['{beginLabel}'] = '';
|
||||
$this->parts['{labelTitle}'] = '';
|
||||
$this->parts['{endLabel}'] = '';
|
||||
} elseif (!isset($this->parts['{beginLabel}'])) {
|
||||
$this->renderLabelParts();
|
||||
}
|
||||
if ($this->enableError === false) {
|
||||
$this->parts['{error}'] = '';
|
||||
}
|
||||
if ($this->inputTemplate) {
|
||||
$options = $this->inputOptions;
|
||||
|
||||
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
|
||||
$this->addErrorClassIfNeeded($options);
|
||||
}
|
||||
$this->addAriaAttributes($options);
|
||||
|
||||
$input = $this->parts['{input}'] ?? Html::activeTextInput($this->model, $this->attribute, $options);
|
||||
$this->parts['{input}'] = strtr($this->inputTemplate, ['{input}' => $input]);
|
||||
}
|
||||
}
|
||||
return parent::render($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkbox($options = [], $enclosedByLabel = false)
|
||||
{
|
||||
$checkOptions = $this->checkOptions;
|
||||
$options = ArrayHelper::merge($checkOptions, $options);
|
||||
Html::removeCssClass($options, 'form-control');
|
||||
$labelOptions = ArrayHelper::remove($options, 'labelOptions', []);
|
||||
$wrapperOptions = ArrayHelper::remove($options, 'wrapperOptions', []);
|
||||
$this->labelOptions = ArrayHelper::merge($this->labelOptions, $labelOptions);
|
||||
$this->wrapperOptions = ArrayHelper::merge($this->wrapperOptions, $wrapperOptions);
|
||||
|
||||
if (!isset($options['template'])) {
|
||||
$this->template = ($enclosedByLabel) ? $this->checkEnclosedTemplate : $this->checkTemplate;
|
||||
} else {
|
||||
$this->template = $options['template'];
|
||||
}
|
||||
if ($this->form->layout === ActiveForm::LAYOUT_HORIZONTAL) {
|
||||
if (!isset($options['template'])) {
|
||||
$this->template = $this->checkHorizontalTemplate;
|
||||
}
|
||||
Html::removeCssClass($this->labelOptions, $this->horizontalCssClasses['label']);
|
||||
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']);
|
||||
}
|
||||
unset($options['template']);
|
||||
|
||||
if ($enclosedByLabel) {
|
||||
if (isset($options['label'])) {
|
||||
$this->parts['{labelTitle}'] = $options['label'];
|
||||
}
|
||||
}
|
||||
|
||||
return parent::checkbox($options, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function radio($options = [], $enclosedByLabel = false)
|
||||
{
|
||||
$checkOptions = $this->radioOptions;
|
||||
$options = ArrayHelper::merge($checkOptions, $options);
|
||||
Html::removeCssClass($options, 'form-control');
|
||||
$labelOptions = ArrayHelper::remove($options, 'labelOptions', []);
|
||||
$wrapperOptions = ArrayHelper::remove($options, 'wrapperOptions', []);
|
||||
$this->labelOptions = ArrayHelper::merge($this->labelOptions, $labelOptions);
|
||||
$this->wrapperOptions = ArrayHelper::merge($this->wrapperOptions, $wrapperOptions);
|
||||
|
||||
if (!isset($options['template'])) {
|
||||
$this->template = $enclosedByLabel ? $this->checkEnclosedTemplate : $this->radioTemplate;
|
||||
} else {
|
||||
$this->template = $options['template'];
|
||||
}
|
||||
if ($this->form->layout === ActiveForm::LAYOUT_HORIZONTAL) {
|
||||
if (!isset($options['template'])) {
|
||||
$this->template = $this->radioHorizontalTemplate;
|
||||
}
|
||||
Html::removeCssClass($this->labelOptions, $this->horizontalCssClasses['label']);
|
||||
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']);
|
||||
}
|
||||
unset($options['template']);
|
||||
|
||||
if ($enclosedByLabel && isset($options['label'])) {
|
||||
$this->parts['{labelTitle}'] = $options['label'];
|
||||
}
|
||||
|
||||
return parent::radio($options, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkboxList($items, $options = [])
|
||||
{
|
||||
if (!isset($options['item'])) {
|
||||
$this->template = str_replace("\n{error}", '', $this->template);
|
||||
$itemOptions = $options['itemOptions'] ?? [];
|
||||
$encode = ArrayHelper::getValue($options, 'encode', true);
|
||||
$itemCount = count($items) - 1;
|
||||
$error = $this->error()->parts['{error}'];
|
||||
$options['item'] = function ($i, $label, $name, $checked, $value) use (
|
||||
$itemOptions,
|
||||
$encode,
|
||||
$itemCount,
|
||||
$error
|
||||
) {
|
||||
$options = array_merge($this->checkOptions, [
|
||||
'label' => $encode ? Html::encode($label) : $label,
|
||||
'value' => $value,
|
||||
], $itemOptions);
|
||||
$wrapperOptions = ArrayHelper::remove($options, 'wrapperOptions', ['class' => ['custom-control', 'custom-checkbox']]);
|
||||
if ($this->inline) {
|
||||
Html::addCssClass($wrapperOptions, 'custom-control-inline');
|
||||
}
|
||||
|
||||
$html = Html::beginTag('div', $wrapperOptions) . "\n" .
|
||||
Html::checkbox($name, $checked, $options) . "\n";
|
||||
if ($itemCount === $i) {
|
||||
$html .= $error . "\n";
|
||||
}
|
||||
$html .= Html::endTag('div') . "\n";
|
||||
|
||||
return $html;
|
||||
};
|
||||
}
|
||||
|
||||
parent::checkboxList($items, $options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function radioList($items, $options = [])
|
||||
{
|
||||
if (!isset($options['item'])) {
|
||||
$this->template = str_replace("\n{error}", '', $this->template);
|
||||
$itemOptions = $options['itemOptions'] ?? [];
|
||||
$encode = ArrayHelper::getValue($options, 'encode', true);
|
||||
$itemCount = count($items) - 1;
|
||||
$error = $this->error()->parts['{error}'];
|
||||
$options['item'] = function ($i, $label, $name, $checked, $value) use (
|
||||
$itemOptions,
|
||||
$encode,
|
||||
$itemCount,
|
||||
$error
|
||||
) {
|
||||
$options = array_merge($this->radioOptions, [
|
||||
'label' => $encode ? Html::encode($label) : $label,
|
||||
'value' => $value,
|
||||
], $itemOptions);
|
||||
$wrapperOptions = ArrayHelper::remove($options, 'wrapperOptions', ['class' => ['custom-control', 'custom-radio']]);
|
||||
if ($this->inline) {
|
||||
Html::addCssClass($wrapperOptions, 'custom-control-inline');
|
||||
}
|
||||
|
||||
$html = Html::beginTag('div', $wrapperOptions) . "\n" .
|
||||
Html::radio($name, $checked, $options) . "\n";
|
||||
if ($itemCount === $i) {
|
||||
$html .= $error . "\n";
|
||||
}
|
||||
$html .= Html::endTag('div') . "\n";
|
||||
|
||||
return $html;
|
||||
};
|
||||
}
|
||||
|
||||
parent::radioList($items, $options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listBox($items, $options = [])
|
||||
{
|
||||
if ($this->form->layout === ActiveForm::LAYOUT_INLINE) {
|
||||
Html::removeCssClass($this->labelOptions, 'sr-only');
|
||||
}
|
||||
|
||||
return parent::listBox($items, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dropdownList($items, $options = [])
|
||||
{
|
||||
if ($this->form->layout === ActiveForm::LAYOUT_INLINE) {
|
||||
Html::removeCssClass($this->labelOptions, 'sr-only');
|
||||
}
|
||||
|
||||
return parent::dropdownList($items, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders Bootstrap static form control.
|
||||
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
|
||||
* the attributes of the resulting tag. There are also a special options:
|
||||
*
|
||||
* - encode: bool, whether value should be HTML-encoded or not.
|
||||
*
|
||||
* @return $this the field object itself
|
||||
* @see https://getbootstrap.com/docs/5.0/components/forms/#readonly-plain-text
|
||||
*/
|
||||
public function staticControl($options = []): self
|
||||
{
|
||||
$this->adjustLabelFor($options);
|
||||
$this->parts['{input}'] = Html::activeStaticControl($this->model, $this->attribute, $options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label($label = null, $options = [])
|
||||
{
|
||||
if (is_bool($label)) {
|
||||
$this->enableLabel = $label;
|
||||
if ($label === false && $this->form->layout === ActiveForm::LAYOUT_HORIZONTAL) {
|
||||
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']);
|
||||
}
|
||||
} else {
|
||||
$this->enableLabel = true;
|
||||
$this->renderLabelParts($label, $options);
|
||||
parent::label($label, $options);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $value whether to render a inline list
|
||||
* @return $this the field object itself
|
||||
* Make sure you call this method before [[checkboxList()]] or [[radioList()]] to have any effect.
|
||||
*/
|
||||
public function inline($value = true): self
|
||||
{
|
||||
$this->inline = (bool)$value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $instanceConfig the configuration passed to this instance's constructor
|
||||
* @return array the layout specific default configuration for this instance
|
||||
*/
|
||||
protected function createLayoutConfig(array $instanceConfig): array
|
||||
{
|
||||
$config = [
|
||||
'hintOptions' => [
|
||||
'tag' => 'small',
|
||||
'class' => ['form-text', 'text-muted'],
|
||||
],
|
||||
'errorOptions' => [
|
||||
'tag' => 'div',
|
||||
'class' => 'invalid-feedback',
|
||||
],
|
||||
'inputOptions' => [
|
||||
'class' => 'form-control',
|
||||
],
|
||||
'labelOptions' => [
|
||||
'class' => [],
|
||||
],
|
||||
];
|
||||
|
||||
$layout = $instanceConfig['form']->layout;
|
||||
|
||||
if ($layout === ActiveForm::LAYOUT_HORIZONTAL) {
|
||||
$config['template'] = "{label}\n{beginWrapper}\n{input}\n{error}\n{hint}\n{endWrapper}";
|
||||
$config['wrapperOptions'] = [];
|
||||
$config['labelOptions'] = [];
|
||||
$config['options'] = [];
|
||||
$cssClasses = [
|
||||
'offset' => ['col-sm-10', 'offset-sm-2'],
|
||||
'label' => ['col-sm-2', 'col-form-label'],
|
||||
'wrapper' => 'col-sm-10',
|
||||
'error' => '',
|
||||
'hint' => '',
|
||||
'field' => 'form-group row',
|
||||
];
|
||||
if (isset($instanceConfig['horizontalCssClasses'])) {
|
||||
$cssClasses = ArrayHelper::merge($cssClasses, $instanceConfig['horizontalCssClasses']);
|
||||
}
|
||||
$config['horizontalCssClasses'] = $cssClasses;
|
||||
|
||||
Html::addCssClass($config['wrapperOptions'], $cssClasses['wrapper']);
|
||||
Html::addCssClass($config['labelOptions'], $cssClasses['label']);
|
||||
Html::addCssClass($config['errorOptions'], $cssClasses['error']);
|
||||
Html::addCssClass($config['hintOptions'], $cssClasses['hint']);
|
||||
Html::addCssClass($config['options'], $cssClasses['field']);
|
||||
} elseif ($layout === ActiveForm::LAYOUT_INLINE) {
|
||||
$config['inputOptions']['placeholder'] = true;
|
||||
$config['enableError'] = false;
|
||||
|
||||
Html::addCssClass($config['labelOptions'], ['screenreader' => 'sr-only']);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fileInput($options = [])
|
||||
{
|
||||
Html::addCssClass($options, ['widget' => 'form-control-file']);
|
||||
return parent::fileInput($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $label the label or null to use model label
|
||||
* @param array $options the tag options
|
||||
*/
|
||||
protected function renderLabelParts($label = null, $options = [])
|
||||
{
|
||||
$options = array_merge($this->labelOptions, $options);
|
||||
if ($label === null) {
|
||||
if (isset($options['label'])) {
|
||||
$label = $options['label'];
|
||||
unset($options['label']);
|
||||
} else {
|
||||
$attribute = Html::getAttributeName($this->attribute);
|
||||
$label = Html::encode($this->model->getAttributeLabel($attribute));
|
||||
}
|
||||
}
|
||||
if (!isset($options['for'])) {
|
||||
$options['for'] = Html::getInputId($this->model, $this->attribute);
|
||||
}
|
||||
$this->parts['{beginLabel}'] = Html::beginTag('label', $options);
|
||||
$this->parts['{endLabel}'] = Html::endTag('label');
|
||||
if (!isset($this->parts['{labelTitle}'])) {
|
||||
$this->parts['{labelTitle}'] = $label;
|
||||
}
|
||||
}
|
||||
}
|
134
src/ActiveForm.php
Normal file
134
src/ActiveForm.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
|
||||
/**
|
||||
* A Bootstrap 4 enhanced version of [[\yii\widgets\ActiveForm]].
|
||||
*
|
||||
* This class mainly adds the [[layout]] property to choose a Bootstrap 4 form layout.
|
||||
* So for example to render a horizontal form you would:
|
||||
*
|
||||
* ```php
|
||||
* use yii\bootstrap5\ActiveForm;
|
||||
*
|
||||
* $form = ActiveForm::begin(['layout' => 'horizontal'])
|
||||
* ```
|
||||
*
|
||||
* This will set default values for the [[ActiveField]]
|
||||
* to render horizontal form fields. In particular the [[ActiveField::template|template]]
|
||||
* is set to `{label} {beginWrapper} {input} {error} {endWrapper} {hint}` and the
|
||||
* [[ActiveField::horizontalCssClasses|horizontalCssClasses]] are set to:
|
||||
*
|
||||
* ```php
|
||||
* [
|
||||
* 'offset' => 'offset-sm-3',
|
||||
* 'label' => 'col-sm-3',
|
||||
* 'wrapper' => 'col-sm-6',
|
||||
* 'error' => '',
|
||||
* 'hint' => 'col-sm-3',
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* To get a different column layout in horizontal mode you can modify those options
|
||||
* through [[fieldConfig]]:
|
||||
*
|
||||
* ```php
|
||||
* $form = ActiveForm::begin([
|
||||
* 'layout' => 'horizontal',
|
||||
* 'fieldConfig' => [
|
||||
* 'template' => "{label}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}",
|
||||
* 'horizontalCssClasses' => [
|
||||
* 'label' => 'col-sm-4',
|
||||
* 'offset' => 'offset-sm-4',
|
||||
* 'wrapper' => 'col-sm-8',
|
||||
* 'error' => '',
|
||||
* 'hint' => '',
|
||||
* ],
|
||||
* ],
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @see ActiveField for details on the [[fieldConfig]] options
|
||||
* @see https://getbootstrap.com/docs/5.0/components/forms/
|
||||
*
|
||||
* @author Michael Härtl <haertl.mike@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
class ActiveForm extends \yii\widgets\ActiveForm
|
||||
{
|
||||
/**
|
||||
* Default form layout
|
||||
*/
|
||||
const LAYOUT_DEFAULT = 'default';
|
||||
/**
|
||||
* Horizontal form layout
|
||||
*/
|
||||
const LAYOUT_HORIZONTAL = 'horizontal';
|
||||
/**
|
||||
* Inline form layout
|
||||
*/
|
||||
const LAYOUT_INLINE = 'inline';
|
||||
|
||||
/**
|
||||
* @var string the default field class name when calling [[field()]] to create a new field.
|
||||
* @see fieldConfig
|
||||
*/
|
||||
public $fieldClass = ActiveField::class;
|
||||
/**
|
||||
* @var array HTML attributes for the form tag. Default is `[]`.
|
||||
*/
|
||||
public $options = [];
|
||||
/**
|
||||
* @var string the form layout. Either [[LAYOUT_DEFAULT]], [[LAYOUT_HORIZONTAL]] or [[LAYOUT_INLINE]].
|
||||
* By choosing a layout, an appropriate default field configuration is applied. This will
|
||||
* render the form fields with slightly different markup for each layout. You can
|
||||
* override these defaults through [[fieldConfig]].
|
||||
* @see ActiveField for details on Bootstrap 4 field configuration
|
||||
*/
|
||||
public string $layout = self::LAYOUT_DEFAULT;
|
||||
/**
|
||||
* @var string the CSS class that is added to a field container when the associated attribute has validation error.
|
||||
*/
|
||||
public $errorCssClass = 'is-invalid';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $successCssClass = 'is-valid';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $errorSummaryCssClass = 'alert alert-danger';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $validationStateOn = self::VALIDATION_STATE_ON_INPUT;
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if (!in_array($this->layout, [self::LAYOUT_DEFAULT, self::LAYOUT_HORIZONTAL, self::LAYOUT_INLINE])) {
|
||||
throw new InvalidConfigException('Invalid layout type: ' . $this->layout);
|
||||
}
|
||||
|
||||
if ($this->layout === self::LAYOUT_INLINE) {
|
||||
Html::addCssClass($this->options, ['widget' => 'form-inline']);
|
||||
}
|
||||
parent::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function field($model, $attribute, $options = []): \yii\widgets\ActiveField
|
||||
{
|
||||
return parent::field($model, $attribute, $options);
|
||||
}
|
||||
}
|
140
src/Alert.php
Normal file
140
src/Alert.php
Normal file
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Alert renders an alert bootstrap component.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* echo Alert::widget([
|
||||
* 'options' => [
|
||||
* 'class' => 'alert-info',
|
||||
* ],
|
||||
* 'body' => 'Say hello...',
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* The following example will show the content enclosed between the [[begin()]]
|
||||
* and [[end()]] calls within the alert box:
|
||||
*
|
||||
* ```php
|
||||
* Alert::begin([
|
||||
* 'options' => [
|
||||
* 'class' => 'alert-warning',
|
||||
* ],
|
||||
* ]);
|
||||
*
|
||||
* echo 'Say hello...';
|
||||
*
|
||||
* Alert::end();
|
||||
* ```
|
||||
*
|
||||
* @see https://getbootstrap.com/docs/5.0/components/alerts/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
class Alert extends Widget
|
||||
{
|
||||
/**
|
||||
* @var string the body content in the alert component. Note that anything between
|
||||
* the [[begin()]] and [[end()]] calls of the Alert widget will also be treated
|
||||
* as the body content, and will be rendered before this.
|
||||
*/
|
||||
public $body;
|
||||
/**
|
||||
* @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'.
|
||||
* - 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 [Alert documentation](https://getbootstrap.com/docs/5.0/components/alerts/)
|
||||
* for the supported HTML attributes.
|
||||
*/
|
||||
public $closeButton = [];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->initOptions();
|
||||
|
||||
echo Html::beginTag('div', $this->options) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
echo "\n" . $this->renderBodyEnd();
|
||||
echo "\n" . Html::endTag('div');
|
||||
|
||||
$this->registerPlugin('alert');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the alert body and the close button (if any).
|
||||
* @return string the rendering result
|
||||
*/
|
||||
protected function renderBodyEnd()
|
||||
{
|
||||
return $this->body . "\n" . $this->renderCloseButton() . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the close button.
|
||||
* @return string|null the rendering result
|
||||
*/
|
||||
protected function renderCloseButton(): ?string
|
||||
{
|
||||
if (($closeButton = $this->closeButton) !== false) {
|
||||
$tag = ArrayHelper::remove($closeButton, 'tag', 'button');
|
||||
$label = ArrayHelper::remove($closeButton, 'label', Html::tag('span', '×', [
|
||||
'aria-hidden' => 'true',
|
||||
]));
|
||||
if ($tag === 'button' && !isset($closeButton['type'])) {
|
||||
$closeButton['type'] = 'button';
|
||||
}
|
||||
|
||||
return Html::tag($tag, $label, $closeButton);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the widget options.
|
||||
* This method sets the default values for various options.
|
||||
*/
|
||||
protected function initOptions()
|
||||
{
|
||||
Html::addCssClass($this->options, ['widget' => 'alert']);
|
||||
|
||||
if ($this->closeButton !== false) {
|
||||
$this->closeButton = array_merge([
|
||||
'data-dismiss' => 'alert',
|
||||
'class' => ['widget' => 'close'],
|
||||
], $this->closeButton);
|
||||
|
||||
Html::addCssClass($this->options, ['toggle' => 'alert-dismissible']);
|
||||
}
|
||||
if (!isset($this->options['role'])) {
|
||||
$this->options['role'] = 'alert';
|
||||
}
|
||||
}
|
||||
}
|
180
src/BaseHtml.php
Normal file
180
src/BaseHtml.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\Model;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* BaseHtml provides concrete implementation for [[Html]].
|
||||
*/
|
||||
abstract class BaseHtml extends \yii\helpers\Html
|
||||
{
|
||||
/**
|
||||
* @var int a counter used to generate [[id]] for widgets.
|
||||
* @internal
|
||||
*/
|
||||
public static int $counter = 0;
|
||||
/**
|
||||
* @var string the prefix to the automatically generated widget IDs.
|
||||
* @see getId()
|
||||
*/
|
||||
public static string $autoIdPrefix = 'i';
|
||||
/**
|
||||
* @var array list of tag attributes that should be specially handled when their values are of array type.
|
||||
* In particular, if the value of the `data` attribute is `['name' => 'xyz', 'age' => 13]`, two attributes
|
||||
* will be generated instead of one: `data-name="xyz" data-age="13"`.
|
||||
* @since 2.0.3
|
||||
*/
|
||||
public static $dataAttributes = ['data', 'data-ng', 'ng', 'aria'];
|
||||
|
||||
|
||||
/**
|
||||
* Renders Bootstrap static form control.
|
||||
*
|
||||
* @param string $value static control value.
|
||||
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
|
||||
* the attributes of the resulting tag. There are also a special options:
|
||||
*
|
||||
* @return string generated HTML
|
||||
* @see https://getbootstrap.com/docs/5.0/components/forms/#readonly-plain-text
|
||||
*/
|
||||
public static function staticControl(string $value, array $options = []): string
|
||||
{
|
||||
static::addCssClass($options, 'form-control-plaintext');
|
||||
$value = (string)$value;
|
||||
$options['readonly'] = true;
|
||||
return static::input('text', null, $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Bootstrap static form control for the given model attribute.
|
||||
* @param \yii\base\Model $model the model object.
|
||||
* @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format
|
||||
* about attribute expression.
|
||||
* @param array $options the tag options in terms of name-value pairs. See [[staticControl()]] for details.
|
||||
* @return string generated HTML
|
||||
* @see staticControl()
|
||||
*/
|
||||
public static function activeStaticControl(Model $model, string $attribute, $options = []): string
|
||||
{
|
||||
if (isset($options['value'])) {
|
||||
$value = $options['value'];
|
||||
unset($options['value']);
|
||||
} else {
|
||||
$value = static::getAttributeValue($model, $attribute);
|
||||
}
|
||||
return static::staticControl($value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function radioList($name, $selection = null, $items = [], $options = []): string
|
||||
{
|
||||
if (!isset($options['item'])) {
|
||||
$itemOptions = ArrayHelper::remove($options, 'itemOptions', []);
|
||||
$encode = ArrayHelper::getValue($options, 'encode', true);
|
||||
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $encode) {
|
||||
unset($index);
|
||||
$options = array_merge(
|
||||
[
|
||||
'class' => 'form-check-input',
|
||||
'label' => $encode ? static::encode($label) : $label,
|
||||
'labelOptions' => ['class' => 'form-check-label'],
|
||||
'value' => $value,
|
||||
], $itemOptions);
|
||||
return '<div class="form-check">' . static::radio($name, $checked, $options) . '</div>';
|
||||
};
|
||||
}
|
||||
|
||||
return parent::radioList($name, $selection, $items, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function checkboxList($name, $selection = null, $items = [], $options = []): string
|
||||
{
|
||||
if (!isset($options['item'])) {
|
||||
$itemOptions = ArrayHelper::remove($options, 'itemOptions', []);
|
||||
$encode = ArrayHelper::getValue($options, 'encode', true);
|
||||
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $encode) {
|
||||
unset($index);
|
||||
$options = array_merge(
|
||||
[
|
||||
'class' => 'form-check-input',
|
||||
'label' => $encode ? static::encode($label) : $label,
|
||||
'labelOptions' => ['class' => 'form-check-label'],
|
||||
'value' => $value,
|
||||
], $itemOptions);
|
||||
return '<div class="form-check">' . Html::checkbox($name, $checked, $options) . '</div>';
|
||||
};
|
||||
}
|
||||
|
||||
return parent::checkboxList($name, $selection, $items, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected static function booleanInput($type, $name, $checked = false, $options = []): string
|
||||
{
|
||||
$options['checked'] = (bool)$checked;
|
||||
$value = array_key_exists('value', $options) ? $options['value'] : '1';
|
||||
if (isset($options['uncheck'])) {
|
||||
// add a hidden field so that if the checkbox is not selected, it still submits a value
|
||||
$hiddenOptions = [];
|
||||
if (isset($options['form'])) {
|
||||
$hiddenOptions['form'] = $options['form'];
|
||||
}
|
||||
$hidden = static::hiddenInput($name, $options['uncheck'], $hiddenOptions);
|
||||
unset($options['uncheck']);
|
||||
} else {
|
||||
$hidden = '';
|
||||
}
|
||||
if (isset($options['label'])) {
|
||||
$label = $options['label'];
|
||||
$labelOptions = $options['labelOptions'] ?? [];
|
||||
unset($options['label'], $options['labelOptions']);
|
||||
|
||||
if (!isset($options['id'])) {
|
||||
$options['id'] = static::getId();
|
||||
}
|
||||
|
||||
$input = static::input($type, $name, $value, $options);
|
||||
|
||||
if (isset($labelOptions['wrapInput']) && $labelOptions['wrapInput']) {
|
||||
unset($labelOptions['wrapInput']);
|
||||
$content = static::label($input . $label, $options['id'], $labelOptions);
|
||||
} else {
|
||||
$content = $input . "\n" . static::label($label, $options['id'], $labelOptions);
|
||||
}
|
||||
return $hidden . $content;
|
||||
}
|
||||
|
||||
return $hidden . static::input($type, $name, $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function error($model, $attribute, $options = []): string
|
||||
{
|
||||
if (!array_key_exists('class', $options)) {
|
||||
$options['class'] = ['invalid-feedback'];
|
||||
}
|
||||
return parent::error($model, $attribute, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an autogenerated ID
|
||||
* @return string Autogenerated ID
|
||||
*/
|
||||
protected static function getId(): string
|
||||
{
|
||||
return static::$autoIdPrefix . static::$counter++;
|
||||
}
|
||||
}
|
20
src/BootstrapAsset.php
Normal file
20
src/BootstrapAsset.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\web\AssetBundle;
|
||||
|
||||
class BootstrapAsset extends AssetBundle
|
||||
{
|
||||
public $sourcePath = '@npm/bootstrap/dist';
|
||||
|
||||
public $css = [
|
||||
'css/bootstrap.css',
|
||||
];
|
||||
|
||||
public $js = [
|
||||
'js/bootstrap.bundle.js',
|
||||
];
|
||||
}
|
23
src/BootstrapPluginAsset.php
Normal file
23
src/BootstrapPluginAsset.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\web\AssetBundle;
|
||||
|
||||
/**
|
||||
* Asset bundle for the Twitter bootstrap javascript files.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
*/
|
||||
class BootstrapPluginAsset extends AssetBundle
|
||||
{
|
||||
public $sourcePath = '@npm/bootstrap/dist';
|
||||
public $js = [
|
||||
'js/bootstrap.bundle.js',
|
||||
];
|
||||
public $depends = [
|
||||
BootstrapAsset::class,
|
||||
];
|
||||
}
|
99
src/BootstrapWidgetTrait.php
Normal file
99
src/BootstrapWidgetTrait.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\Json;
|
||||
|
||||
/**
|
||||
* BootstrapWidgetTrait is the trait, which provides basic for all bootstrap widgets features.
|
||||
*
|
||||
* Note: class, which uses this trait must declare public field named `options` with the array default value:
|
||||
*
|
||||
* ```php
|
||||
* class MyWidget extends \yii\base\Widget
|
||||
* {
|
||||
* use BootstrapWidgetTrait;
|
||||
*
|
||||
* public $options = [];
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This field is not present in the trait in order to avoid possible PHP Fatal error on definition conflict.
|
||||
*
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @author Paul Klimov <klimov.paul@gmail.com>
|
||||
*/
|
||||
trait BootstrapWidgetTrait
|
||||
{
|
||||
/**
|
||||
* @var array the options for the underlying Bootstrap JS plugin.
|
||||
* Please refer to the corresponding Bootstrap plugin Web page for possible options.
|
||||
* For example, [this page](http://getbootstrap.com/javascript/#modals) shows
|
||||
* how to use the "Modal" plugin and the supported options (e.g. "remote").
|
||||
*/
|
||||
public array $clientOptions = [];
|
||||
/**
|
||||
* @var array the event handlers for the underlying Bootstrap JS plugin.
|
||||
* Please refer to the corresponding Bootstrap plugin Web page for possible events.
|
||||
* For example, [this page](http://getbootstrap.com/javascript/#modals) shows
|
||||
* how to use the "Modal" plugin and the supported events (e.g. "shown").
|
||||
*/
|
||||
public array $clientEvents = [];
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the widget.
|
||||
* This method will register the bootstrap asset bundle. If you override this method,
|
||||
* make sure you call the parent implementation first.
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
if (!isset($this->options['id'])) {
|
||||
$this->options['id'] = $this->getId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a specific Bootstrap plugin and the related events
|
||||
* @param string $name the name of the Bootstrap plugin
|
||||
*/
|
||||
protected function registerPlugin(string $name)
|
||||
{
|
||||
$view = $this->getView();
|
||||
|
||||
BootstrapPluginAsset::register($view);
|
||||
|
||||
$id = $this->options['id'];
|
||||
|
||||
if ($this->clientOptions !== []) {
|
||||
$options = empty($this->clientOptions) ? '' : Json::htmlEncode($this->clientOptions);
|
||||
$js = "jQuery('#$id').$name($options);";
|
||||
$view->registerJs($js);
|
||||
}
|
||||
|
||||
$this->registerClientEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers JS event handlers that are listed in [[clientEvents]].
|
||||
*/
|
||||
protected function registerClientEvents()
|
||||
{
|
||||
if (!empty($this->clientEvents)) {
|
||||
$id = $this->options['id'];
|
||||
$js = [];
|
||||
foreach ($this->clientEvents as $event => $handler) {
|
||||
$js[] = "jQuery('#$id').on('$event', $handler);";
|
||||
}
|
||||
$this->getView()->registerJs(implode("\n", $js));
|
||||
}
|
||||
}
|
||||
|
||||
abstract function getView();
|
||||
}
|
297
src/Breadcrumbs.php
Normal file
297
src/Breadcrumbs.php
Normal file
@ -0,0 +1,297 @@
|
||||
<?php
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use JsonException;
|
||||
use RuntimeException;
|
||||
use Yii;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Breadcrumbs represents a bootstrap 4 version of [[\yii\widgets\Breadcrumbs]]. It displays
|
||||
* a list of links indicating the position of the current page in the whole site hierarchy.
|
||||
*
|
||||
* To use Breadcrumbs, you need to configure its [[links]] property, which specifies the links to be displayed. For example,
|
||||
*
|
||||
* ```php
|
||||
* echo Breadcrumbs::widget([
|
||||
* 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
|
||||
* 'options' => [],
|
||||
* ]);
|
||||
* ```
|
||||
* @see https://getbootstrap.com/docs/5.0/components/breadcrumb/
|
||||
* @author Alexandr Kozhevnikov <onmotion1@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
class Breadcrumbs extends Widget
|
||||
{
|
||||
/**
|
||||
* @var string the name of the breadcrumb container tag.
|
||||
*/
|
||||
public string $tag = 'ol';
|
||||
/**
|
||||
* @var bool whether to HTML-encode the link labels.
|
||||
*/
|
||||
public bool $encodeLabels = true;
|
||||
/**
|
||||
* @var array the first hyperlink in the breadcrumbs (called home link).
|
||||
* Please refer to [[links]] on the format of the link.
|
||||
* If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]]
|
||||
* with the label 'Home'. If this property is false, the home link will not be rendered.
|
||||
*/
|
||||
public array $homeLink = [];
|
||||
/**
|
||||
* @var array 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 link in the breadcrumbs
|
||||
* with the following structure:
|
||||
*
|
||||
* ```php
|
||||
* [
|
||||
* 'label' => 'label of the link', // required
|
||||
* 'url' => 'url of the link', // optional, will be processed by Url::to()
|
||||
* 'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
*
|
||||
*/
|
||||
public array $links = [];
|
||||
/**
|
||||
* @var string 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.
|
||||
*/
|
||||
public string $itemTemplate = "<li class=\"breadcrumb-item\">{link}</li>\n";
|
||||
/**
|
||||
* @var string 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.
|
||||
*/
|
||||
public string $activeItemTemplate = "<li class=\"breadcrumb-item active\" aria-current=\"page\">{link}</li>\n";
|
||||
/**
|
||||
* @var array the HTML attributes for the widgets nav container tag.
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public array $navOptions = ['aria-label' => 'breadcrumb'];
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the widget.
|
||||
* If you override this method, make sure you call the parent implementation first.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->clientOptions = [];
|
||||
Html::addCssClass($this->options, ['widget' => 'breadcrumb']);
|
||||
}
|
||||
|
||||
public function run(): string
|
||||
{
|
||||
if (!isset($this->options['id'])) {
|
||||
$this->options['id'] = "{$this->getId()}-breadcrumb";
|
||||
}
|
||||
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
Html::addCssClass($this->options, ['widget' => 'breadcrumb']);
|
||||
|
||||
$this->registerPlugin('breadcrumb');
|
||||
|
||||
if (empty($this->links)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$links = [];
|
||||
|
||||
if ($this->homeLink === []) {
|
||||
$links[] = $this->renderItem([
|
||||
'label' => 'Home',
|
||||
'url' => '/',
|
||||
], $this->itemTemplate);
|
||||
} else {
|
||||
$links[] = $this->renderItem($this->homeLink, $this->itemTemplate);
|
||||
}
|
||||
|
||||
foreach ($this->links as $link) {
|
||||
if (!is_array($link)) {
|
||||
$link = ['label' => $link];
|
||||
}
|
||||
|
||||
$links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
|
||||
}
|
||||
|
||||
return Html::tag('nav', Html::tag($this->tag, implode('', $links), $this->options), $this->navOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single breadcrumb item.
|
||||
*
|
||||
* @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
|
||||
* @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the
|
||||
* link.
|
||||
*
|
||||
* @throws JsonException|RuntimeException if `$link` does not have "label" element.
|
||||
*
|
||||
* @return string the rendering result
|
||||
*/
|
||||
protected function renderItem(array $link, string $template): string
|
||||
{
|
||||
$encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
|
||||
|
||||
if (array_key_exists('label', $link)) {
|
||||
$label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
|
||||
} else {
|
||||
throw new RuntimeException('The "label" element is required for each link.');
|
||||
}
|
||||
|
||||
if (isset($link['template'])) {
|
||||
$template = $link['template'];
|
||||
}
|
||||
|
||||
if (isset($link['url'])) {
|
||||
$options = $link;
|
||||
unset($options['template'], $options['label'], $options['url']);
|
||||
$linkHtml = Html::a($label, $link['url'], $options);
|
||||
} else {
|
||||
$linkHtml = $label;
|
||||
}
|
||||
|
||||
return strtr($template, ['{link}' => $linkHtml]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function homeLink(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 link in the breadcrumbs with the following structure:
|
||||
*
|
||||
* ```php
|
||||
* [
|
||||
* 'label' => 'label of the link', // required
|
||||
* 'url' => 'url of the link', // optional, will be processed by Url::to()
|
||||
* 'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* @param array $value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function links(array $value): self
|
||||
{
|
||||
$this->links = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML attributes for the widgets 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;
|
||||
}
|
||||
|
||||
}
|
58
src/Button.php
Normal file
58
src/Button.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
/**
|
||||
* Button renders a bootstrap button.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* echo Button::widget([
|
||||
* 'label' => 'Action',
|
||||
* 'options' => ['class' => 'btn-lg'],
|
||||
* ]);
|
||||
* ```
|
||||
* @see https://getbootstrap.com/docs/5.0/components/buttons/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
204
src/ButtonDropdown.php
Normal file
204
src/ButtonDropdown.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\helpers\Url;
|
||||
|
||||
/**
|
||||
* ButtonDropdown renders a group or split button dropdown bootstrap component.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* // a button group using Dropdown widget
|
||||
* echo ButtonDropdown::widget([
|
||||
* 'label' => 'Action',
|
||||
* 'dropdown' => [
|
||||
* 'items' => [
|
||||
* ['label' => 'DropdownA', 'url' => '/'],
|
||||
* ['label' => 'DropdownB', 'url' => '#'],
|
||||
* ],
|
||||
* ],
|
||||
* ]);
|
||||
* ```
|
||||
* @see https://getbootstrap.com/docs/5.0/components/buttons/
|
||||
* @see https://getbootstrap.com/docs/5.0/components/dropdowns/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
*/
|
||||
class ButtonDropdown extends Widget
|
||||
{
|
||||
/**
|
||||
* The css class part of dropdown
|
||||
*/
|
||||
const DIRECTION_DOWN = 'down';
|
||||
/**
|
||||
* The css class part of dropleft
|
||||
*/
|
||||
const DIRECTION_LEFT = 'left';
|
||||
/**
|
||||
* The css class part of dropright
|
||||
*/
|
||||
const DIRECTION_RIGHT = 'right';
|
||||
/**
|
||||
* The css class part of dropup
|
||||
*/
|
||||
const DIRECTION_UP = 'up';
|
||||
|
||||
/**
|
||||
* @var string the button label
|
||||
*/
|
||||
public string $label = 'Button';
|
||||
/**
|
||||
* @var array the HTML attributes for the 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 $options = [];
|
||||
/**
|
||||
* @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 group.
|
||||
*/
|
||||
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()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
if (!isset($this->buttonOptions['id'])) {
|
||||
$this->buttonOptions['id'] = $this->options['id'] . '-button';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return string
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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 correct css selector in plugin initialisation
|
||||
$this->options['id'] = $this->buttonOptions['id'];
|
||||
|
||||
$this->registerPlugin('dropdown');
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the button dropdown.
|
||||
* @return string the rendering result.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function renderButton(): string
|
||||
{
|
||||
Html::addCssClass($this->buttonOptions, ['widget' => 'btn']);
|
||||
$label = $this->label;
|
||||
if ($this->encodeLabel) {
|
||||
$label = Html::encode($label);
|
||||
}
|
||||
|
||||
if ($this->split) {
|
||||
$buttonOptions = $this->buttonOptions;
|
||||
$this->buttonOptions['data-toggle'] = 'dropdown';
|
||||
$this->buttonOptions['aria-haspopup'] = 'true';
|
||||
$this->buttonOptions['aria-expanded'] = 'false';
|
||||
Html::addCssClass($this->buttonOptions, ['toggle' => 'dropdown-toggle dropdown-toggle-split']);
|
||||
unset($buttonOptions['id']);
|
||||
$splitButton = Button::widget([
|
||||
'label' => '<span class="sr-only">Toggle Dropdown</span>',
|
||||
'encodeLabel' => false,
|
||||
'options' => $this->buttonOptions,
|
||||
'view' => $this->getView(),
|
||||
]);
|
||||
} else {
|
||||
$buttonOptions = $this->buttonOptions;
|
||||
Html::addCssClass($buttonOptions, ['toggle' => 'dropdown-toggle']);
|
||||
$buttonOptions['data-toggle'] = 'dropdown';
|
||||
$buttonOptions['aria-haspopup'] = 'true';
|
||||
$buttonOptions['aria-expanded'] = 'false';
|
||||
$splitButton = '';
|
||||
}
|
||||
|
||||
if (isset($buttonOptions['href'])) {
|
||||
if (is_array($buttonOptions['href'])) {
|
||||
$buttonOptions['href'] = Url::to($buttonOptions['href']);
|
||||
}
|
||||
} else {
|
||||
if ($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.
|
||||
* @return string the rendering result.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function renderDropdown(): string
|
||||
{
|
||||
$config = $this->dropdown;
|
||||
$config['clientOptions'] = false;
|
||||
$config['view'] = $this->getView();
|
||||
/** @var Widget $dropdownClass */
|
||||
$dropdownClass = $this->dropdownClass;
|
||||
return $dropdownClass::widget($config);
|
||||
}
|
||||
}
|
111
src/ButtonGroup.php
Normal file
111
src/ButtonGroup.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* ButtonGroup renders a button group bootstrap component.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* // a button group with items configuration
|
||||
* echo ButtonGroup::widget([
|
||||
* 'buttons' => [
|
||||
* ['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.0/components/buttons/
|
||||
* @see https://getbootstrap.com/docs/5.0/components/button-group/
|
||||
*
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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()
|
||||
{
|
||||
parent::init();
|
||||
Html::addCssClass($this->options, ['widget' => 'btn-group']);
|
||||
if (!isset($this->options['role'])) {
|
||||
$this->options['role'] = 'group';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return string
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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]].
|
||||
* @return string the rendering result.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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'], $button['options']['type'])) {
|
||||
ArrayHelper::setValue($button, 'options.type', 'button');
|
||||
}
|
||||
$buttons[] = Button::widget($button);
|
||||
} else {
|
||||
$buttons[] = $button;
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $buttons);
|
||||
}
|
||||
}
|
110
src/ButtonToolbar.php
Normal file
110
src/ButtonToolbar.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
/**
|
||||
* ButtonToolbar Combines sets of button groups into button toolbars for more complex components.
|
||||
* Use utility classes as needed to space out groups, buttons, and more.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* // a button toolbar with items configuration
|
||||
* echo ButtonToolbar::widget([
|
||||
* 'buttonGroups' => [
|
||||
* [
|
||||
* '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.0/components/buttons/
|
||||
* @see https://getbootstrap.com/docs/5.0/components/button-group/#button-toolbar
|
||||
*
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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()
|
||||
{
|
||||
parent::init();
|
||||
Html::addCssClass($this->options, ['widget' => 'btn-toolbar']);
|
||||
if (!isset($this->options['role'])) {
|
||||
$this->options['role'] = 'toolbar';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return string
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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]].
|
||||
* @return string the rendering result.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
200
src/Carousel.php
Normal file
200
src/Carousel.php
Normal file
@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Carousel renders a carousel bootstrap javascript component.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* echo Carousel::widget([
|
||||
* 'items' => [
|
||||
* // the item contains only the image
|
||||
* '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>',
|
||||
* // equivalent to the above
|
||||
* ['content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-02.jpg"/>'],
|
||||
* // the item contains both the image and the caption
|
||||
* [
|
||||
* 'content' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-03.jpg"/>',
|
||||
* 'caption' => '<h4>This is title</h4><p>This is the caption text</p>',
|
||||
* 'captionOptions' => ['class' => ['d-none', 'd-md-block']]
|
||||
* 'options' => [...],
|
||||
* ],
|
||||
* ]
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @see https://getbootstrap.com/docs/5.0/components/carousel/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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 = [
|
||||
'<span class="carousel-control-prev-icon" aria-hidden="true"></span><span class="visually-hidden">Previous</span>',
|
||||
'<span class="carousel-control-next-icon" aria-hidden="true"></span><span class="visually-hidden">Next</span>',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var bool whether carousel indicators (<ol> 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' => '<img src="http://twitter.github.io/bootstrap/assets/img/bootstrap-mdo-sfmoma-01.jpg"/>',
|
||||
* // optional, the caption (HTML) of the slide
|
||||
* 'caption' => '<h4>This is title</h4><p>This is the caption text</p>',
|
||||
* // 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'];
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the widget.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
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()
|
||||
{
|
||||
$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()
|
||||
{
|
||||
if ($this->showIndicators === false) {
|
||||
return '';
|
||||
}
|
||||
$indicators = [];
|
||||
for ($i = 0, $count = count($this->items); $i < $count; $i++) {
|
||||
$options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i];
|
||||
if ($i === 0) {
|
||||
Html::addCssClass($options, ['activate' => 'active']);
|
||||
}
|
||||
$indicators[] = Html::tag('li', '', $options);
|
||||
}
|
||||
|
||||
return Html::tag('ol', implode("\n", $indicators), ['class' => ['carousel-indicators']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders carousel items as specified on [[items]].
|
||||
* @return string the rendering result
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function renderItems()
|
||||
{
|
||||
$items = [];
|
||||
for ($i = 0, $count = count($this->items); $i < $count; $i++) {
|
||||
$items[] = $this->renderItem($this->items[$i], $i);
|
||||
}
|
||||
|
||||
return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single carousel item
|
||||
* @param string|array $item a single item from [[items]]
|
||||
* @param int $index the item index as the first item should be set to `active`
|
||||
* @return string the rendering result
|
||||
* @throws InvalidConfigException if the item is invalid
|
||||
*/
|
||||
public function renderItem($item, $index)
|
||||
{
|
||||
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.
|
||||
*/
|
||||
public function renderControls()
|
||||
{
|
||||
if (isset($this->controls[0], $this->controls[1])) {
|
||||
return Html::a($this->controls[0], '#' . $this->options['id'], [
|
||||
'class' => 'carousel-control-prev',
|
||||
'data-slide' => 'prev',
|
||||
'role' => 'button',
|
||||
]) . "\n"
|
||||
. Html::a($this->controls[1], '#' . $this->options['id'], [
|
||||
'class' => 'carousel-control-next',
|
||||
'data-slide' => 'next',
|
||||
'role' => 'button',
|
||||
]);
|
||||
} elseif ($this->controls === false) {
|
||||
return '';
|
||||
} else {
|
||||
throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.');
|
||||
}
|
||||
}
|
||||
}
|
161
src/Dropdown.php
Normal file
161
src/Dropdown.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Dropdown renders a Bootstrap dropdown menu component.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* <div class="dropdown">
|
||||
* <a href="#" data-toggle="dropdown" class="dropdown-toggle">Label <b class="caret"></b></a>
|
||||
* <?php
|
||||
* echo Dropdown::widget([
|
||||
* 'items' => [
|
||||
* ['label' => 'DropdownA', 'url' => '/'],
|
||||
* ['label' => 'DropdownB', 'url' => '#'],
|
||||
* ],
|
||||
* ]);
|
||||
* ?>
|
||||
* </div>
|
||||
* ```
|
||||
* @see https://getbootstrap.com/docs/5.0/components/dropdowns/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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()
|
||||
{
|
||||
parent::init();
|
||||
Html::addCssClass($this->options, ['widget' => 'dropdown-menu']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget.
|
||||
* @return string
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function run(): string
|
||||
{
|
||||
BootstrapPluginAsset::register($this->getView());
|
||||
$this->registerClientEvents();
|
||||
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
|
||||
* @return string the rendering result.
|
||||
* @throws InvalidConfigException if the label option is not specified in one of the items.
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function renderItems(array $items, array $options = []): string
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($items as $item) {
|
||||
if (is_string($item)) {
|
||||
$lines[] = ($item === '-')
|
||||
? Html::tag('div', '', ['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) {
|
||||
Html::addCssClass($linkOptions, ['activate' => 'active']);
|
||||
}
|
||||
|
||||
$url = array_key_exists('url', $item) ? $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([
|
||||
'data-toggle' => 'dropdown',
|
||||
'aria-haspopup' => 'true',
|
||||
'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);
|
||||
}
|
||||
}
|
12
src/Html.php
Normal file
12
src/Html.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
/**
|
||||
* Class Html
|
||||
*/
|
||||
class Html extends BaseHtml
|
||||
{
|
||||
}
|
13
src/InputWidget.php
Normal file
13
src/InputWidget.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
/**
|
||||
* Class InputWidget
|
||||
*/
|
||||
abstract class InputWidget extends \yii\widgets\InputWidget
|
||||
{
|
||||
use BootstrapWidgetTrait;
|
||||
}
|
316
src/LinkPager.php
Normal file
316
src/LinkPager.php
Normal file
@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\data\Pagination;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* LinkPager represents a bootstrap 4 version of [[\yii\widgets\LinkPager]]. It displays a list of hyperlinks
|
||||
* that lead to different pages of target.
|
||||
*
|
||||
* LinkPager works with a [[\yii\widget\Pagination]] object which specifies the total number
|
||||
* of pages and the current page number.
|
||||
*
|
||||
* To apply LinkPager globally e.g. in all GridViews, set in configuration DI:
|
||||
*
|
||||
* ```php
|
||||
* 'container' => [
|
||||
* 'definitions' => [
|
||||
* \yii\widgets\LinkPager::class => \yii\bootstrap5\LinkPager::class,
|
||||
* ],
|
||||
* ],
|
||||
* ```
|
||||
*
|
||||
* @see https://getbootstrap.com/docs/5.0/components/pagination/
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
* @since 2.0.2
|
||||
*
|
||||
* @property-read array $pageRange
|
||||
*/
|
||||
class LinkPager extends Widget
|
||||
{
|
||||
/**
|
||||
* @var Pagination the pagination object that this pager is associated with.
|
||||
* You must set this property in order to make LinkPager work.
|
||||
*/
|
||||
public Pagination $pagination;
|
||||
/**
|
||||
* @var array HTML attributes for the pager container tag.
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public array $options = [];
|
||||
/**
|
||||
* @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 each page button.
|
||||
*/
|
||||
public string $pageCssClass;
|
||||
/**
|
||||
* @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 string|bool 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 $nextPageLabel = "<span aria-hidden=\"true\">»</span>\n<span class=\"sr-only\">Next</span>";
|
||||
/**
|
||||
* @var string|bool 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 $prevPageLabel = "<span aria-hidden=\"true\">«</span>\n<span class=\"sr-only\">Previous</span>";
|
||||
/**
|
||||
* @var string|bool 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 $firstPageLabel = false;
|
||||
/**
|
||||
* @var string|bool 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 $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 http://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()
|
||||
{
|
||||
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 http://www.w3.org/TR/html401/struct/links.html#h-12.1.2
|
||||
*/
|
||||
protected function registerLinkTags()
|
||||
{
|
||||
$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(
|
||||
$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(
|
||||
$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, empty($class) ? $this->pageCssClass : $class);
|
||||
|
||||
$linkOptions = $this->linkOptions;
|
||||
$linkOptions['data-page'] = $page;
|
||||
|
||||
if ($active) {
|
||||
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 begin 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];
|
||||
}
|
||||
}
|
313
src/Modal.php
Normal file
313
src/Modal.php
Normal file
@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Modal renders a modal window that can be toggled by clicking on a button.
|
||||
*
|
||||
* The following example will show the content enclosed between the [[begin()]]
|
||||
* and [[end()]] calls within the modal window:
|
||||
*
|
||||
* ~~~php
|
||||
* Modal::begin([
|
||||
* 'title' => 'Hello world',
|
||||
* 'toggleButton' => ['label' => 'click me'],
|
||||
* ]);
|
||||
*
|
||||
* echo 'Say hello...';
|
||||
*
|
||||
* Modal::end();
|
||||
* ~~~
|
||||
*
|
||||
* @see https://getbootstrap.com/docs/5.0/components/modal/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
class Modal extends Widget
|
||||
{
|
||||
/**
|
||||
* The additional css class of extra large modal
|
||||
* @since 2.0.3
|
||||
*/
|
||||
const SIZE_EXTRA_LARGE = 'modal-xl';
|
||||
/**
|
||||
* The additional css class of large modal
|
||||
*/
|
||||
const SIZE_LARGE = 'modal-lg';
|
||||
/**
|
||||
* The additional css class of small modal
|
||||
*/
|
||||
const SIZE_SMALL = 'modal-sm';
|
||||
/**
|
||||
* The additional css class of default modal
|
||||
*/
|
||||
const SIZE_DEFAULT = '';
|
||||
|
||||
/**
|
||||
* @var string the tile content in the modal window.
|
||||
*/
|
||||
public $title;
|
||||
/**
|
||||
* @var array additional title options
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public $titleOptions = [];
|
||||
/**
|
||||
* @var array additional header options
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public $headerOptions = [];
|
||||
/**
|
||||
* @var array body options
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public $bodyOptions = [];
|
||||
/**
|
||||
* @var string the footer content in the modal window.
|
||||
*/
|
||||
public $footer;
|
||||
/**
|
||||
* @var array additional footer options
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public $footerOptions = [];
|
||||
/**
|
||||
* @var string the modal size. Can be [[SIZE_LARGE]] or [[SIZE_SMALL]], or empty for default.
|
||||
*/
|
||||
public $size;
|
||||
/**
|
||||
* @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'.
|
||||
* - 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 [Modal plugin help](http://getbootstrap.com/javascript/#modals)
|
||||
* for the supported HTML attributes.
|
||||
*/
|
||||
public $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](http://getbootstrap.com/javascript/#modals)
|
||||
* for the supported HTML attributes.
|
||||
*/
|
||||
public $toggleButton = false;
|
||||
/**
|
||||
* @var boolean whether to center the modal vertically
|
||||
*
|
||||
* When true the modal-dialog-centered class will be added to the modal-dialog
|
||||
* @since 2.0.9
|
||||
*/
|
||||
public $centerVertical = false;
|
||||
/**
|
||||
* @var boolean whether to make the modal body scrollable
|
||||
*
|
||||
* When true the modal-dialog-scrollable class will be added to the modal-dialog
|
||||
* @since 2.0.9
|
||||
*/
|
||||
public $scrollable = false;
|
||||
/**
|
||||
* @var array modal dialog options
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
* @since 2.0.9
|
||||
*/
|
||||
public $dialogOptions = [];
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the widget.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
$button = $this->renderCloseButton();
|
||||
if ($this->title !== null) {
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
return Html::endTag('div');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the HTML markup for the footer of the modal
|
||||
* @return string the rendering result
|
||||
*/
|
||||
protected function renderFooter()
|
||||
{
|
||||
if ($this->footer !== null) {
|
||||
Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']);
|
||||
return Html::tag('div', "\n" . $this->footer . "\n", $this->footerOptions);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the toggle button.
|
||||
* @return string the rendering result
|
||||
*/
|
||||
protected function renderToggleButton()
|
||||
{
|
||||
if (($toggleButton = $this->toggleButton) !== false) {
|
||||
$tag = ArrayHelper::remove($toggleButton, 'tag', 'button');
|
||||
$label = ArrayHelper::remove($toggleButton, 'label', 'Show');
|
||||
|
||||
return Html::tag($tag, $label, $toggleButton);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the close button.
|
||||
* @return string the rendering result
|
||||
*/
|
||||
protected function renderCloseButton()
|
||||
{
|
||||
if (($closeButton = $this->closeButton) !== false) {
|
||||
$tag = ArrayHelper::remove($closeButton, 'tag', 'button');
|
||||
$label = ArrayHelper::remove($closeButton, 'label', Html::tag('span', '×', [
|
||||
'aria-hidden' => 'true',
|
||||
]));
|
||||
|
||||
return Html::tag($tag, $label, $closeButton);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the widget options.
|
||||
* This method sets the default values for various options.
|
||||
*/
|
||||
protected function initOptions()
|
||||
{
|
||||
$this->options = array_merge([
|
||||
'class' => 'fade',
|
||||
'role' => 'dialog',
|
||||
'tabindex' => -1,
|
||||
'aria-hidden' => 'true',
|
||||
], $this->options);
|
||||
Html::addCssClass($this->options, ['widget' => 'modal']);
|
||||
|
||||
if ($this->clientOptions !== false) {
|
||||
$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']) && $this->title !== null) {
|
||||
$this->options['aria-labelledby'] = $this->titleOptions['id'];
|
||||
}
|
||||
|
||||
if ($this->closeButton !== false) {
|
||||
$this->closeButton = array_merge([
|
||||
'data-dismiss' => 'modal',
|
||||
'class' => 'close',
|
||||
'type' => 'button',
|
||||
], $this->closeButton);
|
||||
}
|
||||
|
||||
if ($this->toggleButton !== false) {
|
||||
$this->toggleButton = array_merge([
|
||||
'data-toggle' => 'modal',
|
||||
'type' => 'button',
|
||||
], $this->toggleButton);
|
||||
if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) {
|
||||
$this->toggleButton['data-target'] = '#' . $this->options['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->dialogOptions = array_merge([
|
||||
'role' => 'document',
|
||||
], $this->dialogOptions);
|
||||
Html::addCssClass($this->dialogOptions, ['widget' => 'modal-dialog']);
|
||||
if ($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']);
|
||||
}
|
||||
}
|
||||
}
|
292
src/Nav.php
Normal file
292
src/Nav.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* Nav renders a nav HTML component.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* echo Nav::widget([
|
||||
* 'items' => [
|
||||
* [
|
||||
* 'label' => 'Home',
|
||||
* 'url' => ['site/index'],
|
||||
* 'linkOptions' => [...],
|
||||
* ],
|
||||
* [
|
||||
* 'label' => 'Dropdown',
|
||||
* 'items' => [
|
||||
* ['label' => 'Level 1 - Dropdown A', 'url' => '#'],
|
||||
* '<div class="dropdown-divider"></div>',
|
||||
* '<div class="dropdown-header">Dropdown Header</div>',
|
||||
* ['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 4.
|
||||
*
|
||||
* @see https://getbootstrap.com/docs/5.0/components/navs/
|
||||
* @see https://getbootstrap.com/docs/5.0/components/dropdowns/
|
||||
*
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
*/
|
||||
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 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 $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;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the widget.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
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.
|
||||
* @return string
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function run(): string
|
||||
{
|
||||
BootstrapAsset::register($this->getView());
|
||||
return $this->renderItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders widget items.
|
||||
* @return string
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function renderItems(): string
|
||||
{
|
||||
$items = [];
|
||||
foreach ($this->items as $i => $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 string|array $item the item to render.
|
||||
* @return string the rendering result.
|
||||
* @throws InvalidConfigException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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-toggle'] = 'dropdown';
|
||||
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.
|
||||
* @return string the rendering result.
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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' => false,
|
||||
'view' => $this->getView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a child item is active optionally activating the parent.
|
||||
* @param array $items @see items
|
||||
* @param bool $active should the parent be active too
|
||||
* @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 ($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
|
||||
* @return bool whether the menu item is active
|
||||
*/
|
||||
protected function isItemActive(array $item): bool
|
||||
{
|
||||
if (!$this->activateItems) {
|
||||
return false;
|
||||
}
|
||||
if (isset($item['active'])) {
|
||||
return ArrayHelper::getValue($item, 'active', false);
|
||||
}
|
||||
if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
|
||||
$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;
|
||||
}
|
||||
}
|
211
src/NavBar.php
Normal file
211
src/NavBar.php
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use Yii;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* NavBar renders a navbar HTML component.
|
||||
*
|
||||
* Any content enclosed between the [[begin()]] and [[end()]] calls of NavBar
|
||||
* is treated as the content of the navbar. You may use widgets such as [[Nav]]
|
||||
* or [[\yii\widgets\Menu]] to build up such content. For example,
|
||||
*
|
||||
* ```php
|
||||
* use yii\bootstrap5\NavBar;
|
||||
* use yii\bootstrap5\Nav;
|
||||
*
|
||||
* NavBar::begin(['brandLabel' => 'NavBar Test']);
|
||||
* echo Nav::widget([
|
||||
* 'items' => [
|
||||
* ['label' => 'Home', 'url' => ['/site/index']],
|
||||
* ['label' => 'About', 'url' => ['/site/about']],
|
||||
* ],
|
||||
* 'options' => ['class' => 'navbar-nav'],
|
||||
* ]);
|
||||
* NavBar::end();
|
||||
* ```
|
||||
*
|
||||
* @property-write array $containerOptions
|
||||
*
|
||||
* @see https://getbootstrap.com/docs/5.0/components/navbar/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Alexander Kochetov <creocoder@gmail.com>
|
||||
*/
|
||||
class NavBar extends Widget
|
||||
{
|
||||
/**
|
||||
* @var array the HTML attributes for the widget container tag. The following special options are recognized:
|
||||
*
|
||||
* - tag: string, defaults to "nav", the name of the container tag.
|
||||
*
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public array $options = [];
|
||||
/**
|
||||
* @var array the HTML attributes for the 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 $collapseOptions = [];
|
||||
/**
|
||||
* @var string|bool 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.0/components/navbar/
|
||||
*/
|
||||
public $brandLabel = false;
|
||||
/**
|
||||
* @var string|bool 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.0/components/navbar/
|
||||
* @since 2.0.8
|
||||
*/
|
||||
public $brandImage = false;
|
||||
/**
|
||||
* @var array|string|bool $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 $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 = 'Toggle navigation';
|
||||
/**
|
||||
* @var string the toggle button content. Defaults to bootstrap 4 default `<span class="navbar-toggler-icon"></span>`
|
||||
*/
|
||||
public string $togglerContent = '<span class="navbar-toggler-icon"></span>';
|
||||
/**
|
||||
* @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 array $clientOptions = [];
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the widget.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
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 (!isset($this->collapseOptions['id'])) {
|
||||
$this->collapseOptions['id'] = "{$this->options['id']}-collapse";
|
||||
}
|
||||
if ($this->brandImage !== false) {
|
||||
$this->brandLabel = Html::img($this->brandImage);
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
Html::addCssClass($this->collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
|
||||
$collapseOptions = $this->collapseOptions;
|
||||
$collapseTag = ArrayHelper::remove($collapseOptions, 'tag', 'div');
|
||||
|
||||
echo Html::beginTag($navTag, $navOptions) . "\n";
|
||||
if ($this->renderInnerContainer) {
|
||||
echo Html::beginTag('div', $this->innerContainerOptions) . "\n";
|
||||
}
|
||||
echo $brand . "\n";
|
||||
echo $this->renderToggleButton() . "\n";
|
||||
echo Html::beginTag($collapseTag, $collapseOptions) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget.
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$tag = ArrayHelper::remove($this->collapseOptions, 'tag', 'div');
|
||||
echo Html::endTag($tag) . "\n";
|
||||
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
|
||||
{
|
||||
$options = $this->togglerOptions;
|
||||
Html::addCssClass($options, ['widget' => 'navbar-toggler']);
|
||||
return Html::button(
|
||||
$this->togglerContent,
|
||||
ArrayHelper::merge($options, [
|
||||
'type' => 'button',
|
||||
'data' => [
|
||||
'toggle' => 'collapse',
|
||||
'target' => '#' . $this->collapseOptions['id'],
|
||||
],
|
||||
'aria-controls' => $this->collapseOptions['id'],
|
||||
'aria-expanded' => 'false',
|
||||
'aria-label' => $this->screenReaderToggleText,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Container options setter for backwards compatibility
|
||||
* @param array $collapseOptions
|
||||
* @deprecated
|
||||
*/
|
||||
public function setContainerOptions(array $collapseOptions)
|
||||
{
|
||||
$this->collapseOptions = $collapseOptions;
|
||||
}
|
||||
}
|
181
src/Progress.php
Normal file
181
src/Progress.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Progress renders a bootstrap progress bar component.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* // default with label
|
||||
* echo Progress::widget([
|
||||
* 'percent' => 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.0/components/progress/
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
class Progress extends Widget
|
||||
{
|
||||
/**
|
||||
* @var string the button label. This property will only be considered if [[bars]] is empty
|
||||
*/
|
||||
public $label;
|
||||
/**
|
||||
* @var int the amount of progress as a percentage. This property will only be considered if [[bars]] is empty
|
||||
*/
|
||||
public $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.
|
||||
* @since 2.0.6
|
||||
*/
|
||||
public $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 $bars;
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
Html::addCssClass($this->options, ['widget' => 'progress']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
BootstrapAsset::register($this->getView());
|
||||
return $this->renderProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the progress.
|
||||
* @return string the rendering result.
|
||||
* @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
|
||||
*/
|
||||
protected function renderProgress()
|
||||
{
|
||||
$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 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($percent, $label = '', $options = [])
|
||||
{
|
||||
$percent = (float)trim(rtrim((string)$percent, '%'));
|
||||
$options = array_merge($options, [
|
||||
'role' => 'progressbar',
|
||||
'aria-valuenow' => $percent,
|
||||
'aria-valuemin' => 0,
|
||||
'aria-valuemax' => 100,
|
||||
]);
|
||||
Html::addCssClass($options, ['widget' => 'progress-bar']);
|
||||
Html::addCssStyle($options, ['width' => $percent . '%'], true);
|
||||
|
||||
return Html::tag('div', $label, $options);
|
||||
}
|
||||
}
|
263
src/Tabs.php
Normal file
263
src/Tabs.php
Normal file
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Tabs renders a Tab bootstrap javascript component.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* echo Tabs::widget([
|
||||
* 'items' => [
|
||||
* [
|
||||
* '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.0/components/navs/#tabs
|
||||
* @see https://getbootstrap.com/docs/5.0/components/card/#navigation
|
||||
* @author Antonio Ramirez <amigo.cobos@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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()
|
||||
{
|
||||
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 items as specified on [[items]].
|
||||
*
|
||||
* @param array $items
|
||||
* @param string $prefix
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
protected function prepareItems(array &$items, string $prefix = '')
|
||||
{
|
||||
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;
|
||||
} else {
|
||||
ArrayHelper::setValue($items[$n], 'options', $headerOptions);
|
||||
if (!isset($item['url'])) {
|
||||
ArrayHelper::setValue($items[$n], 'url', '#' . $options['id']);
|
||||
ArrayHelper::setValue($items[$n], 'linkOptions.data.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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`).
|
||||
*/
|
||||
protected function activateFirstVisibleTab()
|
||||
{
|
||||
foreach ($this->items as $i => $item) {
|
||||
$active = ArrayHelper::getValue($item, 'active', null);
|
||||
$visible = ArrayHelper::getValue($item, 'visible', true);
|
||||
$disabled = ArrayHelper::getValue($item, 'disabled', false);
|
||||
if ($visible && $active !== false && $disabled !== true) {
|
||||
$this->items[$i]['active'] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) : '';
|
||||
}
|
||||
}
|
210
src/Toast.php
Normal file
210
src/Toast.php
Normal file
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use Yii;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Toasts renders an toast bootstrap component.
|
||||
*
|
||||
* For example,
|
||||
*
|
||||
* ```php
|
||||
* echo Toast::widget([
|
||||
* 'title' => '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.0/components/toasts/
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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 int|string|\DateTime|\DateTimeInterface|\DateInterval|false The date time the toast message references to.
|
||||
* This will be formatted as relative time (via formatter component). It will be omitted if
|
||||
* set to `false` (default).
|
||||
*/
|
||||
public $dateTime = false;
|
||||
/**
|
||||
* @var array 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.0/components/toasts/)
|
||||
* for the supported HTML attributes.
|
||||
*/
|
||||
public array $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()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->initOptions();
|
||||
|
||||
echo Html::beginTag('div', $this->options) . "\n";
|
||||
echo $this->renderHeader() . "\n";
|
||||
echo $this->renderBodyBegin() . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
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' => 'mr-auto']);
|
||||
$title = Html::tag($tag, $this->title === null ? '' : $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 the rendering result
|
||||
*/
|
||||
protected function renderCloseButton(): string
|
||||
{
|
||||
$tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
|
||||
$label = ArrayHelper::remove($this->closeButton, 'label', Html::tag('span', '×', [
|
||||
'aria-hidden' => 'true',
|
||||
]));
|
||||
|
||||
return Html::tag($tag, "\n" . $label . "\n", $this->closeButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the widget options.
|
||||
* This method sets the default values for various options.
|
||||
*/
|
||||
protected function initOptions()
|
||||
{
|
||||
Html::addCssClass($this->options, ['widget' => 'toast']);
|
||||
|
||||
$this->closeButton = array_merge([
|
||||
'aria' => ['label' => 'Close'],
|
||||
'data' => ['dismiss' => 'toast'],
|
||||
'class' => ['widget' => 'ml-2 mb-1 close'],
|
||||
'type' => 'button',
|
||||
], $this->closeButton);
|
||||
|
||||
if (!isset($this->options['role'])) {
|
||||
$this->options['role'] = 'alert';
|
||||
}
|
||||
if (!isset($this->options['aria']) && !isset($this->options['aria-live'])) {
|
||||
$this->options['aria'] = [
|
||||
'live' => 'assertive',
|
||||
'atomic' => 'true',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
134
src/ToggleButtonGroup.php
Normal file
134
src/ToggleButtonGroup.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
|
||||
/**
|
||||
* ToggleButtonGroup allows rendering form inputs Checkbox/Radio toggle button groups.
|
||||
*
|
||||
* You can use this widget in an [[yii\bootstrap5\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
|
||||
* method, for example like this:
|
||||
*
|
||||
* ```php
|
||||
* <?= $form->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.0/components/buttons/#checkbox-and-radio-buttons
|
||||
*
|
||||
* @author Paul Klimov <klimov.paul@gmail.com>
|
||||
* @author Simon Karlen <simi.albi@outlook.com>
|
||||
*/
|
||||
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-secondary'],
|
||||
];
|
||||
/**
|
||||
* @var bool whether the items labels should be HTML-encoded.
|
||||
*/
|
||||
public bool $encodeLabels = true;
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->registerPlugin('button');
|
||||
Html::addCssClass($this->options, ['widget' => 'btn-group-toggle']);
|
||||
$this->options['data-toggle'] = 'buttons';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return string
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
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);
|
||||
} else {
|
||||
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);
|
||||
} else {
|
||||
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 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'] = true;
|
||||
Html::addCssClass($labelOptions, ['widget' => 'btn']);
|
||||
if ($checked) {
|
||||
Html::addCssClass($labelOptions, ['activate' => 'active']);
|
||||
}
|
||||
$type = $this->type;
|
||||
if ($this->encodeLabels) {
|
||||
$label = Html::encode($label);
|
||||
}
|
||||
return Html::$type($name, $checked, [
|
||||
'label' => $label,
|
||||
'labelOptions' => $labelOptions,
|
||||
'autocomplete' => 'off',
|
||||
'value' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
19
src/Widget.php
Normal file
19
src/Widget.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace yii\bootstrap5;
|
||||
|
||||
/**
|
||||
* \yii\bootstrap5\Widget is the base class for all bootstrap5 widgets.
|
||||
*/
|
||||
class Widget extends \yii\base\Widget
|
||||
{
|
||||
use BootstrapWidgetTrait;
|
||||
|
||||
/**
|
||||
* @var array the HTML attributes for the widget container tag.
|
||||
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
|
||||
*/
|
||||
public array $options = [];
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user