├── CHANGELOG.md ├── .phpunit-watcher.yml ├── infection.json.dist ├── psalm.xml ├── src ├── Assets │ ├── JqueryAsset.php │ └── BootstrapAsset.php ├── Widget.php ├── Button.php ├── ButtonToolbar.php ├── ButtonGroup.php ├── BootstrapWidgetTrait.php ├── Alert.php ├── Progress.php ├── Breadcrumbs.php ├── Dropdown.php ├── Carousel.php ├── ButtonDropdown.php ├── Accordion.php ├── NavBar.php ├── Modal.php ├── Tabs.php └── Nav.php ├── LICENSE.md ├── composer.json ├── .styleci.yml └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Yii Framework bootstrap4 extension Change Log 2 | 3 | ## 1.0.0 under development 4 | 5 | - Initial release 6 | -------------------------------------------------------------------------------- /.phpunit-watcher.yml: -------------------------------------------------------------------------------- 1 | watch: 2 | directories: 3 | - src 4 | - tests 5 | fileMask: '*.php' 6 | notifications: 7 | passingTests: false 8 | failingTests: false 9 | phpunit: 10 | binaryPath: vendor/bin/phpunit 11 | timeout: 180 12 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ] 6 | }, 7 | "logs": { 8 | "text": "php:\/\/stderr", 9 | "badge": { 10 | "branch": "master" 11 | } 12 | }, 13 | "mutators": { 14 | "@default": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Assets/JqueryAsset.php: -------------------------------------------------------------------------------- 1 | [ 30 | 'dist/jquery.js', 31 | ], 32 | ]; 33 | } 34 | -------------------------------------------------------------------------------- /src/Assets/BootstrapAsset.php: -------------------------------------------------------------------------------- 1 | [ 34 | 'css/bootstrap.css', 35 | 'js/bootstrap.bundle.js', 36 | ], 37 | ]; 38 | 39 | public array $depends = [ 40 | JqueryAsset::class, 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /src/Widget.php: -------------------------------------------------------------------------------- 1 | autoGenerate) { 24 | $this->id = self::$autoIdPrefix . static::$counter++; 25 | } 26 | 27 | return $this->id; 28 | } 29 | 30 | /** 31 | * Set the Id of the widget. 32 | * 33 | * @param string $value 34 | * 35 | * @return $this 36 | */ 37 | public function setId(string $value): self 38 | { 39 | $this->id = $value; 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * Counter used to generate {@see id} for widgets. 46 | * 47 | * @param int $value 48 | */ 49 | public static function counter(int $value): void 50 | { 51 | self::$counter = $value; 52 | } 53 | 54 | /** 55 | * The prefix to the automatically generated widget IDs. 56 | * 57 | * @param string $value 58 | * 59 | * {@see getId()} 60 | */ 61 | public static function autoIdPrefix(string $value): void 62 | { 63 | self::$autoIdPrefix = $value; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software (https://www.yiiframework.com/) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/yii-bootstrap4", 3 | "type": "library", 4 | "description": "Yii Framework Twitter Bootstrap 4 Extension", 5 | "keywords": [ 6 | "yii", 7 | "bootstrap4" 8 | ], 9 | "license": "BSD-3-Clause", 10 | "support": { 11 | "source": "https://github.com/yiisoft/yii-bootstrap4", 12 | "issues": "https://github.com/yiisoft/yii-bootstrap4/issues", 13 | "forum": "http://www.yiiframework.com/forum/", 14 | "wiki": "http://www.yiiframework.com/wiki/", 15 | "irc": "irc://irc.freenode.net/yii" 16 | }, 17 | "minimum-stability": "dev", 18 | "prefer-stable": true, 19 | "provide": { 20 | "yiisoft/yii-bootstrap": "1.0" 21 | }, 22 | "require": { 23 | "php": "^7.4|^8.0", 24 | "npm-asset/bootstrap": "^4.5.3", 25 | "npm-asset/jquery": "^3.5.1", 26 | "oomphinc/composer-installers-extender": "^2.0.0", 27 | "yiisoft/arrays": "^1.0", 28 | "yiisoft/assets": "^1.0@dev", 29 | "yiisoft/html": "^3.0@dev", 30 | "yiisoft/json": "^1.0", 31 | "yiisoft/widget": "^3.0@dev" 32 | }, 33 | "require-dev": { 34 | "phpunit/phpunit": "^9.4", 35 | "roave/infection-static-analysis-plugin": "^1.5", 36 | "spatie/phpunit-watcher": "^1.23", 37 | "vimeo/psalm": "^4.2", 38 | "yiisoft/aliases": "^1.0", 39 | "yiisoft/di": "^3.0@dev" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "Yiisoft\\Yii\\Bootstrap4\\": "src" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Yiisoft\\Yii\\Bootstrap4\\Tests\\": "tests" 49 | } 50 | }, 51 | "extra": { 52 | "branch-alias": { 53 | "dev-master": "3.0.x-dev" 54 | }, 55 | "installer-types": [ 56 | "npm-asset" 57 | ], 58 | "installer-paths": { 59 | "./node_modules/{$name}": [ 60 | "type:npm-asset" 61 | ] 62 | } 63 | }, 64 | "config": { 65 | "sort-packages": true 66 | }, 67 | "repositories": [ 68 | { 69 | "type": "composer", 70 | "url": "https://asset-packagist.org" 71 | } 72 | ], 73 | "scripts": { 74 | "test": "phpunit --testdox --no-interaction", 75 | "test-watch": "phpunit-watcher watch" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr12 2 | risky: true 3 | 4 | version: 8 5 | 6 | finder: 7 | exclude: 8 | - docs 9 | - vendor 10 | - resources 11 | - views 12 | - public 13 | - templates 14 | not-name: 15 | - UnionCar.php 16 | - TimerUnionTypes.php 17 | - schema1.php 18 | 19 | enabled: 20 | - alpha_ordered_traits 21 | - array_indentation 22 | - array_push 23 | - combine_consecutive_issets 24 | - combine_consecutive_unsets 25 | - combine_nested_dirname 26 | - declare_strict_types 27 | - dir_constant 28 | - final_static_access 29 | - fully_qualified_strict_types 30 | - function_to_constant 31 | - hash_to_slash_comment 32 | - is_null 33 | - logical_operators 34 | - magic_constant_casing 35 | - magic_method_casing 36 | - method_separation 37 | - modernize_types_casting 38 | - native_function_casing 39 | - native_function_type_declaration_casing 40 | - no_alias_functions 41 | - no_empty_comment 42 | - no_empty_phpdoc 43 | - no_empty_statement 44 | - no_extra_block_blank_lines 45 | - no_short_bool_cast 46 | - no_superfluous_elseif 47 | - no_unneeded_control_parentheses 48 | - no_unneeded_curly_braces 49 | - no_unneeded_final_method 50 | - no_unset_cast 51 | - no_unused_imports 52 | - no_unused_lambda_imports 53 | - no_useless_else 54 | - no_useless_return 55 | - normalize_index_brace 56 | - php_unit_dedicate_assert 57 | - php_unit_dedicate_assert_internal_type 58 | - php_unit_expectation 59 | - php_unit_mock 60 | - php_unit_mock_short_will_return 61 | - php_unit_namespaced 62 | - php_unit_no_expectation_annotation 63 | - phpdoc_no_empty_return 64 | - phpdoc_no_useless_inheritdoc 65 | - phpdoc_order 66 | - phpdoc_property 67 | - phpdoc_scalar 68 | - phpdoc_separation 69 | - phpdoc_singular_inheritdoc 70 | - phpdoc_trim 71 | - phpdoc_trim_consecutive_blank_line_separation 72 | - phpdoc_type_to_var 73 | - phpdoc_types 74 | - phpdoc_types_order 75 | - print_to_echo 76 | - regular_callable_call 77 | - return_assignment 78 | - self_accessor 79 | - self_static_accessor 80 | - set_type_to_cast 81 | - short_array_syntax 82 | - short_list_syntax 83 | - simplified_if_return 84 | - single_quote 85 | - standardize_not_equals 86 | - ternary_to_null_coalescing 87 | - trailing_comma_in_multiline_array 88 | - unalign_double_arrow 89 | - unalign_equals 90 | -------------------------------------------------------------------------------- /src/Button.php: -------------------------------------------------------------------------------- 1 | label('Action') 17 | * ->options(['class' => 'btn-lg']); 18 | * ``` 19 | */ 20 | class Button extends Widget 21 | { 22 | private string $tagName = 'button'; 23 | private string $label = 'Button'; 24 | private bool $encodeLabels = true; 25 | private array $options = []; 26 | 27 | protected function run(): string 28 | { 29 | if (!isset($this->options['id'])) { 30 | $this->options['id'] = "{$this->getId()}-button"; 31 | } 32 | 33 | Html::addCssClass($this->options, ['widget' => 'btn']); 34 | 35 | $this->registerPlugin('button', $this->options); 36 | 37 | return Html::tag( 38 | $this->tagName, 39 | $this->encodeLabels ? Html::encode($this->label) : $this->label, 40 | $this->options 41 | ); 42 | } 43 | 44 | /** 45 | * Whether the label should be HTML-encoded. 46 | * 47 | * @param bool $value 48 | * 49 | * @return $this 50 | */ 51 | public function encodeLabels(bool $value): self 52 | { 53 | $this->encodeLabels = $value; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * The button label 60 | * 61 | * @param string $value 62 | * 63 | * @return $this 64 | */ 65 | public function label(string $value): self 66 | { 67 | $this->label = $value; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * The HTML attributes for the widget container tag. The following special options are recognized. 74 | * 75 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 76 | * 77 | * @param array $value 78 | * 79 | * @return $this 80 | */ 81 | public function options(array $value): self 82 | { 83 | $this->options = $value; 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * The tag to use to render the button. 90 | * 91 | * @param string $value 92 | * 93 | * @return $this 94 | */ 95 | public function tagName(string $value): self 96 | { 97 | $this->tagName = $value; 98 | 99 | return $this; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/ButtonToolbar.php: -------------------------------------------------------------------------------- 1 | buttonGroups([ 23 | * [ 24 | * 'buttons' => [ 25 | * ['label' => '1', 'class' => ['btn-secondary']], 26 | * ['label' => '2', 'class' => ['btn-secondary']], 27 | * ['label' => '3', 'class' => ['btn-secondary']], 28 | * ['label' => '4', 'class' => ['btn-secondary']] 29 | * ], 30 | * 'class' => ['mr-2'] 31 | * ], 32 | * [ 33 | * 'buttons' => [ 34 | * ['label' => '5', 'class' => ['btn-secondary']], 35 | * ['label' => '6', 'class' => ['btn-secondary']], 36 | * ['label' => '7', 'class' => ['btn-secondary']] 37 | * ], 38 | * 'class' => ['mr-2'] 39 | * ], 40 | * [ 41 | * 'buttons' => [ 42 | * ['label' => '8', 'class' => ['btn-secondary']] 43 | * ] 44 | * ] 45 | * ]); 46 | * ``` 47 | * 48 | * Pressing on the button should be handled via JavaScript. See the following for details: 49 | */ 50 | class ButtonToolbar extends Widget 51 | { 52 | private array $buttonGroups = []; 53 | private array $options = []; 54 | 55 | protected function run(): string 56 | { 57 | if (!isset($this->options['id'])) { 58 | $this->options['id'] = "{$this->getId()}-button-toolbar"; 59 | } 60 | 61 | Html::addCssClass($this->options, ['widget' => 'btn-toolbar']); 62 | 63 | if (!isset($this->options['role'])) { 64 | $this->options['role'] = 'toolbar'; 65 | } 66 | 67 | return Html::tag('div', $this->renderButtonGroups(), $this->options); 68 | } 69 | 70 | /** 71 | * Generates the button groups that compound the toolbar as specified on {@see buttonGroups}. 72 | * 73 | * @throws InvalidConfigException 74 | * 75 | * @return string the rendering result. 76 | */ 77 | protected function renderButtonGroups(): string 78 | { 79 | $buttonGroups = []; 80 | 81 | foreach ($this->buttonGroups as $group) { 82 | if (is_array($group)) { 83 | if (!isset($group['buttons'])) { 84 | continue; 85 | } 86 | 87 | $buttonGroups[] = ButtonGroup::widget() 88 | ->buttons($group['buttons']) 89 | ->options($group['options']) 90 | ->render(); 91 | } else { 92 | $buttonGroups[] = $group; 93 | } 94 | } 95 | 96 | return implode("\n", $buttonGroups); 97 | } 98 | 99 | /** 100 | * List of buttons groups. Each array element represents a single group which can be specified as a string or an 101 | * array of the following structure: 102 | * 103 | * - buttons: array list of buttons. Either as array or string representation 104 | * - options: array optional, the HTML attributes of the button group. 105 | * - encodeLabels: bool whether to HTML-encode the button labels. 106 | * 107 | * @param array $value 108 | * 109 | * @return $this 110 | */ 111 | public function buttonGroups(array $value): self 112 | { 113 | $this->buttonGroups = $value; 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * The HTML attributes for the container tag. The following special options are recognized. 120 | * 121 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 122 | * 123 | * @param array $value 124 | * 125 | * @return $this 126 | */ 127 | public function options(array $value): self 128 | { 129 | $this->options = $value; 130 | 131 | return $this; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/ButtonGroup.php: -------------------------------------------------------------------------------- 1 | buttons([ 23 | * ['label' => 'A'], 24 | * ['label' => 'B'], 25 | * ['label' => 'C', 'visible' => false], 26 | * ]); 27 | * 28 | * // button group with an item as a string 29 | * echo ButtonGroup::widget() 30 | * ->buttons([ 31 | * Button::widget() 32 | * ->label('A'), 33 | * ['label' => 'B'], 34 | * ]); 35 | * ``` 36 | * 37 | * Pressing on the button should be handled via JavaScript. See the following for details: 38 | */ 39 | class ButtonGroup extends Widget 40 | { 41 | private array $buttons = []; 42 | private bool $encodeLabels = true; 43 | private array $options = []; 44 | 45 | protected function run(): string 46 | { 47 | if (!isset($this->options['id'])) { 48 | $this->options['id'] = "{$this->getId()}-button-group"; 49 | } 50 | 51 | Html::addCssClass($this->options, ['widget' => 'btn-group']); 52 | 53 | if (!isset($this->options['role'])) { 54 | $this->options['role'] = 'group'; 55 | } 56 | 57 | return Html::tag('div', $this->renderButtons(), $this->options); 58 | } 59 | 60 | /** 61 | * Generates the buttons that compound the group as specified on {@see buttons}. 62 | * 63 | * @throws InvalidConfigException 64 | * 65 | * @return string the rendering result. 66 | */ 67 | protected function renderButtons(): string 68 | { 69 | $buttons = []; 70 | 71 | foreach ($this->buttons as $button) { 72 | if (is_array($button)) { 73 | $visible = ArrayHelper::remove($button, 'visible', true); 74 | 75 | if ($visible === false) { 76 | continue; 77 | } 78 | 79 | if (!isset($button['encodeLabel'])) { 80 | $button['encodeLabel'] = $this->encodeLabels; 81 | } 82 | 83 | if (!isset($button['options']['type'])) { 84 | ArrayHelper::setValueByPath($button, 'options.type', 'button'); 85 | } 86 | 87 | $buttons[] = Button::widget() 88 | ->encodeLabels($button['encodeLabel']) 89 | ->label($button['label']) 90 | ->options($button['options']) 91 | ->render(); 92 | } else { 93 | $buttons[] = $button; 94 | } 95 | } 96 | 97 | return implode("\n", $buttons); 98 | } 99 | 100 | /** 101 | * List of buttons. Each array element represents a single button which can be specified as a string or an array of 102 | * the following structure: 103 | * 104 | * - label: string, required, the button label. 105 | * - options: array, optional, the HTML attributes of the button. 106 | * - visible: bool, optional, whether this button is visible. Defaults to true. 107 | * 108 | * @param array $value 109 | * 110 | * @return $this 111 | */ 112 | public function buttons(array $value): self 113 | { 114 | $this->buttons = $value; 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * Whether to HTML-encode the button labels. 121 | * 122 | * @param bool $value 123 | * 124 | * @return $this 125 | */ 126 | public function encodeLabels(bool $value): self 127 | { 128 | $this->encodeLabels = $value; 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * The HTML attributes for the widget container tag. The following special options are recognized. 135 | * 136 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 137 | * 138 | * @param array $value 139 | * 140 | * @return $this 141 | */ 142 | public function options(array $value): self 143 | { 144 | $this->options = $value; 145 | 146 | return $this; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 | 8 |

Yii Framework Twitter Bootstrap 4 Extension

9 |
10 |

11 | 12 | > ⚠️ Please do not start new projects with Bootstrap 4. There is [Bootstrap 5 package available](https://github.com/yiisoft/yii-bootstrap5). 13 | 14 | This [Yii Framework] extension encapsulates [Twitter Bootstrap 4] components 15 | and plugins in terms of Yii widgets, and thus makes using Bootstrap components/plugins 16 | in Yii applications extremely easy. 17 | 18 | [Yii Framework]: http://www.yiiframework.com/ 19 | [Twitter Bootstrap 4]: https://getbootstrap.com/docs/4.1/getting-started/introduction/ 20 | 21 | Documentation is at [docs/guide/README.md](docs/guide/README.md). 22 | 23 | [![Latest Stable Version](https://poser.pugx.org/yiisoft/yii-bootstrap4/v/stable.png)](https://packagist.org/packages/yiisoft/yii-bootstrap4) 24 | [![Total Downloads](https://poser.pugx.org/yiisoft/yii-bootstrap4/downloads.png)](https://packagist.org/packages/yiisoft/yii-bootstrap4) 25 | [![Build status](https://github.com/yiisoft/yii-bootstrap4/workflows/build/badge.svg)](https://github.com/yiisoft/yii-bootstrap4/actions?query=workflow%3Abuild) 26 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/yiisoft/yii-bootstrap4/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/yiisoft/yii-bootstrap4/?branch=master) 27 | [![Code Coverage](https://scrutinizer-ci.com/g/yiisoft/yii-bootstrap4/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/yiisoft/yii-bootstrap4/?branch=master) 28 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Fyii-bootstrap4%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/yii-bootstrap4/master) 29 | [![static analysis](https://github.com/yiisoft/yii-bootstrap4/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/yii-bootstrap4/actions?query=workflow%3A%22static+analysis%22) 30 | [![type-coverage](https://shepherd.dev/github/yiisoft/yii-bootstrap4/coverage.svg)](https://shepherd.dev/github/yiisoft/yii-bootstrap4) 31 | 32 | ### Installation 33 | 34 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 35 | 36 | ``` 37 | composer require yiisoft/yii-bootstrap4 38 | ``` 39 | 40 | ### Usage 41 | 42 | For example, the following 43 | single line of code in a view file would render a Bootstrap Progress plugin: 44 | 45 | ```php 46 | percent('60')->label(test); ?> 47 | ``` 48 | Read [Documentation](docs/guide/README.md) for more information 49 | 50 | ### Unit testing 51 | 52 | The package is tested with [PHPUnit](https://phpunit.de/). To run tests: 53 | 54 | ```shell 55 | ./vendor/bin/phpunit 56 | ``` 57 | 58 | ### Mutation testing 59 | 60 | The package tests are checked with [Infection](https://infection.github.io/) mutation framework. To run it: 61 | 62 | ```shell 63 | ./vendor/bin/infection 64 | ``` 65 | 66 | ### Static analysis 67 | 68 | The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis: 69 | 70 | ```shell 71 | ./vendor/bin/psalm 72 | ``` 73 | 74 | ### Support the project 75 | 76 | [![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft) 77 | 78 | ### Follow updates 79 | 80 | [![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/) 81 | [![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework) 82 | [![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en) 83 | [![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk) 84 | [![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack) 85 | 86 | ## License 87 | 88 | The Yii Framework Twitter Bootstrap 4 Extension is free software. It is released under the terms of the BSD License. 89 | Please see [`LICENSE`](./LICENSE.md) for more information. 90 | 91 | Maintained by [Yii Software](https://www.yiiframework.com/). 92 | -------------------------------------------------------------------------------- /src/BootstrapWidgetTrait.php: -------------------------------------------------------------------------------- 1 | assetManager !== null) { 49 | $this->assetManager->register([ 50 | BootstrapAsset::class, 51 | ]); 52 | } 53 | 54 | if ($this->enableClientOptions !== false) { 55 | $optionsString = Json::htmlEncode($this->clientOptions); 56 | $js = "jQuery('#$id').$name($optionsString);"; 57 | 58 | if ($this->webView !== null) { 59 | $this->webView->registerJs($js); 60 | } 61 | } 62 | 63 | $this->registerClientEvents($id); 64 | } 65 | 66 | /** 67 | * Registers JS event handlers that are listed in {@see clientEvents}. 68 | * 69 | * @param string $id 70 | */ 71 | protected function registerClientEvents(string $id): void 72 | { 73 | if ($this->clientEvents) { 74 | $js = []; 75 | 76 | foreach ($this->clientEvents as $event => $handler) { 77 | $js[] = "jQuery('#$id').on('$event', $handler);"; 78 | } 79 | 80 | if ($this->webView !== null) { 81 | $this->webView->registerJs(implode("\n", $js)); 82 | } 83 | } 84 | } 85 | 86 | public function assetManager(AssetManager $value): self 87 | { 88 | $this->assetManager = $value; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * The event handlers for the underlying Bootstrap JS plugin. 95 | * 96 | * Please refer to the corresponding Bootstrap plugin Web page for possible events. 97 | * 98 | * For example, [this page](http://getbootstrap.com/javascript/#modals) shows how to use the "Modal" plugin and the 99 | * supported events (e.g. "shown"). 100 | * 101 | * @param array $value 102 | * 103 | * @return $this 104 | */ 105 | public function clientEvents(array $value): self 106 | { 107 | $this->clientEvents = $value; 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * The options for the underlying Bootstrap JS plugin. 114 | * 115 | * Please refer to the corresponding Bootstrap plugin Web page for possible options. 116 | * 117 | * For example, [this page](http://getbootstrap.com/javascript/#modals) shows how to use the "Modal" plugin and the 118 | * supported options (e.g. "remote"). 119 | * 120 | * @param array $value 121 | * 122 | * @return $this 123 | */ 124 | public function clientOptions(array $value): self 125 | { 126 | $this->clientOptions = $value; 127 | 128 | return $this; 129 | } 130 | 131 | public function getClientOptions(): array 132 | { 133 | return $this->clientOptions; 134 | } 135 | 136 | /** 137 | * Enable/Disable script Bootstrap JS plugin. 138 | * 139 | * @param bool $value 140 | * 141 | * @return $this 142 | */ 143 | public function enableClientOptions(bool $value): self 144 | { 145 | $this->enableClientOptions = $value; 146 | 147 | return $this; 148 | } 149 | 150 | public function getEnableClientOptions(): bool 151 | { 152 | return $this->enableClientOptions; 153 | } 154 | 155 | public function webView(object $value): self 156 | { 157 | $this->webView = $value; 158 | 159 | return $this; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Alert.php: -------------------------------------------------------------------------------- 1 | options([ 19 | * 'class' => 'alert-info', 20 | * ]) 21 | * ->body('Say hello...'); 22 | * ``` 23 | */ 24 | class Alert extends Widget 25 | { 26 | private ?string $body = null; 27 | private array $closeButton = []; 28 | private bool $closeButtonEnabled = true; 29 | private array $options = []; 30 | 31 | protected function run(): string 32 | { 33 | if (!isset($this->options['id'])) { 34 | $this->options['id'] = "{$this->getId()}-alert"; 35 | } 36 | 37 | $this->initOptions(); 38 | 39 | $this->registerPlugin('alert', $this->options); 40 | 41 | return Html::beginTag('div', $this->options) . "\n" 42 | . "\n" . $this->renderBodyEnd() 43 | . "\n" . Html::endTag('div'); 44 | } 45 | 46 | /** 47 | * Renders the alert body and the close button (if any). 48 | * 49 | * @throws JsonException 50 | * 51 | * @return string the rendering result 52 | */ 53 | protected function renderBodyEnd(): string 54 | { 55 | return $this->body . "\n" . $this->renderCloseButton() . "\n"; 56 | } 57 | 58 | /** 59 | * Renders the close button. 60 | * 61 | * @throws JsonException 62 | * 63 | * @return string the rendering result. 64 | */ 65 | protected function renderCloseButton(): ?string 66 | { 67 | if ($this->closeButtonEnabled === false) { 68 | return null; 69 | } 70 | 71 | $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); 72 | $label = ArrayHelper::remove($this->closeButton, 'label', Html::tag('span', '×', [ 73 | 'aria-hidden' => 'true', 74 | ])); 75 | 76 | if ($tag === 'button' && !isset($this->closeButton['type'])) { 77 | $this->closeButton['type'] = 'button'; 78 | } 79 | 80 | return Html::tag($tag, $label, $this->closeButton); 81 | } 82 | 83 | /** 84 | * Initializes the widget options. 85 | * 86 | * This method sets the default values for various options. 87 | */ 88 | protected function initOptions(): void 89 | { 90 | Html::addCssClass($this->options, ['widget' => 'alert']); 91 | 92 | if ($this->closeButtonEnabled !== false) { 93 | $this->closeButton = [ 94 | 'data-dismiss' => 'alert', 95 | 'class' => ['widget' => 'close'], 96 | ]; 97 | 98 | Html::addCssClass($this->options, ['alert-dismissible']); 99 | } 100 | 101 | if (!isset($this->options['role'])) { 102 | $this->options['role'] = 'alert'; 103 | } 104 | } 105 | 106 | /** 107 | * The body content in the alert component. Alert widget will also be treated as the body content, and will be 108 | * rendered before this. 109 | * 110 | * @param string|null $value 111 | * 112 | * @return $this 113 | */ 114 | public function body(?string $value): self 115 | { 116 | $this->body = $value; 117 | 118 | return $this; 119 | } 120 | 121 | /** 122 | * The options for rendering the close button tag. 123 | * 124 | * The close button is displayed in the header of the modal window. Clicking on the button will hide the modal 125 | * window. If {@see closeButtonEnabled} is false, no close button will be rendered. 126 | * 127 | * The following special options are supported: 128 | * 129 | * - tag: string, the tag name of the button. Defaults to 'button'. 130 | * - label: string, the label of the button. Defaults to '×'. 131 | * 132 | * The rest of the options will be rendered as the HTML attributes of the button tag. 133 | * 134 | * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts) for the supported HTML 135 | * attributes. 136 | * 137 | * @param array $value 138 | * 139 | * @return $this 140 | */ 141 | public function closeButton(array $value): self 142 | { 143 | $this->closeButton = $value; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * Enable/Disable close button. 150 | * 151 | * @param bool $value 152 | * 153 | * @return $this 154 | */ 155 | public function closeButtonEnabled(bool $value): self 156 | { 157 | $this->closeButtonEnabled = $value; 158 | 159 | return $this; 160 | } 161 | 162 | /** 163 | * The HTML attributes for the widget container tag. The following special options are recognized. 164 | * 165 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 166 | * 167 | * @param array $value 168 | * 169 | * @return $this 170 | */ 171 | public function options(array $value): self 172 | { 173 | $this->options = $value; 174 | 175 | return $this; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Progress.php: -------------------------------------------------------------------------------- 1 | percent('60') 26 | * ->label(test); 27 | * 28 | * // styled 29 | * echo Progress::widget() 30 | * ->bars([ 31 | * ['percent' => '65', 'options' => ['class' => 'bg-danger']] 32 | * ]); 33 | * 34 | * // striped 35 | * echo Progress::widget() 36 | * ->bars([ 37 | * ['percent' => '70', 'options' => ['class' => 'bg-warning progress-bar-striped']] 38 | * ]); 39 | * 40 | * // striped animated 41 | * echo Progress::widget() 42 | * ->percent('70') 43 | * ->options'(['class' => 'bg-success progress-bar-animated progress-bar-striped']); 44 | * 45 | * // stacked bars 46 | * echo Progress::widget() 47 | * ->bars([ 48 | * ['percent' => '30', 'options' => ['class' => 'bg-danger']], 49 | * ['percent' => '30', 'label' => 'test', 'options' => ['class' => 'bg-success']], 50 | * ['percent' => '35', 'options' => ['class' => 'bg-warning']], 51 | * ]); 52 | * ``` 53 | */ 54 | class Progress extends Widget 55 | { 56 | private ?string $label = null; 57 | private ?string $percent = null; 58 | private array $bars = []; 59 | private array $options = []; 60 | private array $barOptions = []; 61 | 62 | protected function run(): string 63 | { 64 | if (!isset($this->options['id'])) { 65 | $this->options['id'] = "{$this->getId()}-progress"; 66 | } 67 | 68 | Html::addCssClass($this->options, ['widget' => 'progress']); 69 | 70 | return $this->renderProgress(); 71 | } 72 | 73 | /** 74 | * Renders the progress. 75 | * 76 | * @throws JsonException|RuntimeException if the "percent" option is not set in a stacked progress bar. 77 | * 78 | * @return string the rendering result. 79 | */ 80 | protected function renderProgress(): string 81 | { 82 | $out = Html::beginTag('div', $this->options) . "\n"; 83 | 84 | if (empty($this->bars)) { 85 | $this->bars = [ 86 | ['label' => $this->label, 'percent' => $this->percent, 'options' => $this->barOptions], 87 | ]; 88 | } 89 | 90 | $bars = []; 91 | 92 | foreach ($this->bars as $bar) { 93 | $label = ArrayHelper::getValue($bar, 'label', ''); 94 | if (!isset($bar['percent'])) { 95 | throw new RuntimeException('The "percent" option is required.'); 96 | } 97 | $options = ArrayHelper::getValue($bar, 'options', []); 98 | $bars[] = $this->renderBar($bar['percent'], $label, $options); 99 | } 100 | 101 | $out .= implode("\n", $bars) . "\n"; 102 | $out .= Html::endTag('div'); 103 | 104 | return $out; 105 | } 106 | 107 | /** 108 | * Generates a bar. 109 | * 110 | * @param string $percent the percentage of the bar 111 | * @param string $label , optional, the label to display at the bar 112 | * @param array $options the HTML attributes of the bar 113 | * 114 | * @throws JsonException 115 | * 116 | * @return string the rendering result. 117 | */ 118 | protected function renderBar(string $percent, string $label = '', array $options = []): string 119 | { 120 | $valuePercent = (float)trim(rtrim($percent, '%')); 121 | 122 | $options = array_merge($options, [ 123 | 'role' => 'progressbar', 124 | 'aria-valuenow' => $percent, 125 | 'aria-valuemin' => 0, 126 | 'aria-valuemax' => 100, 127 | ]); 128 | 129 | Html::addCssClass($options, ['widget' => 'progress-bar']); 130 | Html::addCssStyle($options, ['width' => $valuePercent . '%'], true); 131 | 132 | return Html::tag('div', $label, $options); 133 | } 134 | 135 | /** 136 | * Set of bars that are stacked together to form a single progress bar. 137 | * 138 | * Each bar is an array of the following structure: 139 | * 140 | * ```php 141 | * [ 142 | * // required, the amount of progress as a percentage. 143 | * 'percent' => '30', 144 | * // optional, the label to be displayed on the bar 145 | * 'label' => '30%', 146 | * // optional, array, additional HTML attributes for the bar tag 147 | * 'options' => [], 148 | * ] 149 | * ``` 150 | * 151 | * @param array $value 152 | * 153 | * @return $this 154 | */ 155 | public function bars(array $value): self 156 | { 157 | $this->bars = $value; 158 | 159 | return $this; 160 | } 161 | 162 | /** 163 | * The HTML attributes of the bar. This property will only be considered if {@see bars} is empty. 164 | * 165 | * {@see \Yiisoft\Html\Html::renderTagAttributes() for details on how attributes are being rendered} 166 | * 167 | * @param array $value 168 | * 169 | * @return $this 170 | */ 171 | public function barOptions(array $value): self 172 | { 173 | $this->barOptions = $value; 174 | 175 | return $this; 176 | } 177 | 178 | /** 179 | * The button label. 180 | * 181 | * @param string|null $value 182 | * 183 | * @return $this 184 | */ 185 | public function label(?string $value): self 186 | { 187 | $this->label = $value; 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * The HTML attributes for the widget container tag. The following special options are recognized. 194 | * 195 | * @param array $value 196 | * 197 | * @return $this 198 | * 199 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 200 | */ 201 | public function options(array $value): self 202 | { 203 | $this->options = $value; 204 | 205 | return $this; 206 | } 207 | 208 | /** 209 | * The amount of progress as a percentage. 210 | * 211 | * @param string|null $value 212 | * 213 | * @return $this 214 | */ 215 | public function percent(?string $value): self 216 | { 217 | $this->percent = $value; 218 | 219 | return $this; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Breadcrumbs.php: -------------------------------------------------------------------------------- 1 | links(['label' => !empty($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : []]); 25 | * ``` 26 | */ 27 | class Breadcrumbs extends Widget 28 | { 29 | private string $tag = 'ol'; 30 | private bool $encodeLabels = true; 31 | private array $homeLink = []; 32 | private array $links = []; 33 | private string $itemTemplate = "
  • {link}
  • \n"; 34 | private string $activeItemTemplate = "
  • {link}
  • \n"; 35 | private array $navOptions = ['aria-label' => 'breadcrumb']; 36 | private array $options = []; 37 | 38 | protected function run(): string 39 | { 40 | if (!isset($this->options['id'])) { 41 | $this->options['id'] = "{$this->getId()}-breadcrumb"; 42 | } 43 | 44 | Html::addCssClass($this->options, ['widget' => 'breadcrumb']); 45 | 46 | $this->registerPlugin('breadcrumb', $this->options); 47 | 48 | if (empty($this->links)) { 49 | return ''; 50 | } 51 | 52 | $links = []; 53 | 54 | if ($this->homeLink === []) { 55 | $links[] = $this->renderItem([ 56 | 'label' => 'Home', 57 | 'url' => '/', 58 | ], $this->itemTemplate); 59 | } else { 60 | $links[] = $this->renderItem($this->homeLink, $this->itemTemplate); 61 | } 62 | 63 | foreach ($this->links as $link) { 64 | if (!is_array($link)) { 65 | $link = ['label' => $link]; 66 | } 67 | 68 | $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate); 69 | } 70 | 71 | return Html::tag('nav', Html::tag($this->tag, implode('', $links), $this->options), $this->navOptions); 72 | } 73 | 74 | /** 75 | * Renders a single breadcrumb item. 76 | * 77 | * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional. 78 | * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the 79 | * link. 80 | * 81 | * @throws JsonException|RuntimeException if `$link` does not have "label" element. 82 | * 83 | * @return string the rendering result 84 | */ 85 | protected function renderItem(array $link, string $template): string 86 | { 87 | $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels); 88 | 89 | if (array_key_exists('label', $link)) { 90 | $label = $encodeLabel ? Html::encode($link['label']) : $link['label']; 91 | } else { 92 | throw new RuntimeException('The "label" element is required for each link.'); 93 | } 94 | 95 | if (isset($link['template'])) { 96 | $template = $link['template']; 97 | } 98 | 99 | if (isset($link['url'])) { 100 | $options = $link; 101 | unset($options['template'], $options['label'], $options['url']); 102 | $linkHtml = Html::a($label, $link['url'], $options); 103 | } else { 104 | $linkHtml = $label; 105 | } 106 | 107 | return strtr($template, ['{link}' => $linkHtml]); 108 | } 109 | 110 | /** 111 | * The template used to render each active item in the breadcrumbs. The token `{link}` will be replaced with the 112 | * actual HTML link for each active item. 113 | * 114 | * @param string $value 115 | * 116 | * @return $this 117 | */ 118 | public function activeItemTemplate(string $value): self 119 | { 120 | $this->activeItemTemplate = $value; 121 | 122 | return $this; 123 | } 124 | 125 | /** 126 | * Whether to HTML-encode the link labels. 127 | * 128 | * @param bool $value 129 | * 130 | * @return $this 131 | */ 132 | public function encodeLabels(bool $value): self 133 | { 134 | $this->encodeLabels = $value; 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * The first hyperlink in the breadcrumbs (called home link). 141 | * 142 | * Please refer to {@see links} on the format of the link. 143 | * 144 | * If this property is not set, it will default to a link pointing with the label 'Home'. If this property is false, 145 | * the home link will not be rendered. 146 | * 147 | * @param array $value 148 | * 149 | * @return $this 150 | */ 151 | public function homeLink(array $value): self 152 | { 153 | $this->homeLink = $value; 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * The template used to render each inactive item in the breadcrumbs. The token `{link}` will be replaced with the 160 | * actual HTML link for each inactive item. 161 | * 162 | * @param string $value 163 | * 164 | * @return $this 165 | */ 166 | public function itemTemplate(string $value): self 167 | { 168 | $this->itemTemplate = $value; 169 | 170 | return $this; 171 | } 172 | 173 | /** 174 | * List of links to appear in the breadcrumbs. If this property is empty, the widget will not render anything. Each 175 | * array element represents a single link in the breadcrumbs with the following structure: 176 | * 177 | * ```php 178 | * [ 179 | * 'label' => 'label of the link', // required 180 | * 'url' => 'url of the link', // optional, will be processed by Url::to() 181 | * 'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used 182 | * ] 183 | * ``` 184 | * 185 | * @param array $value 186 | * 187 | * @return $this 188 | */ 189 | public function links(array $value): self 190 | { 191 | $this->links = $value; 192 | 193 | return $this; 194 | } 195 | 196 | /** 197 | * The HTML attributes for the widgets nav container tag. 198 | * 199 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 200 | * 201 | * @param array $value 202 | * 203 | * @return $this 204 | */ 205 | public function navOptions(array $value): self 206 | { 207 | $this->navOptions = $value; 208 | 209 | return $this; 210 | } 211 | 212 | /** 213 | * The HTML attributes for the widget container tag. The following special options are recognized. 214 | * 215 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 216 | * 217 | * @param array $value 218 | * 219 | * @return $this 220 | */ 221 | public function options(array $value): self 222 | { 223 | $this->options = $value; 224 | 225 | return $this; 226 | } 227 | 228 | /** 229 | * The name of the breadcrumb container tag. 230 | * 231 | * @param string $value 232 | * 233 | * @return $this 234 | */ 235 | public function tag(string $value): self 236 | { 237 | $this->tag = $value; 238 | 239 | return $this; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/Dropdown.php: -------------------------------------------------------------------------------- 1 | 24 | * items([ 27 | * ['label' => 'DropdownA', 'url' => '/'], 28 | * ['label' => 'DropdownB', 'url' => '#'], 29 | * ]); 30 | * ?> 31 | * 32 | * ``` 33 | */ 34 | class Dropdown extends Widget 35 | { 36 | private array $items = []; 37 | private bool $encodeLabels = true; 38 | private array $submenuOptions = []; 39 | private array $options = []; 40 | 41 | protected function run(): string 42 | { 43 | if (!isset($this->options['id'])) { 44 | $this->options['id'] = "{$this->getId()}-dropdown"; 45 | } 46 | 47 | Html::addCssClass($this->options, ['widget' => 'dropdown-menu']); 48 | 49 | $this->registerClientEvents($this->options['id']); 50 | 51 | return $this->renderItems($this->items, $this->options); 52 | } 53 | 54 | /** 55 | * Renders menu items. 56 | * 57 | * @param array $items the menu items to be rendered 58 | * @param array $options the container HTML attributes 59 | * 60 | * @throws JsonException|RuntimeException if the label option is not specified in one of the items. 61 | * 62 | * @return string the rendering result. 63 | */ 64 | protected function renderItems(array $items, array $options = []): string 65 | { 66 | $lines = []; 67 | 68 | foreach ($items as $item) { 69 | if (is_string($item)) { 70 | $item = ['label' => $item, 'encode' => false, 'enclose' => false]; 71 | } 72 | 73 | if (isset($item['visible']) && !$item['visible']) { 74 | continue; 75 | } 76 | 77 | if (!array_key_exists('label', $item)) { 78 | throw new RuntimeException('The "label" option is required.'); 79 | } 80 | 81 | $encodeLabel = $item['encode'] ?? $this->encodeLabels; 82 | $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; 83 | $itemOptions = ArrayHelper::getValue($item, 'options', []); 84 | $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); 85 | $active = ArrayHelper::getValue($item, 'active', false); 86 | $disabled = ArrayHelper::getValue($item, 'disabled', false); 87 | $enclose = ArrayHelper::getValue($item, 'enclose', true); 88 | 89 | Html::addCssClass($linkOptions, 'dropdown-item'); 90 | 91 | if ($disabled) { 92 | ArrayHelper::setValue($linkOptions, 'tabindex', '-1'); 93 | ArrayHelper::setValue($linkOptions, 'aria-disabled', 'true'); 94 | Html::addCssClass($linkOptions, 'disabled'); 95 | } elseif ($active) { 96 | Html::addCssClass($linkOptions, 'active'); 97 | } 98 | 99 | $url = $item['url'] ?? null; 100 | 101 | if (empty($item['items'])) { 102 | if ($label === '-') { 103 | $content = Html::div('', ['class' => 'dropdown-divider']); 104 | } elseif ($enclose === false) { 105 | $content = $label; 106 | } elseif ($url === null) { 107 | $content = Html::tag('h6', $label, ['class' => 'dropdown-header']); 108 | } else { 109 | $content = Html::a($label, $url, $linkOptions); 110 | } 111 | 112 | $lines[] = $content; 113 | } else { 114 | $submenuOptions = $this->submenuOptions; 115 | 116 | if (isset($item['submenuOptions'])) { 117 | $submenuOptions = array_merge($submenuOptions, $item['submenuOptions']); 118 | } 119 | 120 | Html::addCssClass($submenuOptions, ['dropdown-submenu']); 121 | Html::addCssClass($linkOptions, ['dropdown-toggle']); 122 | 123 | $lines[] = Html::beginTag( 124 | 'div', 125 | array_merge_recursive(['class' => ['dropdown'], 'aria-expanded' => 'false'], $itemOptions) 126 | ); 127 | 128 | $lines[] = Html::a($label, $url, array_merge([ 129 | 'data-toggle' => 'dropdown', 130 | 'aria-haspopup' => 'true', 131 | 'aria-expanded' => 'false', 132 | 'role' => 'button', 133 | ], $linkOptions)); 134 | 135 | $lines[] = self::widget() 136 | ->items($item['items']) 137 | ->options($submenuOptions) 138 | ->submenuOptions($submenuOptions) 139 | ->encodeLabels($this->encodeLabels) 140 | ->run(); 141 | $lines[] = Html::endTag('div'); 142 | } 143 | } 144 | 145 | return Html::tag('div', implode("\n", $lines), $options); 146 | } 147 | 148 | /** 149 | * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a 150 | * single menu with the following structure: 151 | * 152 | * - label: string, required, the label of the item link. 153 | * - encode: bool, optional, whether to HTML-encode item label. 154 | * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}. 155 | * If not set, the item will be treated as a menu header when the item has no sub-menu. 156 | * - visible: bool, optional, whether this menu item is visible. Defaults to true. 157 | * - linkOptions: array, optional, the HTML attributes of the item link. 158 | * - options: array, optional, the HTML attributes of the item. 159 | * - items: array, optional, the submenu items. The structure is the same as this property. 160 | * Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it. 161 | * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be 162 | * merged with {@see submenuOptions}. 163 | * 164 | * To insert divider use `-`. 165 | * 166 | * @param array $value 167 | * 168 | * @return $this 169 | */ 170 | public function items(array $value): self 171 | { 172 | $this->items = $value; 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Whether the labels for header items should be HTML-encoded. 179 | * 180 | * @param bool $value 181 | * 182 | * @return $this 183 | */ 184 | public function encodeLabels(bool $value): self 185 | { 186 | $this->encodeLabels = $value; 187 | 188 | return $this; 189 | } 190 | 191 | /** 192 | * The HTML attributes for sub-menu container tags. 193 | * 194 | * @param array $value 195 | * 196 | * @return $this 197 | */ 198 | public function submenuOptions(array $value): self 199 | { 200 | $this->submenuOptions = $value; 201 | 202 | return $this; 203 | } 204 | 205 | /** 206 | * @param array $value the HTML attributes for the widget container tag. The following special options are 207 | * recognized. 208 | * 209 | * @return $this 210 | * 211 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 212 | */ 213 | public function options(array $value): self 214 | { 215 | $this->options = $value; 216 | 217 | return $this; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Carousel.php: -------------------------------------------------------------------------------- 1 | items([ 24 | * // the item contains only the image 25 | * '', 26 | * // equivalent to the above 27 | * ['content' => ''], 28 | * // the item contains both the image and the caption 29 | * [ 30 | * 'content' => '', 31 | * 'caption' => '

    This is title

    This is the caption text

    ', 32 | * 'captionOptions' => ['class' => ['d-none', 'd-md-block']], 33 | * 'options' => [...], 34 | * ], 35 | * ]); 36 | * ``` 37 | */ 38 | class Carousel extends Widget 39 | { 40 | private ?array $controls = [ 41 | 'Previous', 42 | 'Next', 43 | ]; 44 | private bool $showIndicators = true; 45 | private array $items = []; 46 | private bool $crossfade = false; 47 | private array $options = ['data-ride' => 'carousel']; 48 | 49 | protected function run(): string 50 | { 51 | if (!isset($this->options['id'])) { 52 | $this->options['id'] = "{$this->getId()}-carousel"; 53 | } 54 | 55 | Html::addCssClass($this->options, ['widget' => 'carousel', 'slide']); 56 | 57 | if ($this->crossfade) { 58 | Html::addCssClass($this->options, 'carousel-fade'); 59 | } 60 | 61 | $this->registerPlugin('carousel', $this->options); 62 | 63 | return implode("\n", [ 64 | Html::beginTag('div', $this->options), 65 | $this->renderIndicators(), 66 | $this->renderItems(), 67 | $this->renderControls(), 68 | Html::endTag('div'), 69 | ]) . "\n"; 70 | } 71 | 72 | /** 73 | * Renders carousel indicators. 74 | */ 75 | public function renderIndicators(): string 76 | { 77 | if ($this->showIndicators === false) { 78 | return ''; 79 | } 80 | 81 | $indicators = []; 82 | 83 | for ($i = 0, $count = count($this->items); $i < $count; $i++) { 84 | $options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i]; 85 | if ($i === 0) { 86 | Html::addCssClass($options, 'active'); 87 | } 88 | $indicators[] = Html::tag('li', '', $options); 89 | } 90 | 91 | return Html::tag('ol', implode("\n", $indicators), ['class' => ['carousel-indicators']]); 92 | } 93 | 94 | /** 95 | * Renders carousel items as specified on {@see items}. 96 | */ 97 | public function renderItems(): string 98 | { 99 | $items = []; 100 | 101 | foreach ($this->items as $i => $iValue) { 102 | $items[] = $this->renderItem($iValue, $i); 103 | } 104 | 105 | return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']); 106 | } 107 | 108 | /** 109 | * Renders a single carousel item 110 | * 111 | * @param array|string $item a single item from {@see items} 112 | * @param int $index the item index as the first item should be set to `active`. 113 | * 114 | * @throws JsonException|RuntimeException if the item is invalid. 115 | * 116 | * @return string the rendering result. 117 | */ 118 | public function renderItem($item, int $index): string 119 | { 120 | if (is_string($item)) { 121 | $content = $item; 122 | $caption = null; 123 | $options = []; 124 | } elseif (isset($item['content'])) { 125 | $content = $item['content']; 126 | $caption = ArrayHelper::getValue($item, 'caption'); 127 | 128 | if ($caption !== null) { 129 | $captionOptions = ArrayHelper::remove($item, 'captionOptions', []); 130 | Html::addCssClass($captionOptions, ['widget' => 'carousel-caption']); 131 | 132 | $caption = Html::tag('div', $caption, $captionOptions); 133 | } 134 | 135 | $options = ArrayHelper::getValue($item, 'options', []); 136 | } else { 137 | throw new RuntimeException('The "content" option is required.'); 138 | } 139 | 140 | Html::addCssClass($options, ['widget' => 'carousel-item']); 141 | 142 | if ($index === 0) { 143 | Html::addCssClass($options, 'active'); 144 | } 145 | 146 | return Html::tag('div', $content . "\n" . $caption, $options); 147 | } 148 | 149 | /** 150 | * Renders previous and next control buttons. 151 | * 152 | * @throws JsonException|RuntimeException if {@see controls} is invalid. 153 | */ 154 | public function renderControls(): ?string 155 | { 156 | if (isset($this->controls[0], $this->controls[1])) { 157 | return Html::a($this->controls[0], '#' . $this->options['id'], [ 158 | 'class' => 'carousel-control-prev', 159 | 'data-slide' => 'prev', 160 | 'role' => 'button', 161 | ]) . "\n" 162 | . Html::a($this->controls[1], '#' . $this->options['id'], [ 163 | 'class' => 'carousel-control-next', 164 | 'data-slide' => 'next', 165 | 'role' => 'button', 166 | ]); 167 | } 168 | 169 | if ($this->controls === null) { 170 | return ''; 171 | } 172 | 173 | throw new RuntimeException( 174 | 'The "controls" property must be either null or an array of two elements.' 175 | ); 176 | } 177 | 178 | /** 179 | * The labels for the previous and the next control buttons. 180 | * 181 | * If null, it means the previous and the next control buttons should not be displayed. 182 | * 183 | * @param array|null $value 184 | * 185 | * @return $this 186 | */ 187 | public function controls(?array $value): self 188 | { 189 | $this->controls = $value; 190 | 191 | return $this; 192 | } 193 | 194 | /** 195 | * Animate slides with a fade transition instead of a slide. Defaults to `false`. 196 | * 197 | * @param bool $value 198 | * 199 | * @return $this 200 | */ 201 | public function crossfade(bool $value): self 202 | { 203 | $this->crossfade = $value; 204 | 205 | return $this; 206 | } 207 | 208 | /** 209 | * List of slides in the carousel. Each array element represents a single slide with the following structure: 210 | * 211 | * ```php 212 | * [ 213 | * // required, slide content (HTML), such as an image tag 214 | * 'content' => '', 215 | * // optional, the caption (HTML) of the slide 216 | * 'caption' => '

    This is title

    This is the caption text

    ', 217 | * // optional the HTML attributes of the slide container 218 | * 'options' => [], 219 | * ] 220 | * ``` 221 | * 222 | * @param array $value 223 | * 224 | * @return $this 225 | */ 226 | public function items(array $value): self 227 | { 228 | $this->items = $value; 229 | 230 | return $this; 231 | } 232 | 233 | /** 234 | * The HTML attributes for the container tag. The following special options are recognized. 235 | * 236 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 237 | * 238 | * @param array $value 239 | * 240 | * @return $this 241 | */ 242 | public function options(array $value): self 243 | { 244 | $this->options = $value; 245 | 246 | return $this; 247 | } 248 | 249 | /** 250 | * Whether carousel indicators (
      tag with anchors to items) should be displayed or not. 251 | * 252 | * @param bool $value 253 | * 254 | * @return $this 255 | */ 256 | public function showIndicators(bool $value): self 257 | { 258 | $this->showIndicators = $value; 259 | 260 | return $this; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/ButtonDropdown.php: -------------------------------------------------------------------------------- 1 | label('Action') 20 | * ->dropdown'([ 21 | * 'items' => [ 22 | * ['label' => 'DropdownA', 'url' => '/'], 23 | * ['label' => 'DropdownB', 'url' => '#'], 24 | * ], 25 | * ]); 26 | * ``` 27 | */ 28 | class ButtonDropdown extends Widget 29 | { 30 | /** 31 | * The css class part of dropdown 32 | */ 33 | public const DIRECTION_DOWN = 'down'; 34 | 35 | /** 36 | * The css class part of dropleft 37 | */ 38 | public const DIRECTION_LEFT = 'left'; 39 | 40 | /** 41 | * The css class part of dropright 42 | */ 43 | public const DIRECTION_RIGHT = 'right'; 44 | 45 | /** 46 | * The css class part of dropup 47 | */ 48 | public const DIRECTION_UP = 'up'; 49 | 50 | private string $label = 'Button'; 51 | private array $options = []; 52 | private array $buttonOptions = []; 53 | private array $dropdown = []; 54 | private string $direction = self::DIRECTION_DOWN; 55 | private bool $split = false; 56 | private string $tagName = 'button'; 57 | private bool $encodeLabels = true; 58 | private string $dropdownClass = Dropdown::class; 59 | private bool $renderContainer = true; 60 | 61 | protected function run(): string 62 | { 63 | /** Set options id to button options id to ensure correct css selector in plugin initialisation */ 64 | if (empty($this->options['id'])) { 65 | $id = $this->getId(); 66 | 67 | $this->options['id'] = "{$id}-button-dropdown"; 68 | $this->buttonOptions['id'] = "{$id}-button"; 69 | } 70 | 71 | $html = $this->renderButton() . "\n" . $this->renderDropdown(); 72 | 73 | if ($this->renderContainer) { 74 | Html::addCssClass($this->options, ['widget' => 'drop' . $this->direction, 'btn-group']); 75 | 76 | $options = $this->options; 77 | $tag = ArrayHelper::remove($options, 'tag', 'div'); 78 | $html = Html::tag($tag, $html, $options); 79 | } 80 | 81 | $this->registerPlugin('dropdown', $this->options); 82 | 83 | return $html; 84 | } 85 | 86 | /** 87 | * Generates the button dropdown. 88 | * 89 | * @throws InvalidConfigException 90 | * 91 | * @return string the rendering result. 92 | */ 93 | protected function renderButton(): string 94 | { 95 | Html::addCssClass($this->buttonOptions, ['widget' => 'btn']); 96 | 97 | $label = $this->label; 98 | 99 | if ($this->encodeLabels) { 100 | $label = Html::encode($label); 101 | } 102 | 103 | if ($this->split) { 104 | $buttonOptions = $this->buttonOptions; 105 | 106 | $this->buttonOptions['data-toggle'] = 'dropdown'; 107 | $this->buttonOptions['aria-haspopup'] = 'true'; 108 | $this->buttonOptions['aria-expanded'] = 'false'; 109 | 110 | Html::addCssClass($this->buttonOptions, ['toggle' => 'dropdown-toggle dropdown-toggle-split']); 111 | 112 | unset($buttonOptions['id']); 113 | 114 | $splitButton = Button::widget() 115 | ->label('Toggle Dropdown') 116 | ->encodeLabels(false) 117 | ->options($this->buttonOptions) 118 | ->render(); 119 | } else { 120 | $buttonOptions = $this->buttonOptions; 121 | 122 | Html::addCssClass($buttonOptions, ['toggle' => 'dropdown-toggle']); 123 | 124 | $buttonOptions['data-toggle'] = 'dropdown'; 125 | $buttonOptions['aria-haspopup'] = 'true'; 126 | $buttonOptions['aria-expanded'] = 'false'; 127 | $splitButton = ''; 128 | } 129 | 130 | if (!isset($buttonOptions['href']) && ($this->tagName === 'a')) { 131 | $buttonOptions['href'] = '#'; 132 | $buttonOptions['role'] = 'button'; 133 | } 134 | 135 | return Button::widget() 136 | ->tagName($this->tagName) 137 | ->label($label) 138 | ->options($buttonOptions) 139 | ->encodeLabels(false) 140 | ->render() 141 | . "\n" . $splitButton; 142 | } 143 | 144 | /** 145 | * Generates the dropdown menu. 146 | * 147 | * @return string the rendering result. 148 | */ 149 | protected function renderDropdown(): string 150 | { 151 | $dropdownClass = $this->dropdownClass; 152 | 153 | return $dropdownClass::widget() 154 | ->items($this->dropdown['items']) 155 | ->render(); 156 | } 157 | 158 | /** 159 | * The HTML attributes of the button. 160 | * 161 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 162 | * 163 | * @param array $value 164 | * 165 | * @return $this 166 | */ 167 | public function buttonOptions(array $value): self 168 | { 169 | $this->buttonOptions = $value; 170 | 171 | return $this; 172 | } 173 | 174 | /** 175 | * The drop-direction of the widget. 176 | * 177 | * Possible values are 'left', 'right', 'up', or 'down' (default) 178 | * 179 | * @param string $value 180 | * 181 | * @return $this 182 | */ 183 | public function direction(string $value): self 184 | { 185 | $this->direction = $value; 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * The configuration array for example: 192 | * 193 | * ```php 194 | * [ 195 | * 'items' => [ 196 | * ['label' => 'DropdownA', 'url' => '/'], 197 | * ['label' => 'DropdownB', 'url' => '#'], 198 | * ], 199 | * ] 200 | * ``` 201 | * 202 | * {@see Dropdown} 203 | * 204 | * @param array $value 205 | * 206 | * @return $this 207 | */ 208 | public function dropdown(array $value): self 209 | { 210 | $this->dropdown = $value; 211 | 212 | return $this; 213 | } 214 | 215 | /** 216 | * Name of a class to use for rendering dropdowns withing this widget. Defaults to {@see Dropdown}. 217 | * 218 | * @param string $value 219 | * 220 | * @return $this 221 | */ 222 | public function dropdownClass(string $value): self 223 | { 224 | $this->dropdownClass = $value; 225 | 226 | return $this; 227 | } 228 | 229 | /** 230 | * Whether the label should be HTML-encoded. 231 | * 232 | * @param bool $value 233 | * 234 | * @return $this 235 | */ 236 | public function encodeLabels(bool $value): self 237 | { 238 | $this->encodeLabels = $value; 239 | 240 | return $this; 241 | } 242 | 243 | /** 244 | * The button label. 245 | * 246 | * @param string $value 247 | * 248 | * @return $this 249 | */ 250 | public function label(string $value): self 251 | { 252 | $this->label = $value; 253 | 254 | return $this; 255 | } 256 | 257 | /** 258 | * The HTML attributes for the container tag. The following special options are recognized. 259 | * 260 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 261 | * 262 | * @param array $value 263 | * 264 | * @return $this 265 | */ 266 | public function options(array $value): self 267 | { 268 | $this->options = $value; 269 | 270 | return $this; 271 | } 272 | 273 | /** 274 | * Whether to render the container using the {@see options} as HTML attributes. If set to `false`, the container 275 | * element enclosing the button and dropdown will NOT be rendered. 276 | * 277 | * @param bool $value 278 | * 279 | * @return $this 280 | */ 281 | public function renderContainer(bool $value): self 282 | { 283 | $this->renderContainer = $value; 284 | 285 | return $this; 286 | } 287 | 288 | /** 289 | * Whether to display a group of split-styled button group. 290 | * 291 | * @param bool $value 292 | * 293 | * @return $this 294 | */ 295 | public function split(bool $value): self 296 | { 297 | $this->split = $value; 298 | 299 | return $this; 300 | } 301 | 302 | /** 303 | * The tag to use to render the button. 304 | * 305 | * @param string $value 306 | * 307 | * @return $this 308 | */ 309 | public function tagName(string $value): self 310 | { 311 | $this->tagName = $value; 312 | 313 | return $this; 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/Accordion.php: -------------------------------------------------------------------------------- 1 | items([ 30 | * // equivalent to the above 31 | * [ 32 | * 'label' => 'Collapsible Group Item #1', 33 | * 'content' => 'Anim pariatur cliche...', 34 | * // open its content by default 35 | * 'contentOptions' => ['class' => 'in'], 36 | * ], 37 | * // another group item 38 | * [ 39 | * 'label' => 'Collapsible Group Item #1', 40 | * 'content' => 'Anim pariatur cliche...', 41 | * 'contentOptions' => [...], 42 | * 'options' => [...], 43 | * 'expand' => true, 44 | * ], 45 | * // if you want to swap out .card-block with .list-group, you may use the following 46 | * [ 47 | * 'label' => 'Collapsible Group Item #1', 48 | * 'content' => [ 49 | * 'Anim pariatur cliche...', 50 | * 'Anim pariatur cliche...', 51 | * ], 52 | * 'contentOptions' => [...], 53 | * 'options' => [...], 54 | * 'footer' => 'Footer' // the footer label in list-group, 55 | * ], 56 | * ]); 57 | * ``` 58 | */ 59 | class Accordion extends Widget 60 | { 61 | private array $items = []; 62 | private bool $encodeLabels = true; 63 | private bool $autoCloseItems = true; 64 | private array $itemToggleOptions = []; 65 | private array $options = []; 66 | 67 | protected function run(): string 68 | { 69 | if (!isset($this->options['id'])) { 70 | $this->options['id'] = "{$this->getId()}-accordion"; 71 | } 72 | 73 | $this->registerPlugin('collapse', $this->options); 74 | 75 | Html::addCssClass($this->options, 'accordion'); 76 | 77 | return implode("\n", [ 78 | Html::beginTag('div', $this->options), 79 | $this->renderItems(), 80 | Html::endTag('div'), 81 | ]) . "\n"; 82 | } 83 | 84 | /** 85 | * Renders collapsible items as specified on {@see items}. 86 | * 87 | * @throws RuntimeException|JsonException| 88 | * 89 | * @return string the rendering result. 90 | */ 91 | public function renderItems(): string 92 | { 93 | $items = []; 94 | $index = 0; 95 | $expanded = array_search(true, array_column($this->items, 'expand'), true); 96 | 97 | foreach ($this->items as $key => $item) { 98 | if (!is_array($item)) { 99 | $item = ['content' => $item]; 100 | } 101 | 102 | if ($expanded === false && $index === 0) { 103 | $item['expand'] = true; 104 | } 105 | 106 | if (!array_key_exists('label', $item)) { 107 | if (is_int($key)) { 108 | throw new RuntimeException('The "label" option is required.'); 109 | } 110 | 111 | $item['label'] = $key; 112 | } 113 | 114 | $header = ArrayHelper::remove($item, 'label'); 115 | $options = ArrayHelper::getValue($item, 'options', []); 116 | 117 | Html::addCssClass($options, ['panel' => 'card']); 118 | 119 | $items[] = Html::tag('div', $this->renderItem($header, $item, $index++), $options); 120 | } 121 | 122 | return implode("\n", $items); 123 | } 124 | 125 | /** 126 | * Renders a single collapsible item group. 127 | * 128 | * @param string $header a label of the item group {@see items} 129 | * @param array $item a single item from {@see items} 130 | * @param int $index the item index as each item group content must have an id. 131 | * 132 | * @throws JsonException|RuntimeException 133 | * 134 | * @return string the rendering result 135 | */ 136 | public function renderItem(string $header, array $item, int $index): string 137 | { 138 | if (array_key_exists('content', $item)) { 139 | $id = $this->options['id'] . '-collapse' . $index; 140 | $expand = ArrayHelper::remove($item, 'expand', false); 141 | $options = ArrayHelper::getValue($item, 'contentOptions', []); 142 | $options['id'] = $id; 143 | 144 | Html::addCssClass($options, ['widget' => 'collapse']); 145 | 146 | if ($expand) { 147 | Html::addCssClass($options, 'show'); 148 | } 149 | 150 | if (!isset($options['aria-label'], $options['aria-labelledby'])) { 151 | $options['aria-labelledby'] = $options['id'] . '-heading'; 152 | } 153 | 154 | $encodeLabel = $item['encode'] ?? $this->encodeLabels; 155 | 156 | if ($encodeLabel) { 157 | $header = Html::encode($header); 158 | } 159 | 160 | $itemToggleOptions = array_merge([ 161 | 'tag' => 'button', 162 | 'type' => 'button', 163 | 'data-toggle' => 'collapse', 164 | 'data-target' => '#' . $options['id'], 165 | 'aria-expanded' => $expand ? 'true' : 'false', 166 | 'aria-controls' => $options['id'], 167 | ], $this->itemToggleOptions); 168 | $itemToggleTag = ArrayHelper::remove($itemToggleOptions, 'tag', 'button'); 169 | 170 | /** @psalm-suppress ConflictingReferenceConstraint */ 171 | if ($itemToggleTag === 'a') { 172 | ArrayHelper::remove($itemToggleOptions, 'data-target'); 173 | $headerToggle = Html::a($header, '#' . $id, $itemToggleOptions) . "\n"; 174 | } else { 175 | Html::addCssClass($itemToggleOptions, 'btn-link'); 176 | $headerToggle = Button::widget() 177 | ->label($header) 178 | ->encodeLabels(false) 179 | ->options($itemToggleOptions) 180 | ->render() . "\n"; 181 | } 182 | 183 | $header = Html::tag('h5', $headerToggle, ['class' => 'mb-0']); 184 | 185 | if (is_string($item['content']) || is_numeric($item['content']) || is_object($item['content'])) { 186 | $content = Html::tag('div', $item['content'], ['class' => 'card-body']) . "\n"; 187 | } elseif (is_array($item['content'])) { 188 | $content = Html::ul($item['content'], [ 189 | 'class' => 'list-group', 190 | 'itemOptions' => [ 191 | 'class' => 'list-group-item', 192 | ], 193 | 'encode' => false, 194 | ]) . "\n"; 195 | } else { 196 | throw new RuntimeException('The "content" option should be a string, array or object.'); 197 | } 198 | } else { 199 | throw new RuntimeException('The "content" option is required.'); 200 | } 201 | 202 | $group = []; 203 | 204 | if ($this->autoCloseItems) { 205 | $options['data-parent'] = '#' . $this->options['id']; 206 | } 207 | 208 | $group[] = Html::tag('div', $header, ['class' => 'card-header', 'id' => $options['id'] . '-heading']); 209 | $group[] = Html::beginTag('div', $options); 210 | $group[] = $content; 211 | 212 | if (isset($item['footer'])) { 213 | $group[] = Html::tag('div', $item['footer'], ['class' => 'card-footer']); 214 | } 215 | 216 | $group[] = Html::endTag('div'); 217 | 218 | return implode("\n", $group); 219 | } 220 | 221 | /** 222 | * Whether to close other items if an item is opened. Defaults to `true` which causes an accordion effect. 223 | * 224 | * Set this to `false` to allow keeping multiple items open at once. 225 | * 226 | * @param bool $value 227 | * 228 | * @return $this 229 | */ 230 | public function autoCloseItems(bool $value): self 231 | { 232 | $this->autoCloseItems = $value; 233 | 234 | return $this; 235 | } 236 | 237 | /** 238 | * Whether the labels for header items should be HTML-encoded. 239 | * 240 | * @param bool $value 241 | * 242 | * @return $this 243 | */ 244 | public function encodeLabels(bool $value): self 245 | { 246 | $this->encodeLabels = $value; 247 | 248 | return $this; 249 | } 250 | 251 | /** 252 | * List of groups in the collapse widget. Each array element represents a single group with the following structure: 253 | * 254 | * - label: string, required, the group header label. 255 | * - encode: bool, optional, whether this label should be HTML-encoded. This param will override global 256 | * `$this->encodeLabels` param. 257 | * - content: array|string|object, required, the content (HTML) of the group 258 | * - options: array, optional, the HTML attributes of the group 259 | * - contentOptions: optional, the HTML attributes of the group's content 260 | * 261 | * You may also specify this property as key-value pairs, where the key refers to the `label` and the value refers 262 | * to `content`. If value is a string it is interpreted as label. If it is an array, it is interpreted as explained 263 | * above. 264 | * 265 | * For example: 266 | * 267 | * ```php 268 | * echo Accordion::widget([ 269 | * 'items' => [ 270 | * 'Introduction' => 'This is the first collapsible menu', 271 | * 'Second panel' => [ 272 | * 'content' => 'This is the second collapsible menu', 273 | * ], 274 | * [ 275 | * 'label' => 'Third panel', 276 | * 'content' => 'This is the third collapsible menu', 277 | * ], 278 | * ] 279 | * ]) 280 | * ``` 281 | * 282 | * @param array $value 283 | * 284 | * @return $this 285 | */ 286 | public function items(array $value): self 287 | { 288 | $this->items = $value; 289 | 290 | return $this; 291 | } 292 | 293 | /** 294 | * The HTML options for the item toggle tag. Key 'tag' might be used here for the tag name specification. 295 | * 296 | * For example: 297 | * 298 | * ```php 299 | * [ 300 | * 'tag' => 'div', 301 | * 'class' => 'custom-toggle', 302 | * ] 303 | * ``` 304 | * 305 | * @param array $value 306 | * 307 | * @return $this 308 | */ 309 | public function itemToggleOptions(array $value): self 310 | { 311 | $this->itemToggleOptions = $value; 312 | 313 | return $this; 314 | } 315 | 316 | /** 317 | * The HTML attributes for the widget container tag. The following special options are recognized. 318 | * 319 | * @param array $value 320 | * 321 | * @return $this 322 | * 323 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 324 | */ 325 | public function options(array $value): self 326 | { 327 | $this->options = $value; 328 | 329 | return $this; 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/NavBar.php: -------------------------------------------------------------------------------- 1 | getId() !== null) { 19 | * $menuItems = [ 20 | * [ 21 | * 'label' => 'About', 22 | * 'url' => '/about', 23 | * ], 24 | * [ 25 | * 'label' => 'Contact', 26 | * 'url' => '/contact', 27 | * ], 28 | * [ 29 | * 'label' => 'Logout' . ' ' . '(' . $user->getUsername() . ')', 30 | * 'url' => '/logout' 31 | * ], 32 | * ]; 33 | * } else { 34 | * $menuItems = [ 35 | * [ 36 | * 'label' => 'About', 37 | * 'url' => '/about', 38 | * ], 39 | * [ 40 | * 'label' => 'Contact', 41 | * 'url' => '/contact', 42 | * ], 43 | * [ 44 | * 'label' => 'Login', 45 | * 'url' => '/login', 46 | * ], 47 | * ]; 48 | * } 49 | * 50 | * brandLabel('My Application Basic') 52 | * ->brandUrl('/') 53 | * ->options([ 54 | * 'class' => 'navbar navbar-dark bg-dark navbar-expand-lg text-white', 55 | * ]) 56 | * ->begin(); 57 | * 58 | * echo Nav::widget() 59 | * ->currentPath($currentPath) 60 | * ->items($menuItems) 61 | * ->options([ 62 | * 'class' => 'navbar-nav float-right ml-auto' 63 | * ]); 64 | * 65 | * echo NavBar::end(); ?> 66 | * ``` 67 | * Note: $currentPath it must be injected from each controller to the main controller. 68 | * 69 | * SiteController.php 70 | * 71 | * ```php 72 | * 73 | * public function index(ServerRequestInterface $request): ResponseInterface 74 | * { 75 | * $response = $this->responseFactory->createResponse(); 76 | * $currentPath = $request->getUri()->getPath(); 77 | * $output = $this->render('index', ['currentPath' => $currentPath]); 78 | * $response->getBody()->write($output); 79 | * 80 | * return $response; 81 | * } 82 | * ``` 83 | * 84 | * Controller.php 85 | * 86 | * ```php 87 | * private function renderContent($content, array $parameters = []): string 88 | * { 89 | * $user = $this->user->getIdentity(); 90 | * $layout = $this->findLayoutFile($this->layout); 91 | * 92 | * if ($layout !== null) { 93 | * return $this->view->renderFile( 94 | * $layout, 95 | * [ 96 | * 'aliases' => $this->aliases, 97 | * 'content' => $content, 98 | * 'user' => $user, 99 | * 'params' => $this->params, 100 | * 'currentPath' => !isset($parameters['currentPath']) ?: $parameters['currentPath'] 101 | * ], 102 | * $this 103 | * ); 104 | * } 105 | * 106 | * return $content; 107 | * } 108 | * ``` 109 | */ 110 | class NavBar extends Widget 111 | { 112 | private array $collapseOptions = []; 113 | private ?string $brandLabel = null; 114 | private ?string $brandImage = null; 115 | private ?string $brandUrl = '/'; 116 | private array $brandOptions = []; 117 | private string $screenReaderToggleText = 'Toggle navigation'; 118 | private string $togglerContent = ''; 119 | private array $togglerOptions = []; 120 | private bool $renderInnerContainer = true; 121 | private array $innerContainerOptions = []; 122 | private array $options = []; 123 | 124 | public function begin(): ?string 125 | { 126 | parent::begin(); 127 | 128 | if (!isset($this->options['id'])) { 129 | $id = $this->getId(); 130 | $this->options['id'] = "{$id}-navbar"; 131 | $this->collapseOptions['id'] = "{$id}-collapse"; 132 | } 133 | 134 | if (empty($this->options['class'])) { 135 | Html::addCssClass($this->options, ['widget' => 'navbar', 'navbar-expand-lg', 'navbar-light', 'bg-light']); 136 | } else { 137 | Html::addCssClass($this->options, ['widget' => 'navbar']); 138 | } 139 | 140 | $navOptions = $this->options; 141 | $navTag = ArrayHelper::remove($navOptions, 'tag', 'nav'); 142 | $brand = ''; 143 | 144 | if (!isset($this->innerContainerOptions['class'])) { 145 | Html::addCssClass($this->innerContainerOptions, 'container'); 146 | } 147 | 148 | if ($this->brandImage !== null) { 149 | $this->brandLabel = Html::img($this->brandImage); 150 | } 151 | 152 | if ($this->brandLabel !== null) { 153 | Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']); 154 | if (empty($this->brandUrl)) { 155 | $brand = Html::tag('span', $this->brandLabel, $this->brandOptions); 156 | } else { 157 | $brand = Html::a( 158 | $this->brandLabel, 159 | $this->brandUrl, 160 | $this->brandOptions 161 | ); 162 | } 163 | } 164 | 165 | Html::addCssClass($this->collapseOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']); 166 | $collapseOptions = $this->collapseOptions; 167 | $collapseTag = ArrayHelper::remove($collapseOptions, 'tag', 'div'); 168 | 169 | $htmlStart = Html::beginTag($navTag, $navOptions) . "\n"; 170 | 171 | if ($this->renderInnerContainer) { 172 | $htmlStart .= Html::beginTag('div', $this->innerContainerOptions) . "\n"; 173 | } 174 | 175 | $htmlStart .= $brand . "\n"; 176 | $htmlStart .= $this->renderToggleButton() . "\n"; 177 | 178 | $htmlStart .= Html::beginTag($collapseTag, $collapseOptions) . "\n"; 179 | 180 | return $htmlStart; 181 | } 182 | 183 | protected function run(): string 184 | { 185 | $tag = ArrayHelper::remove($this->collapseOptions, 'tag', 'div'); 186 | 187 | $htmlRun = Html::endTag($tag) . "\n"; 188 | 189 | if ($this->renderInnerContainer) { 190 | $htmlRun .= Html::endTag('div') . "\n"; 191 | } 192 | 193 | $tag = ArrayHelper::remove($this->options, 'tag', 'nav'); 194 | 195 | $htmlRun .= Html::endTag($tag); 196 | 197 | return $htmlRun; 198 | } 199 | 200 | /** 201 | * Renders collapsible toggle button. 202 | * 203 | * @throws JsonException 204 | * 205 | * @return string the rendering toggle button. 206 | */ 207 | protected function renderToggleButton(): string 208 | { 209 | $options = $this->togglerOptions; 210 | 211 | Html::addCssClass($options, ['widget' => 'navbar-toggler']); 212 | 213 | return Html::button( 214 | $this->togglerContent, 215 | ArrayHelper::merge($options, [ 216 | 'type' => 'button', 217 | 'data' => [ 218 | 'toggle' => 'collapse', 219 | 'target' => '#' . $this->collapseOptions['id'], 220 | ], 221 | 'aria-controls' => $this->collapseOptions['id'], 222 | 'aria-expanded' => 'false', 223 | 'aria-label' => $this->screenReaderToggleText, 224 | ]) 225 | ); 226 | } 227 | 228 | /** 229 | * The HTML attributes for the container tag. The following special options are recognized. 230 | * 231 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 232 | * 233 | * @param array $value 234 | * 235 | * @return $this 236 | */ 237 | public function collapseOptions(array $value): self 238 | { 239 | $this->collapseOptions = $value; 240 | 241 | return $this; 242 | } 243 | 244 | /** 245 | * The text of the brand or empty if it's not used. Note that this is not HTML-encoded. 246 | * 247 | * @param string|null $value 248 | * 249 | * @return $this 250 | * 251 | * {@see https://getbootstrap.com/docs/4.2/components/navbar/} 252 | */ 253 | public function brandLabel(?string $value): self 254 | { 255 | $this->brandLabel = $value; 256 | 257 | return $this; 258 | } 259 | 260 | /** 261 | * Src of the brand image or empty if it's not used. Note that this param will override `$this->brandLabel` param. 262 | * 263 | * @param string|null $value 264 | * 265 | * @return $this 266 | * 267 | * {@see https://getbootstrap.com/docs/4.2/components/navbar/} 268 | */ 269 | public function brandImage(?string $value): self 270 | { 271 | $this->brandImage = $value; 272 | 273 | return $this; 274 | } 275 | 276 | /** 277 | * The URL for the brand's hyperlink tag and will be used for the "href" attribute of the brand link. Default value 278 | * is '/' will be used. You may set it to `null` if you want to have no link at all. 279 | * 280 | * @param string|null $value 281 | * 282 | * @return $this 283 | */ 284 | public function brandUrl(?string $value): self 285 | { 286 | $this->brandUrl = $value; 287 | 288 | return $this; 289 | } 290 | 291 | /** 292 | * The HTML attributes of the brand link. 293 | * 294 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 295 | * 296 | * @param array $value 297 | * 298 | * @return $this 299 | */ 300 | public function brandOptions(array $value): self 301 | { 302 | $this->brandOptions = $value; 303 | 304 | return $this; 305 | } 306 | 307 | /** 308 | * Text to show for screen readers for the button to toggle the navbar. 309 | * 310 | * @param string $value 311 | * 312 | * @return $this 313 | */ 314 | public function screenReaderToggleText(string $value): self 315 | { 316 | $this->screenReaderToggleText = $value; 317 | 318 | return $this; 319 | } 320 | 321 | /** 322 | * The toggle button content. Defaults to bootstrap 4 default ``. 323 | * 324 | * @param string $value 325 | * 326 | * @return $this 327 | */ 328 | public function togglerContent(string $value): self 329 | { 330 | $this->togglerContent = $value; 331 | 332 | return $this; 333 | } 334 | 335 | /** 336 | * The HTML attributes of the navbar toggler button. 337 | * 338 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 339 | * 340 | * @param array $value 341 | * 342 | * @return $this 343 | */ 344 | public function togglerOptions(array $value): self 345 | { 346 | $this->togglerOptions = $value; 347 | 348 | return $this; 349 | } 350 | 351 | /** 352 | * Whether the navbar content should be included in an inner div container which by default adds left and right 353 | * padding. Set this to false for a 100% width navbar. 354 | * 355 | * @param bool $value 356 | * 357 | * @return $this 358 | */ 359 | public function renderInnerContainer(bool $value): self 360 | { 361 | $this->renderInnerContainer = $value; 362 | 363 | return $this; 364 | } 365 | 366 | /** 367 | * The HTML attributes of the inner container. 368 | * 369 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 370 | * 371 | * @param array $value 372 | * 373 | * @return $this 374 | */ 375 | public function innerContainerOptions(array $value): self 376 | { 377 | $this->innerContainerOptions = $value; 378 | 379 | return $this; 380 | } 381 | 382 | /** 383 | * The HTML attributes for the widget container tag. The following special options are recognized. 384 | * 385 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 386 | * 387 | * @param array $value 388 | * 389 | * @return $this 390 | */ 391 | public function options(array $value): self 392 | { 393 | $this->options = $value; 394 | 395 | return $this; 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/Modal.php: -------------------------------------------------------------------------------- 1 | title('

      Hello world

      ') 22 | * ->toggleButton(['label' => 'click me']) 23 | * ->begin(); 24 | * 25 | * echo 'Say hello...'; 26 | * 27 | * echo Modal::end(); 28 | * ``` 29 | */ 30 | class Modal extends Widget 31 | { 32 | /** 33 | * The additional css class of large modal 34 | */ 35 | public const SIZE_LARGE = 'modal-lg'; 36 | 37 | /** 38 | * The additional css class of small modal 39 | */ 40 | public const SIZE_SMALL = 'modal-sm'; 41 | 42 | /** 43 | * The additional css class of default modal 44 | */ 45 | public const SIZE_DEFAULT = ''; 46 | 47 | private ?string $title = null; 48 | private array $titleOptions = []; 49 | private array $headerOptions = []; 50 | private array $bodyOptions = []; 51 | private ?string $footer = null; 52 | private array $footerOptions = []; 53 | private ?string $size = null; 54 | private array $closeButton = []; 55 | private bool $closeButtonEnabled = true; 56 | private array $toggleButton = []; 57 | private bool $toggleButtonEnabled = true; 58 | private array $options = []; 59 | 60 | public function begin(): ?string 61 | { 62 | parent::begin(); 63 | 64 | if (!isset($this->options['id'])) { 65 | $this->options['id'] = "{$this->getId()}-modal"; 66 | } 67 | 68 | $this->initOptions(); 69 | 70 | return 71 | $this->renderToggleButton() . "\n" . 72 | Html::beginTag('div', $this->options) . "\n" . 73 | Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n" . 74 | Html::beginTag('div', ['class' => 'modal-content']) . "\n" . 75 | $this->renderHeader() . "\n" . 76 | $this->renderBodyBegin() . "\n"; 77 | } 78 | 79 | protected function run(): string 80 | { 81 | $this->registerPlugin('modal', $this->options); 82 | 83 | return 84 | "\n" . $this->renderBodyEnd() . 85 | "\n" . $this->renderFooter() . 86 | "\n" . Html::endTag('div') . // modal-content 87 | "\n" . Html::endTag('div') . // modal-dialog 88 | "\n" . Html::endTag('div'); 89 | } 90 | 91 | /** 92 | * Renders the header HTML markup of the modal. 93 | * 94 | * @throws JsonException 95 | * 96 | * @return string the rendering result 97 | */ 98 | protected function renderHeader(): string 99 | { 100 | $button = $this->renderCloseButton(); 101 | 102 | if ($this->title !== null) { 103 | Html::addCssClass($this->titleOptions, ['widget' => 'modal-title']); 104 | $header = Html::tag('h5', $this->title, $this->titleOptions); 105 | } else { 106 | $header = ''; 107 | } 108 | 109 | if ($button !== null) { 110 | $header .= "\n" . $button; 111 | } elseif ($header === '') { 112 | return ''; 113 | } 114 | 115 | Html::addCssClass($this->headerOptions, ['widget' => 'modal-header']); 116 | 117 | return Html::tag('div', "\n" . $header . "\n", $this->headerOptions); 118 | } 119 | 120 | /** 121 | * Renders the opening tag of the modal body. 122 | * 123 | * @throws JsonException 124 | * 125 | * @return string the rendering result 126 | */ 127 | protected function renderBodyBegin(): string 128 | { 129 | Html::addCssClass($this->bodyOptions, ['widget' => 'modal-body']); 130 | 131 | return Html::beginTag('div', $this->bodyOptions); 132 | } 133 | 134 | /** 135 | * Renders the closing tag of the modal body. 136 | * 137 | * @return string the rendering result 138 | */ 139 | protected function renderBodyEnd(): string 140 | { 141 | return Html::endTag('div'); 142 | } 143 | 144 | /** 145 | * Renders the HTML markup for the footer of the modal. 146 | * 147 | * @throws JsonException 148 | * 149 | * @return string the rendering result 150 | */ 151 | protected function renderFooter(): ?string 152 | { 153 | if ($this->footer === null) { 154 | return null; 155 | } 156 | 157 | Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']); 158 | return Html::tag('div', "\n" . $this->footer . "\n", $this->footerOptions); 159 | } 160 | 161 | /** 162 | * Renders the toggle button. 163 | * 164 | * @throws JsonException 165 | * 166 | * @return string the rendering result 167 | */ 168 | protected function renderToggleButton(): ?string 169 | { 170 | if ($this->toggleButtonEnabled === false) { 171 | return null; 172 | } 173 | 174 | $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button'); 175 | $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show'); 176 | 177 | return Html::tag($tag, $label, $this->toggleButton); 178 | } 179 | 180 | /** 181 | * Renders the close button. 182 | * 183 | * @throws JsonException 184 | * 185 | * @return string the rendering result. 186 | */ 187 | protected function renderCloseButton(): ?string 188 | { 189 | if ($this->closeButtonEnabled === false) { 190 | return null; 191 | } 192 | 193 | $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); 194 | $label = ArrayHelper::remove($this->closeButton, 'label', Html::tag('span', '×', [ 195 | 'aria-hidden' => 'true', 196 | ])); 197 | 198 | return Html::tag($tag, $label, $this->closeButton); 199 | } 200 | 201 | /** 202 | * Initializes the widget options. 203 | * 204 | * This method sets the default values for various options. 205 | */ 206 | protected function initOptions(): void 207 | { 208 | $this->options = array_merge([ 209 | 'class' => 'fade', 210 | 'role' => 'dialog', 211 | 'tabindex' => -1, 212 | 'aria-hidden' => 'true', 213 | ], $this->options); 214 | 215 | Html::addCssClass($this->options, ['widget' => 'modal']); 216 | 217 | if ($this->getEnableClientOptions() !== false) { 218 | $this->clientOptions(array_merge(['show' => false], $this->getClientOptions())); 219 | } 220 | 221 | $this->titleOptions = array_merge([ 222 | 'id' => $this->options['id'] . '-label', 223 | ], $this->titleOptions); 224 | 225 | if (!isset($this->options['aria-label'], $this->options['aria-labelledby']) && $this->title !== null) { 226 | $this->options['aria-labelledby'] = $this->titleOptions['id']; 227 | } 228 | 229 | if ($this->closeButtonEnabled !== false) { 230 | $this->closeButton = array_merge([ 231 | 'data-dismiss' => 'modal', 232 | 'class' => 'close', 233 | 'type' => 'button', 234 | ], $this->closeButton); 235 | } 236 | 237 | if ($this->toggleButton !== []) { 238 | $this->toggleButton = array_merge([ 239 | 'data-toggle' => 'modal', 240 | 'type' => 'button', 241 | ], $this->toggleButton); 242 | if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) { 243 | $this->toggleButton['data-target'] = '#' . $this->options['id']; 244 | } 245 | } 246 | } 247 | 248 | /** 249 | * Body options. 250 | * 251 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 252 | * 253 | * @param array $value 254 | * 255 | * @return $this 256 | */ 257 | public function bodyOptions(array $value): self 258 | { 259 | $this->bodyOptions = $value; 260 | 261 | return $this; 262 | } 263 | 264 | /** 265 | * The options for rendering the close button tag. 266 | * 267 | * The close button is displayed in the header of the modal window. Clicking on the button will hide the modal 268 | * window. If {@see closeButtonEnabled} is false, no close button will be rendered. 269 | * 270 | * The following special options are supported: 271 | * 272 | * - tag: string, the tag name of the button. Defaults to 'button'. 273 | * - label: string, the label of the button. Defaults to '×'. 274 | * 275 | * The rest of the options will be rendered as the HTML attributes of the button tag. Please refer to the 276 | * [Modal plugin help](http://getbootstrap.com/javascript/#modals) for the supported HTML attributes. 277 | * 278 | * @param array $value 279 | * 280 | * @return $this 281 | */ 282 | public function closeButton(array $value): self 283 | { 284 | $this->closeButton = $value; 285 | 286 | return $this; 287 | } 288 | 289 | /** 290 | * Enable/Disable close button. 291 | * 292 | * @param bool $value 293 | * 294 | * @return $this 295 | */ 296 | public function closeButtonEnabled(bool $value): self 297 | { 298 | $this->closeButtonEnabled = $value; 299 | 300 | return $this; 301 | } 302 | 303 | /** 304 | * The footer content in the modal window. 305 | * 306 | * @param string|null $value 307 | * 308 | * @return $this 309 | */ 310 | public function footer(?string $value): self 311 | { 312 | $this->footer = $value; 313 | 314 | return $this; 315 | } 316 | 317 | /** 318 | * Additional footer options. 319 | * 320 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 321 | */ 322 | public function footerOptions(array $value): self 323 | { 324 | $this->footerOptions = $value; 325 | 326 | return $this; 327 | } 328 | 329 | /** 330 | * Additional header options. 331 | * 332 | * @param array $value 333 | * 334 | * @return $this 335 | * 336 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 337 | */ 338 | public function headerOptions(array $value): self 339 | { 340 | $this->headerOptions = $value; 341 | 342 | return $this; 343 | } 344 | 345 | /** 346 | * @param array $value the HTML attributes for the widget container tag. The following special options are 347 | * recognized. 348 | * 349 | * @return $this 350 | * 351 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 352 | */ 353 | public function options(array $value): self 354 | { 355 | $this->options = $value; 356 | 357 | return $this; 358 | } 359 | 360 | /** 361 | * The title content in the modal window. 362 | * 363 | * @param string|null $value 364 | * 365 | * @return $this 366 | */ 367 | public function title(?string $value): self 368 | { 369 | $this->title = $value; 370 | 371 | return $this; 372 | } 373 | 374 | /** 375 | * Additional title options. 376 | * 377 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 378 | * 379 | * @param array $value 380 | * 381 | * @return $this 382 | */ 383 | public function titleOptions(array $value): self 384 | { 385 | $this->titleOptions = $value; 386 | 387 | return $this; 388 | } 389 | 390 | /** 391 | * The options for rendering the toggle button tag. 392 | * 393 | * The toggle button is used to toggle the visibility of the modal window. If {@see toggleButtonEnabled} is false, 394 | * no toggle button will be rendered. 395 | * 396 | * The following special options are supported: 397 | * 398 | * - tag: string, the tag name of the button. Defaults to 'button'. 399 | * - label: string, the label of the button. Defaults to 'Show'. 400 | * 401 | * The rest of the options will be rendered as the HTML attributes of the button tag. Please refer to the 402 | * [Modal plugin help](http://getbootstrap.com/javascript/#modals) for the supported HTML attributes. 403 | * 404 | * @param array $value 405 | * 406 | * @return $this 407 | */ 408 | public function toggleButton(array $value): self 409 | { 410 | $this->toggleButton = $value; 411 | 412 | return $this; 413 | } 414 | 415 | /** 416 | * Enable/Disable toggle button. 417 | * 418 | * @param bool $value 419 | * 420 | * @return $this 421 | */ 422 | public function toggleButtonEnabled(bool $value): self 423 | { 424 | $this->toggleButtonEnabled = $value; 425 | 426 | return $this; 427 | } 428 | 429 | /** 430 | * The modal size. Can be {@see SIZE_LARGE} or {@see SIZE_SMALL}, or null for default. 431 | * 432 | * @param string|null $value 433 | * 434 | * @return $this 435 | */ 436 | public function size(?string $value): self 437 | { 438 | $this->size = $value; 439 | 440 | return $this; 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/Tabs.php: -------------------------------------------------------------------------------- 1 | items([ 24 | * [ 25 | * 'label' => 'One', 26 | * 'content' => 'Anim pariatur cliche...', 27 | * 'active' => true, 28 | * ], 29 | * [ 30 | * 'label' => 'Two', 31 | * 'content' => 'Anim pariatur cliche...', 32 | * 'headerOptions' => [...], 33 | * 'options' => ['id' => 'myveryownID'], 34 | * ], 35 | * [ 36 | * 'label' => 'Example', 37 | * 'url' => 'http://www.example.com', 38 | * ], 39 | * [ 40 | * 'label' => 'Dropdown', 41 | * 'items' => [ 42 | * [ 43 | * 'label' => 'DropdownA', 44 | * 'content' => 'DropdownA, Anim pariatur cliche...', 45 | * ], 46 | * [ 47 | * 'label' => 'DropdownB', 48 | * 'content' => 'DropdownB, Anim pariatur cliche...', 49 | * ], 50 | * [ 51 | * 'label' => 'External Link', 52 | * 'url' => 'http://www.example.com', 53 | * ], 54 | * ], 55 | * ], 56 | * ]); 57 | * ``` 58 | */ 59 | class Tabs extends Widget 60 | { 61 | private array $panes = []; 62 | private array $items = []; 63 | private array $itemOptions = []; 64 | private array $headerOptions = []; 65 | private array $linkOptions = []; 66 | private bool $encodeLabels = true; 67 | private string $navType = 'nav-tabs'; 68 | private bool $renderTabContent = true; 69 | private array $tabContentOptions = []; 70 | private string $dropdownClass = Dropdown::class; 71 | private array $options = []; 72 | 73 | protected function run(): string 74 | { 75 | if (!isset($this->options['id'])) { 76 | $this->options['id'] = "{$this->getId()}-tabs"; 77 | } 78 | 79 | Html::addCssClass($this->options, ['widget' => 'nav', $this->navType]); 80 | Html::addCssClass($this->tabContentOptions, 'tab-content'); 81 | 82 | $this->registerPlugin('tab', $this->options); 83 | $this->prepareItems($this->items); 84 | 85 | return Nav::widget() 86 | ->dropdownClass($this->dropdownClass) 87 | ->options(ArrayHelper::merge(['role' => 'tablist'], $this->options)) 88 | ->items($this->items) 89 | ->encodeLabels($this->encodeLabels) 90 | ->render() 91 | . $this->renderPanes($this->panes); 92 | } 93 | 94 | /** 95 | * Renders tab items as specified on {@see items}. 96 | * 97 | * @param array $items 98 | * @param string $prefix 99 | * 100 | * @throws JsonException|RuntimeException 101 | */ 102 | protected function prepareItems(array &$items, string $prefix = ''): void 103 | { 104 | if (!$this->hasActiveTab()) { 105 | $this->activateFirstVisibleTab(); 106 | } 107 | 108 | foreach ($items as $n => $item) { 109 | $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); 110 | $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . $prefix . '-tab' . $n); 111 | 112 | /** {@see https://github.com/yiisoft/yii2-bootstrap4/issues/108#issuecomment-465219339} */ 113 | unset($items[$n]['options']['id']); 114 | 115 | if (!ArrayHelper::remove($item, 'visible', true)) { 116 | continue; 117 | } 118 | 119 | if (!array_key_exists('label', $item)) { 120 | throw new RuntimeException('The "label" option is required.'); 121 | } 122 | 123 | $selected = ArrayHelper::getValue($item, 'active', false); 124 | $disabled = ArrayHelper::getValue($item, 'disabled', false); 125 | $headerOptions = ArrayHelper::getValue($item, 'headerOptions', $this->headerOptions); 126 | 127 | if (isset($item['items'])) { 128 | $this->prepareItems($items[$n]['items'], '-dd' . $n); 129 | continue; 130 | } 131 | 132 | ArrayHelper::setValue($items[$n], 'options', $headerOptions); 133 | 134 | if (isset($item['url'])) { 135 | continue; 136 | } 137 | 138 | ArrayHelper::setValue($items[$n], 'url', '#' . $options['id']); 139 | ArrayHelper::setValueByPath($items[$n], 'linkOptions.data.toggle', 'tab'); 140 | ArrayHelper::setValueByPath($items[$n], 'linkOptions.role', 'tab'); 141 | ArrayHelper::setValueByPath($items[$n], 'linkOptions.aria-controls', $options['id']); 142 | 143 | if (!$disabled) { 144 | ArrayHelper::setValueByPath($items[$n], 'linkOptions.aria-selected', $selected ? 'true' : 'false'); 145 | } 146 | 147 | Html::addCssClass($options, ['widget' => 'tab-pane']); 148 | 149 | if ($selected) { 150 | Html::addCssClass($options, 'active'); 151 | } 152 | 153 | if ($this->renderTabContent) { 154 | $tag = ArrayHelper::remove($options, 'tag', 'div'); 155 | $this->panes[] = Html::tag($tag, $item['content'] ?? '', $options); 156 | } 157 | } 158 | } 159 | 160 | /** 161 | * @return bool if there's active tab defined. 162 | */ 163 | protected function hasActiveTab(): bool 164 | { 165 | foreach ($this->items as $item) { 166 | if (isset($item['active']) && $item['active'] === true) { 167 | return true; 168 | } 169 | } 170 | 171 | return false; 172 | } 173 | 174 | /** 175 | * Sets the first visible tab as active. 176 | * 177 | * This method activates the first tab that is visible and not explicitly set to inactive (`'active' => false`). 178 | */ 179 | protected function activateFirstVisibleTab(): void 180 | { 181 | foreach ($this->items as $i => $item) { 182 | $active = ArrayHelper::getValue($item, 'active', null); 183 | $visible = ArrayHelper::getValue($item, 'visible', true); 184 | $disabled = ArrayHelper::getValue($item, 'disabled', false); 185 | 186 | if ($visible && $active !== false && ($disabled !== true)) { 187 | $this->items[$i]['active'] = true; 188 | return; 189 | } 190 | } 191 | } 192 | 193 | /** 194 | * Renders tab panes. 195 | * 196 | * @param array $panes 197 | * 198 | * @throws JsonException 199 | * 200 | * @return string the rendering result. 201 | */ 202 | public function renderPanes(array $panes): string 203 | { 204 | return $this->renderTabContent ? ("\n" . Html::tag('div', implode("\n", $panes), $this->tabContentOptions)) : ''; 205 | } 206 | 207 | /** 208 | * Name of a class to use for rendering dropdowns withing this widget. Defaults to {@see Dropdown}. 209 | * 210 | * @param string $value 211 | * 212 | * @return $this 213 | */ 214 | public function dropdownClass(string $value): self 215 | { 216 | $this->dropdownClass = $value; 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * Whether the labels for header items should be HTML-encoded. 223 | * 224 | * @param bool $value 225 | * 226 | * @return $this 227 | */ 228 | public function encodeLabels(bool $value): self 229 | { 230 | $this->encodeLabels = $value; 231 | 232 | return $this; 233 | } 234 | 235 | /** 236 | * List of HTML attributes for the header container tags. This will be overwritten by the "headerOptions" set in 237 | * individual {@see items}. 238 | * 239 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 240 | * 241 | * @param array $value 242 | * 243 | * @return $this 244 | */ 245 | public function headerOptions(array $value): self 246 | { 247 | $this->headerOptions = $value; 248 | 249 | return $this; 250 | } 251 | 252 | /** 253 | * List of tabs in the tabs widget. Each array element represents a single tab with the following structure: 254 | * 255 | * - label: string, required, the tab header label. 256 | * - encode: bool, optional, whether this label should be HTML-encoded. This param will override 257 | * global `$this->encodeLabels` param. 258 | * - headerOptions: array, optional, the HTML attributes of the tab header. 259 | * - linkOptions: array, optional, the HTML attributes of the tab header link tags. 260 | * - content: string, optional, the content (HTML) of the tab pane. 261 | * - url: string, optional, an external URL. When this is specified, clicking on this tab will bring 262 | * the browser to this URL. 263 | * - options: array, optional, the HTML attributes of the tab pane container. 264 | * - active: bool, optional, whether this item tab header and pane should be active. If no item is marked as 265 | * 'active' explicitly - the first one will be activated. 266 | * - visible: bool, optional, whether the item tab header and pane should be visible or not. Defaults to true. 267 | * - items: array, optional, can be used instead of `content` to specify a dropdown items 268 | * configuration array. Each item can hold three extra keys, besides the above ones: 269 | * * active: bool, optional, whether the item tab header and pane should be visible or not. 270 | * * content: string, required if `items` is not set. The content (HTML) of the tab pane. 271 | * * contentOptions: optional, array, the HTML attributes of the tab content container. 272 | * 273 | * @param array $value 274 | * 275 | * @return $this 276 | */ 277 | public function items(array $value): self 278 | { 279 | $this->items = $value; 280 | 281 | return $this; 282 | } 283 | 284 | /** 285 | * List of HTML attributes for the item container tags. This will be overwritten by the "options" set in individual 286 | * {@see items}. The following special options are recognized. 287 | * 288 | * @param array $value 289 | * 290 | * @return $this 291 | * 292 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 293 | */ 294 | public function itemOptions(array $value): self 295 | { 296 | $this->itemOptions = $value; 297 | 298 | return $this; 299 | } 300 | 301 | /** 302 | * List of HTML attributes for the tab header link tags. This will be overwritten by the "linkOptions" set in 303 | * individual {@see items}. 304 | * 305 | * @param array $value 306 | * 307 | * @return $this 308 | * 309 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 310 | */ 311 | public function linkOptions(array $value): self 312 | { 313 | $this->linkOptions = $value; 314 | 315 | return $this; 316 | } 317 | 318 | /** 319 | * Specifies the Bootstrap tab styling. 320 | * 321 | * @param string $value 322 | * 323 | * @return $this 324 | */ 325 | public function navType(string $value): self 326 | { 327 | $this->navType = $value; 328 | 329 | return $this; 330 | } 331 | 332 | /** 333 | * The HTML attributes for the widget container tag. The following special options are recognized. 334 | * 335 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 336 | * 337 | * @param array $value 338 | * 339 | * @return $this 340 | */ 341 | public function options(array $value): self 342 | { 343 | $this->options = $value; 344 | 345 | return $this; 346 | } 347 | 348 | /** 349 | * Tab panes (contents). 350 | * 351 | * @param array $value 352 | * 353 | * @return $this 354 | */ 355 | public function panes(array $value): self 356 | { 357 | $this->panes = $value; 358 | 359 | return $this; 360 | } 361 | 362 | /** 363 | * Whether to render the `tab-content` container and its content. You may set this property to be false so that you 364 | * can manually render `tab-content` yourself in case your tab contents are complex. 365 | * 366 | * @param bool $value 367 | * 368 | * @return $this 369 | */ 370 | public function renderTabContent(bool $value): self 371 | { 372 | $this->renderTabContent = $value; 373 | 374 | return $this; 375 | } 376 | 377 | /** 378 | * List of HTML attributes for the `tab-content` container. This will always contain the CSS class `tab-content`. 379 | * 380 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 381 | * 382 | * @param array $value 383 | * 384 | * @return $this 385 | */ 386 | public function tabContentOptions(array $value): self 387 | { 388 | $this->tabContentOptions = $value; 389 | 390 | return $this; 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/Nav.php: -------------------------------------------------------------------------------- 1 | getId() !== null) { 22 | * $menuItems = [ 23 | * [ 24 | * 'label' => 'About', 25 | * 'url' => '/about', 26 | * ], 27 | * [ 28 | * 'label' => 'Contact', 29 | * 'url' => '/contact', 30 | * ], 31 | * [ 32 | * 'label' => 'Logout' . ' ' . '(' . $user->getUsername() . ')', 33 | * 'url' => '/logout' 34 | * ], 35 | * ]; 36 | * } else { 37 | * $menuItems = [ 38 | * [ 39 | * 'label' => 'About', 40 | * 'url' => '/about', 41 | * ], 42 | * [ 43 | * 'label' => 'Contact', 44 | * 'url' => '/contact', 45 | * ], 46 | * [ 47 | * 'label' => 'Login', 48 | * 'url' => '/login', 49 | * ], 50 | * ]; 51 | * } 52 | * 53 | * echo Nav::widget() 54 | * ->currentPath($currentPath) 55 | * ->items($menuItems) 56 | * ->options([ 57 | * 'class' => 'navbar-nav float-right ml-auto' 58 | * ]); 59 | * 60 | * Note: Multilevel dropdowns beyond Level 1 are not supported in Bootstrap 3. 61 | * Note: $currentPath it must be injected from each controller to the main controller. 62 | * 63 | * SiteController.php 64 | * 65 | * ```php 66 | * 67 | * public function index(ServerRequestInterface $request): ResponseInterface 68 | * { 69 | * $response = $this->responseFactory->createResponse(); 70 | * $currentPath = $request->getUri()->getPath(); 71 | * $output = $this->render('index', ['currentPath' => $currentPath]); 72 | * $response->getBody()->write($output); 73 | * 74 | * return $response; 75 | * } 76 | * ``` 77 | * 78 | * Controller.php 79 | * 80 | * ```php 81 | * private function renderContent($content, array $parameters = []): string 82 | * { 83 | * $user = $this->user->getIdentity(); 84 | * $layout = $this->findLayoutFile($this->layout); 85 | * 86 | * if ($layout !== null) { 87 | * return $this->view->renderFile( 88 | * $layout, 89 | * [ 90 | * 'aliases' => $this->aliases, 91 | * 'content' => $content, 92 | * 'user' => $user, 93 | * 'params' => $this->params, 94 | * 'currentPath' => !isset($parameters['currentPath']) ?: $parameters['currentPath'] 95 | * ], 96 | * $this 97 | * ); 98 | * } 99 | * 100 | * return $content; 101 | * } 102 | * ``` 103 | * 104 | * {@see http://getbootstrap.com/components/#dropdowns} 105 | * {@see http://getbootstrap.com/components/#nav} 106 | */ 107 | class Nav extends Widget 108 | { 109 | private string $label = ''; 110 | private array $items = []; 111 | private bool $encodeLabels = true; 112 | private bool $activateItems = true; 113 | private bool $activateParents = false; 114 | private ?string $currentPath = null; 115 | private array $params = []; 116 | private string $dropdownClass = Dropdown::class; 117 | private array $options = []; 118 | 119 | protected function run(): string 120 | { 121 | if (!isset($this->options['id'])) { 122 | $this->options['id'] = "{$this->getId()}-nav"; 123 | } 124 | 125 | Html::addCssClass($this->options, ['widget' => 'nav']); 126 | 127 | return $this->renderItems(); 128 | } 129 | 130 | /** 131 | * Renders widget items. 132 | * 133 | * @throws JsonException|RuntimeException 134 | * 135 | * @return string 136 | */ 137 | public function renderItems(): string 138 | { 139 | $items = []; 140 | 141 | foreach ($this->items as $i => $item) { 142 | if (isset($item['visible']) && !$item['visible']) { 143 | continue; 144 | } 145 | 146 | $items[] = $this->renderItem($item); 147 | } 148 | 149 | return Html::tag('ul', implode("\n", $items), $this->options); 150 | } 151 | 152 | /** 153 | * Renders a widget's item. 154 | * 155 | * @param array|string $item the item to render. 156 | * 157 | * @throws JsonException|RuntimeException 158 | * 159 | * @return string the rendering result. 160 | */ 161 | public function renderItem($item): string 162 | { 163 | if (is_string($item)) { 164 | return $item; 165 | } 166 | 167 | if (!isset($item['label'])) { 168 | throw new RuntimeException('The "label" option is required.'); 169 | } 170 | 171 | $encodeLabel = $item['encode'] ?? $this->encodeLabels; 172 | $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; 173 | $options = ArrayHelper::getValue($item, 'options', []); 174 | $items = ArrayHelper::getValue($item, 'items'); 175 | $url = ArrayHelper::getValue($item, 'url', '#'); 176 | $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); 177 | $disabled = ArrayHelper::getValue($item, 'disabled', false); 178 | $active = $this->isItemActive($item); 179 | 180 | if (empty($items)) { 181 | $items = ''; 182 | } else { 183 | $linkOptions['data-toggle'] = 'dropdown'; 184 | 185 | Html::addCssClass($options, ['widget' => 'dropdown']); 186 | Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle']); 187 | 188 | if (is_array($items)) { 189 | $items = $this->isChildActive($items, $active); 190 | $items = $this->renderDropdown($items, $item); 191 | } 192 | } 193 | 194 | Html::addCssClass($options, 'nav-item'); 195 | Html::addCssClass($linkOptions, 'nav-link'); 196 | 197 | if ($disabled) { 198 | ArrayHelper::setValue($linkOptions, 'tabindex', '-1'); 199 | ArrayHelper::setValue($linkOptions, 'aria-disabled', 'true'); 200 | Html::addCssClass($linkOptions, 'disabled'); 201 | } elseif ($this->activateItems && $active) { 202 | Html::addCssClass($linkOptions, 'active'); 203 | } 204 | 205 | return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options); 206 | } 207 | 208 | /** 209 | * Renders the given items as a dropdown. 210 | * 211 | * This method is called to create sub-menus. 212 | * 213 | * @param array $items the given items. Please refer to {@see Dropdown::items} for the array structure. 214 | * @param array $parentItem the parent item information. Please refer to {@see items} for the structure of this 215 | * array. 216 | * 217 | * @return string the rendering result. 218 | */ 219 | protected function renderDropdown(array $items, array $parentItem): string 220 | { 221 | $dropdownClass = $this->dropdownClass; 222 | 223 | return $dropdownClass::widget() 224 | ->enableClientOptions(false) 225 | ->encodeLabels($this->encodeLabels) 226 | ->items($items) 227 | ->options(ArrayHelper::getValue($parentItem, 'dropdownOptions', [])) 228 | ->render(); 229 | } 230 | 231 | /** 232 | * Check to see if a child item is active optionally activating the parent. 233 | * 234 | * @param array $items 235 | * @param bool $active should the parent be active too 236 | * 237 | * @return array 238 | * 239 | * {@see items} 240 | */ 241 | protected function isChildActive(array $items, bool &$active): array 242 | { 243 | foreach ($items as $i => $child) { 244 | if (is_array($child) && !ArrayHelper::getValue($child, 'visible', true)) { 245 | continue; 246 | } 247 | 248 | if ($this->isItemActive($child)) { 249 | ArrayHelper::setValue($items[$i], 'active', true); 250 | if ($this->activateParents) { 251 | $active = true; 252 | } 253 | } 254 | 255 | if (is_array($child) && ($childItems = ArrayHelper::getValue($child, 'items')) && is_array($childItems)) { 256 | $activeParent = false; 257 | $items[$i]['items'] = $this->isChildActive($childItems, $activeParent); 258 | 259 | if ($activeParent) { 260 | $items[$i]['options'] ??= []; 261 | Html::addCssClass($items[$i]['options'], 'active'); 262 | $active = true; 263 | } 264 | } 265 | } 266 | 267 | return $items; 268 | } 269 | 270 | /** 271 | * Checks whether a menu item is active. 272 | * 273 | * This is done by checking if {@see currentPath} match that specified in the `url` option of the menu item. When 274 | * the `url` option of a menu item is specified in terms of an array, its first element is treated as the 275 | * currentPath for the item and the rest of the elements are the associated parameters. Only when its currentPath 276 | * and parameters match {@see currentPath}, respectively, will a menu item be considered active. 277 | * 278 | * @param array|string $item the menu item to be checked 279 | * 280 | * @return bool whether the menu item is active 281 | */ 282 | protected function isItemActive($item): bool 283 | { 284 | if (isset($item['active'])) { 285 | return ArrayHelper::getValue($item, 'active', false); 286 | } 287 | 288 | return isset($item['url']) && $this->currentPath !== '/' && $item['url'] === $this->currentPath && $this->activateItems; 289 | } 290 | 291 | /** 292 | * List of items in the nav widget. Each array element represents a single menu item which can be either a string 293 | * or an array with the following structure: 294 | * 295 | * - label: string, required, the nav item label. 296 | * - url: optional, the item's URL. Defaults to "#". 297 | * - visible: bool, optional, whether this menu item is visible. Defaults to true. 298 | * - linkOptions: array, optional, the HTML attributes of the item's link. 299 | * - options: array, optional, the HTML attributes of the item container (LI). 300 | * - active: bool, optional, whether the item should be on active state or not. 301 | * - dropdownOptions: array, optional, the HTML options that will passed to the {@see Dropdown} widget. 302 | * - items: array|string, optional, the configuration array for creating a {@see Dropdown} widget, or a string 303 | * representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. 304 | * - encode: bool, optional, whether the label will be HTML-encoded. If set, supersedes the $encodeLabels option for 305 | * only this item. 306 | * 307 | * If a menu item is a string, it will be rendered directly without HTML encoding. 308 | * 309 | * @param array $value 310 | * 311 | * @return $this 312 | */ 313 | public function items(array $value): self 314 | { 315 | $this->items = $value; 316 | 317 | return $this; 318 | } 319 | 320 | /** 321 | * Whether the nav items labels should be HTML-encoded. 322 | * 323 | * @param bool $value 324 | * 325 | * @return $this 326 | */ 327 | public function encodeLabels(bool $value): self 328 | { 329 | $this->encodeLabels = $value; 330 | 331 | return $this; 332 | } 333 | 334 | public function label(string $value): self 335 | { 336 | $this->label = $value; 337 | 338 | return $this; 339 | } 340 | 341 | /** 342 | * Whether to automatically activate items according to whether their currentPath matches the currently requested. 343 | * 344 | * @param bool $value 345 | * 346 | * @return $this 347 | * 348 | * {@see isItemActive} 349 | */ 350 | public function activateItems(bool $value): self 351 | { 352 | $this->activateItems = $value; 353 | 354 | return $this; 355 | } 356 | 357 | /** 358 | * Whether to activate parent menu items when one of the corresponding child menu items is active. 359 | * 360 | * @param bool $value 361 | * 362 | * @return $this 363 | */ 364 | public function activateParents(bool $value): self 365 | { 366 | $this->activateParents = $value; 367 | 368 | return $this; 369 | } 370 | 371 | /** 372 | * Allows you to assign the current path of the url from request controller. 373 | * 374 | * @param string|null $value 375 | * 376 | * @return $this 377 | */ 378 | public function currentPath(?string $value): self 379 | { 380 | $this->currentPath = $value; 381 | 382 | return $this; 383 | } 384 | 385 | /** 386 | * The parameters used to determine if a menu item is active or not. If not set, it will use `$_GET`. 387 | * 388 | * @param array $value 389 | * 390 | * @return $this 391 | * 392 | * {@see currentPath} 393 | * {@see isItemActive} 394 | */ 395 | public function params(array $value): self 396 | { 397 | $this->params = $value; 398 | 399 | return $this; 400 | } 401 | 402 | /** 403 | * Name of a class to use for rendering dropdowns within this widget. Defaults to {@see Dropdown}. 404 | * 405 | * @param string $value 406 | * 407 | * @return $this 408 | */ 409 | public function dropdownClass(string $value): self 410 | { 411 | $this->dropdownClass = $value; 412 | 413 | return $this; 414 | } 415 | 416 | /** 417 | * The HTML attributes for the widget container tag. The following special options are recognized. 418 | * 419 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. 420 | * 421 | * @param array $value 422 | * 423 | * @return $this 424 | */ 425 | public function options(array $value): self 426 | { 427 | $this->options = $value; 428 | 429 | return $this; 430 | } 431 | } 432 | --------------------------------------------------------------------------------