├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── phpstan.neon.dist ├── resources └── views │ ├── child │ ├── dropdown.blade.php │ └── item.blade.php │ ├── default.blade.php │ ├── item │ ├── dropdown.blade.php │ └── item.blade.php │ ├── menu.blade.php │ ├── nav-pills-justified.blade.php │ ├── nav-pills-stacked.blade.php │ ├── nav-pills.blade.php │ ├── nav-tabs-justified.blade.php │ ├── nav-tabs.blade.php │ ├── navbar-left.blade.php │ └── navbar-right.blade.php └── src ├── Contracts └── PresenterContract.php ├── Facades └── Menu.php ├── Models ├── MenuGenerator.php ├── MenuItem.php └── MenuManager.php ├── Presenters ├── AdminltePresenter.php ├── BasePresenter.php ├── NavMenuPresenter.php ├── NavPillsPresenter.php ├── NavTabPresenter.php ├── NavbarPresenter.php ├── NavbarRightPresenter.php └── SidebarMenuPresenter.php └── Providers └── MenusServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Rinvex Menus Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](CONTRIBUTING.md). 6 | 7 | 8 | ## [v7.1.2] - 2023-07-03 9 | - Update composer dependencies 10 | - Use canonicalized absolute pathnames for resources 11 | 12 | ## [v7.1.1] - 2023-06-29 13 | - Refactor resource loading and publishing 14 | - Fix: use $item instead of $this (#103) 15 | 16 | ## [v7.1.0] - 2023-05-02 17 | - Add support for Laravel v11, and drop support for Laravel v9 18 | - Update phpunit to v10.1 from v9.5 19 | 20 | ## [v7.0.0] - 2023-01-09 21 | - Drop PHP v8.0 support and update composer dependencies 22 | 23 | ## [v6.2.1] - 2022-12-30 24 | - add feature to destroy menu item dropdown (#102) 25 | 26 | ## [v6.2.0] - 2022-05-17 27 | - Simplify menu divider since it doesn't have a link, so it doesn't need a link attributes 28 | - Rename attributes to linkAttributes to distinguish from itemAttributes 29 | - Add support for list item attributes 30 | 31 | ## [v6.1.0] - 2022-02-14 32 | - Update composer dependencies to Laravel v9 33 | - Use PHP v8 nullsafe operator 34 | 35 | ## [v6.0.0] - 2021-08-22 36 | - Drop PHP v7 support, and upgrade rinvex package dependencies to next major version 37 | - Update composer dependencies 38 | - Upgrade to GitHub-native Dependabot 39 | - Use app() method alias `has` instead of `bound` for better readability 40 | 41 | ## [v5.0.2] - 2021-04-27 42 | - Fix PHP v8.x compatibility issue 43 | 44 | ## [v5.0.1] - 2020-12-25 45 | - Add support for PHP v8 46 | 47 | ## [v5.0.0] - 2020-12-22 48 | - Upgrade to Laravel v8 49 | 50 | ## [v4.1.0] - 2020-06-15 51 | - Drop PHP 7.2 & 7.3 support from travis 52 | 53 | ## [v4.0.4] - 2020-05-30 54 | - Remove undefined $url variable 55 | - Allow specifying menu type when calling findByTitleOrAdd 56 | - Hide parent menu if it doesn't have any visible items 57 | - Add menu type to all items for easier identification 58 | - Fix hide logic for hiding parent items without visible children, only if type is dropdown or header 59 | - Remove default indent size config 60 | - Add support for interface based service binding 61 | 62 | ## [v4.0.3] - 2020-04-04 63 | - Fix namespace issue 64 | 65 | ## [v4.0.2] - 2020-04-04 66 | - Enforce consistent artisan command tag namespacing 67 | - Enforce consistent package namespace 68 | - Drop laravel/helpers usage as it's no longer used 69 | - Update orchestra/testbench package (fix #21) 70 | 71 | ## [v4.0.1] - 2020-03-15 72 | - Fix wrong package version laravelcollective/html 73 | 74 | ## [v4.0.0] - 2020-03-15 75 | - Upgrade to Laravel v7.1.x & PHP v7.4.x 76 | 77 | ## [v3.0.3] - 2020-03-13 78 | - Tweak TravisCI config 79 | - Add migrations autoload option to the package 80 | - Tweak service provider `publishesResources` 81 | - Remove indirect composer dependency 82 | - Drop using global helpers 83 | - Update StyleCI config 84 | 85 | ## [v3.0.2] - 2019-11-23 86 | - Add missing laravel/helpers composer package 87 | - Add missing composer dependency rinvex/laravel-support 88 | 89 | ## [v3.0.1] - 2019-09-24 90 | - Add missing laravel/helpers composer package 91 | 92 | ## [v3.0.0] - 2019-09-23 93 | - Upgrade to Laravel v6 and update dependencies 94 | 95 | ## [v2.1.0] - 2019-06-02 96 | - Update composer deps 97 | - Drop PHP 7.1 travis test 98 | 99 | ## [v2.0.0] - 2019-03-03 100 | - Require PHP 7.2 & Laravel 5.8 101 | - Apply PHPUnit 8 updates 102 | 103 | ## [v1.0.2] - 2019-01-03 104 | - Rename environment variable QUEUE_DRIVER to QUEUE_CONNECTION 105 | - Fix renderView return type 106 | 107 | ## [v1.0.1] - 2018-12-22 108 | - Update composer dependencies 109 | - Add PHP 7.3 support to travis 110 | 111 | ## [v1.0.0] - 2018-10-01 112 | - Enforce Consistency 113 | - Support Laravel 5.7+ 114 | - Rename package to rinvex/laravel-menus 115 | 116 | ## [v0.0.2] - 2018-09-22 117 | - Update travis php versions 118 | - Drop StyleCI multi-language support (paid feature now!) 119 | - Update composer dependencies 120 | - Prepare and tweak testing configuration 121 | - Update StyleCI options 122 | - Update PHPUnit options 123 | 124 | ## v0.0.1 - 2018-02-18 125 | - Tag first release 126 | 127 | [v7.1.2]: https://github.com/rinvex/laravel-menus/compare/v7.1.1...v7.1.2 128 | [v7.1.1]: https://github.com/rinvex/laravel-menus/compare/v7.1.0...v7.1.1 129 | [v7.1.0]: https://github.com/rinvex/laravel-menus/compare/v7.0.0...v7.1.0 130 | [v7.0.0]: https://github.com/rinvex/laravel-menus/compare/v6.2.1...v7.0.0 131 | [v6.2.1]: https://github.com/rinvex/laravel-menus/compare/v6.2.0...v6.2.1 132 | [v6.2.0]: https://github.com/rinvex/laravel-menus/compare/v6.1.0...v6.2.0 133 | [v6.1.0]: https://github.com/rinvex/laravel-menus/compare/v6.0.0...v6.1.0 134 | [v6.0.0]: https://github.com/rinvex/laravel-menus/compare/v5.0.2...v6.0.0 135 | [v5.0.2]: https://github.com/rinvex/laravel-menus/compare/v5.0.1...v5.0.2 136 | [v5.0.1]: https://github.com/rinvex/laravel-menus/compare/v5.0.0...v5.0.1 137 | [v5.0.0]: https://github.com/rinvex/laravel-menus/compare/v4.1.0...v5.0.0 138 | [v4.1.0]: https://github.com/rinvex/laravel-menus/compare/v4.0.4...v4.1.0 139 | [v4.0.4]: https://github.com/rinvex/laravel-menus/compare/v4.0.3...v4.0.4 140 | [v4.0.3]: https://github.com/rinvex/laravel-menus/compare/v4.0.2...v4.0.3 141 | [v4.0.2]: https://github.com/rinvex/laravel-menus/compare/v4.0.1...v4.0.2 142 | [v4.0.1]: https://github.com/rinvex/laravel-menus/compare/v4.0.0...v4.0.1 143 | [v4.0.0]: https://github.com/rinvex/laravel-menus/compare/v3.0.3...v4.0.0 144 | [v3.0.3]: https://github.com/rinvex/laravel-menus/compare/v3.0.2...v3.0.3 145 | [v3.0.2]: https://github.com/rinvex/laravel-menus/compare/v3.0.1...v3.0.2 146 | [v3.0.1]: https://github.com/rinvex/laravel-menus/compare/v3.0.0...v3.0.1 147 | [v3.0.0]: https://github.com/rinvex/laravel-menus/compare/v2.1.0...v3.0.0 148 | [v2.1.0]: https://github.com/rinvex/laravel-menus/compare/v2.0.0...v2.1.0 149 | [v2.0.0]: https://github.com/rinvex/laravel-menus/compare/v1.0.2...v2.0.0 150 | [v1.0.2]: https://github.com/rinvex/laravel-menus/compare/v1.0.1...v1.0.2 151 | [v1.0.1]: https://github.com/rinvex/laravel-menus/compare/v1.0.0...v1.0.1 152 | [v1.0.0]: https://github.com/rinvex/laravel-menus/compare/v0.0.2...v1.0.0 153 | [v0.0.2]: https://github.com/rinvex/laravel-menus/compare/v0.0.1...v0.0.2 154 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [help@rinvex.com](mailto:help@rinvex.com). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | This project adheres to the following standards and practices. 4 | 5 | 6 | ## Versioning 7 | 8 | This project is versioned under the [Semantic Versioning](http://semver.org/) guidelines as much as possible. 9 | 10 | Releases will be numbered with the following format: 11 | 12 | - `..` 13 | - `..` 14 | 15 | And constructed with the following guidelines: 16 | 17 | - Breaking backward compatibility bumps the major and resets the minor and patch. 18 | - New additions without breaking backward compatibility bump the minor and reset the patch. 19 | - Bug fixes and misc changes bump the patch. 20 | 21 | 22 | ## Pull Requests 23 | 24 | The pull request process differs for new features and bugs. 25 | 26 | Pull requests for bugs may be sent without creating any proposal issue. If you believe that you know of a solution for a bug that has been filed, please leave a comment detailing your proposed fix or create a pull request with the fix mentioning that issue id. 27 | 28 | 29 | ## Coding Standards 30 | 31 | This project follows the FIG PHP Standards Recommendations compliant with the [PSR-1: Basic Coding Standard](http://www.php-fig.org/psr/psr-1/), [PSR-2: Coding Style Guide](http://www.php-fig.org/psr/psr-2/) and [PSR-4: Autoloader](http://www.php-fig.org/psr/psr-4/) to ensure a high level of interoperability between shared PHP code. If you notice any compliance oversights, please send a patch via pull request. 32 | 33 | 34 | ## Feature Requests 35 | 36 | If you have a proposal or a feature request, you may create an issue with `[Proposal]` in the title. 37 | 38 | The proposal should also describe the new feature, as well as implementation ideas. The proposal will then be reviewed and either approved or denied. Once a proposal is approved, a pull request may be created implementing the new feature. 39 | 40 | 41 | ## Git Flow 42 | 43 | This project follows [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/), and as such has `master` (latest stable releases), `develop` (latest WIP development) and X.Y support branches (when there's multiple major versions). 44 | 45 | Accordingly all pull requests MUST be sent to the `develop` branch. 46 | 47 | > **Note:** Pull requests which do not follow these guidelines will be closed without any further notice. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2021, Rinvex LLC, 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rinvex Menus 2 | 3 | **Rinvex Menus** is a simple menu builder package for Laravel, that supports hierarchical structure, ordering, and styling with full flexibility using presenters for easy styling and custom structure of menu rendering. 4 | 5 | [![Packagist](https://img.shields.io/packagist/v/rinvex/laravel-menus.svg?label=Packagist&style=flat-square)](https://packagist.org/packages/rinvex/laravel-menus) 6 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/rinvex/laravel-menus.svg?label=Scrutinizer&style=flat-square)](https://scrutinizer-ci.com/g/rinvex/laravel-menus/) 7 | [![Travis](https://img.shields.io/travis/rinvex/laravel-menus.svg?label=TravisCI&style=flat-square)](https://travis-ci.org/rinvex/laravel-menus) 8 | [![StyleCI](https://styleci.io/repos/114586319/shield)](https://styleci.io/repos/114586319) 9 | [![License](https://img.shields.io/packagist/l/rinvex/laravel-menus.svg?label=License&style=flat-square)](https://github.com/rinvex/laravel-menus/blob/develop/LICENSE) 10 | 11 | 12 | ## Credits notice 13 | 14 | This package is a rewritten fork of [nWidart/laravel-menus](https://github.com/nWidart/laravel-menus), which itself is a fork of [pingpong-labs/menus](https://github.com/pingpong-labs/menus), original credits goes to them both. It's been widely rewritten to drop technical debt and remove legacy code, so be aware that the API is different and not compatible with the original package(s) for good. The main goals behind this fork was to: 15 | 16 | - Simplify menu registration 17 | - Clean code and enhance readability 18 | - Enable sorting order feature by default 19 | - Remove legacy code, and drop technical debt 20 | - Allow extensibility with minimum or no core changes 21 | - Enforce consistency and straighten API to be intuitive 22 | - New sidebar menu feature, to treat dropdowns differently 23 | - Integrate with Laravel [Authentication](https://laravel.com/docs/master/authentication) and [Authorization](https://laravel.com/docs/master/authorization) features to streamline hiding/displaying menus according to permissions 24 | 25 | 26 | ## Installation 27 | 28 | 1. Install the package via composer: 29 | ```shell 30 | composer require rinvex/laravel-menus 31 | ``` 32 | 33 | 2. **Optionally** you can publish view files by running the following commands: 34 | ```shell 35 | php artisan vendor:publish --tag="rinvex-menus-views" 36 | ``` 37 | 38 | 3. Done! 39 | 40 | 41 | ## Usage 42 | 43 | ### Create new menu 44 | 45 | To register a new menu, simply call `Menu::register()` method. It takes two parameters, the first one is the menu title and the second one is a callback for defining menu items. See the following example: 46 | 47 | ```php 48 | use Rinvex\Menus\Models\MenuItem; 49 | use Rinvex\Menus\Models\MenuGenerator; 50 | 51 | Menu::register('frontend.sidebar', function(MenuGenerator $menu) { 52 | // Add menu header 53 | $menu->header('Header Title'); 54 | 55 | // Add url menu item 56 | $menu->url('url/path', 'Menu Title #1'); 57 | 58 | // Add route menu item 59 | $menu->route(['route.name'], 'Menu Title #2'); 60 | 61 | // Add menu divider 62 | $menu->divider(); 63 | 64 | // Add menu dropdown (it can have childs too) 65 | $menu->dropdown(function(MenuItem $dropdown) { 66 | $dropdown->header('Child Header Title'); 67 | $dropdown->url('url/path', 'Child Menu Title #1'); 68 | $dropdown->route(['route.name'], 'Child Menu Title #2'); 69 | $dropdown->divider(); 70 | }, 'Dropdown Title', 50, 'fa fa-arrows', ['data-attribute' => 'something']); 71 | }); 72 | ``` 73 | 74 | All the `url`, `route`, `header`, and `dropdown` methods has a standard API like: `$menu->method('data', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')` that's intutive and self explanatory. Only the first parameter is mandatory and different for each method, but the rest are all the same and optional. `header` accepts string title, `url`: string link, `route`: array with string route name and optionally route parameters, `dropdown`: callback for child items definition, other parameters are optional. 75 | 76 | > **Notes:** 77 | > - Menu items are ordered in ascending order by default. If you don't need sorting, just ignore the `order` parameter when defining your menus as it's optional anyway. That way menu items will be displayed in the order they've been added. 78 | > - The `icon` parameter takes a css class name, like `fa fa-user` for fontawesome, and the `linkAttributes` parameter takes array of any additional HTML attributes you would like to add to your menu item. 79 | > - You can create a multi-level menu items by creating child dropdown menus inside parent dropdown menus, and it has no limit, so you can create the structure you need as deep as you want. 80 | > - You can create multiple menus with different names using the `Menu::register()` method, and call them in different places. Like if you want a topbar menu, and a sidebar menu ..etc 81 | 82 | 83 | ### Modify existing menu 84 | 85 | To modify an existing menu item that's already been added somewhere else in the code you can use the same registration method: 86 | 87 | ```php 88 | Menu::register('frontend.sidebar', function(MenuGenerator $menu) { 89 | // Add url menu item above the dropdown we created before 90 | $menu->url('different/path', 'Menu Title #3', 40); 91 | }); 92 | ``` 93 | 94 | As you can see, we just modified the `frontend.sidebar` menu, and added a new url menu item under the divider, above the dropdown. See, it's that simple! 95 | 96 | Alternatively you can get a handle of the menu you need to modify, and then use it as you prefer, like so: 97 | 98 | ```php 99 | $sidebar = Menu::instance('frontend.sidebar'); 100 | $sidebar->url('new/url', 'Menu Title #4', 40); 101 | $sidebar->route('some.new.route', 'Menu Title #5', 60); 102 | ``` 103 | 104 | #### Hide menus conditionally 105 | 106 | To simply hide any of your menu items, you can use any of the following methods: 107 | 108 | ```php 109 | $sidebar->url('one/more/url', 'One more new item')->hideWhen(function () { 110 | return true; // Any expression 111 | }); 112 | ``` 113 | 114 | As you can see, the `hideWhen` method takes a closure that returns true or false. If true returned the menu item will be hidden, otherwise it will be displayed, so you can put whatever logic here to be evaluated. 115 | 116 | And as a syntactic sugar, there's few more methods that makes life easier! See the `ifUser`, `ifGuest`, and `ifCan` methods: 117 | 118 | ```php 119 | // Only display if logged condition is true 120 | $sidebar->url('one/more/url', 'One more new item')->if(true); 121 | 122 | // Only display if logged in user (authenticated) 123 | $sidebar->url('one/more/url', 'One more new item')->ifUser(); 124 | 125 | // Only display if guest not yet authenticated 126 | $sidebar->url('one/more/url', 'One more new item')->ifGuest(); 127 | 128 | // Only display if logged in user has required ability (authorization) 129 | $sidebar->url('one/more/url', 'One more new item')->ifCan('do-some-ability'); 130 | ``` 131 | 132 | Sure, as you expected all these methods works smoothly and fully integrated with Laravel's default [Authentication](https://laravel.com/docs/master/authentication) and [Authorization](https://laravel.com/docs/master/authorization) features. 133 | 134 | To make it easy to control menu hide states, you can chain all hide methods infinitely and all hide callbacks will be stacked and executed in order. It will stop execution with the first positive condition result. Example: 135 | 136 | ```php 137 | // Only display if logged in user has required ability (authorization) 138 | $sidebar->url('one/more/url', 'One more new item')->ifUser()->ifCan('do-some-ability')->hideWhen(function () { 139 | return true; // Any expression 140 | }); 141 | ``` 142 | 143 | This example means that menu will only displayed for users, who has `do-some-ability` permission, and also when the `hideWhen` callback expression returns true. 144 | 145 | #### Activate menus conditionally 146 | 147 | To activate menus conditionally based on route name, you can set the route prefix to match against. If the current route name contains that prefix, then the menu item will be activated automatically. That way we can activate parent menu items by accessing child pages. Example: 148 | 149 | ```php 150 | $menu->route(['route.name.example'], 'Menu Title #2')->activateOnRoute('route.name'); 151 | ``` 152 | 153 | Now when we access any route prefixed by `route.name`, our menu with the `route.name.example` route will be activated automatically. 154 | 155 | Alternatively, you can fully control when that menu item is beeing activated by adding your own logic within callback that resolve to boolean, as follows: 156 | 157 | ```php 158 | $menu->route(['route.name.example'], 'Menu Title #2')->activateWhen(function () { 159 | return true; // Any expression 160 | }); 161 | ``` 162 | 163 | ### Search for existing menu item 164 | 165 | You can also search for a specific menu item, a dropdown for example using `findBy` and add child items to it directly. The `findBy` method can search inside your menus by any attribute and take two required parameters, the attribute name & value to search by, and optionaly you can pass a third parameter as a callback to define child items (a way to modify the dropdown in one go). 166 | 167 | ```php 168 | Menu::register('frontend.sidebar', function(MenuGenerator $menu) { 169 | $menu->findBy('title', 'Dropdown Title', function (MenuItem $dropdown) { // Seach items by title 170 | $dropdown->route(['the.newest.route'], 'Yet another menu item', 15, 'fa fa-building-o'); 171 | }); 172 | }); 173 | ``` 174 | 175 | If you need to update a specific menu item, or for example you need to change the url or rename the title, that's totally achievable too using the same method above with one simple tweak: 176 | 177 | ```php 178 | Menu::register('frontend.sidebar', function(MenuGenerator $menu) { 179 | $menu->findBy('title', 'Yet another menu item', function (MenuItem $item) { 180 | $item->fill(['icon' => 'fa fa-business]); 181 | }); 182 | }); 183 | ``` 184 | 185 | This code search for a menu item titled 'Yet another menu item' and update only it's icon. The `fill` method accepts an array with any properties you'd like to update, and merge it with the originals, resulting an overridden menu item definition. 186 | 187 | 188 | ### Menu presenters 189 | 190 | Rendering menus is the easiest part, but let's discover first few interesting concepts utilized by this package, Presenters! 191 | 192 | Presenters are like layout drivers that defines the way your menus are rendered, and it could be different for each and every menu. Let's make it simple by explained example, if you have multiple sections in your project that uses different CSS frameworks, like vanilla bootstrap and AdminLTE, you can create two different presenters for both (fortunately these two already built in out-of-the-box, but you can build your own for any other framework). The way it work is by creating the presenter, register it with the package, and just use it's name. So in short, your menu definition never change even if you changed the whole layout or even shifted to another CSS framework, you just need to change your presenter. 193 | 194 | Presenters are used also to define the different layouts for your menu structure, that way you can use menus to build navbars, dropdowns, or even tabs. It's all yours and the same code. Just hook your presenter and you're ready to go. By default there's few presenters built in for you out-of-the-box: 195 | 196 | - `navbar` \Rinvex\Menus\Presenters\NavbarPresenter 197 | - `navbar-right` \Rinvex\Menus\Presenters\NavbarRightPresenter 198 | - `nav-pills` \Rinvex\Menus\Presenters\NavPillsPresenter 199 | - `nav-tab` \Rinvex\Menus\Presenters\NavTabPresenter 200 | - `sidebar` \Rinvex\Menus\Presenters\SidebarMenuPresenter 201 | - `navmenu` \Rinvex\Menus\Presenters\NavMenuPresenter 202 | - `adminlte` \Rinvex\Menus\Presenters\AdminltePresenter 203 | 204 | All are based on Bootstrap except for AdminLTE, but you can build your own. You always use the alias, not the full class path. 205 | 206 | #### Create new presenter 207 | 208 | To build your own presenter you need to: 209 | 210 | - Create a new PHP class that implements `\Rinvex\Menus\Contracts\PresenterContract`. 211 | - Register your presenter with the package: `app('rinvex.menus.presenters')->put('new-presenter', \Your\New\Presenter\ClassPresenter::class)` 212 | 213 | That's it, your new presenter is ready to be used by it's name `new-presenter`. See `Rinvex\Menus\Presenters\AdminltePresenter` source code for real example. 214 | 215 | #### View presenters 216 | 217 | In addition to the class-based presenters explained above, you can use view-based presenters as well. Fortunately there's also built in bootstrap views to be used and you can create your own too. 218 | 219 | There's nothing complex here to be explained, just think of view presenters as normal Laravel views, because it is really are, nothing special. The only difference is when you render the menus, you can set your prefered presenter. View-based presenters has precedence over class-based presenters if both supplied, but if none supplied it will fallback to the default class-based built-in presenters. By default there's few view-based presenters built in for you out-of-the-box: 220 | 221 | - `rinvex/menus::menu` Plain Menu 222 | - `rinvex/menus::default` Bootstrap Navbar (default) 223 | - `rinvex/menus::navbar-left` Bootstrap Navbar Left 224 | - `rinvex/menus::navbar-right` Bootstrap Navbar Right 225 | - `rinvex/menus::nav-tabs` Bootstrap Nav Tabs 226 | - `rinvex/menus::nav-tabs-justified` Bootstrap Nav Tabs Justified 227 | - `rinvex/menus::nav-pills` Bootstrap Nav Pills 228 | - `rinvex/menus::nav-pills-stacked` Bootstrap Nav Pills Stacked 229 | - `rinvex/menus::nav-pills-justified` Bootstrap Nav Pills Justified 230 | 231 | 232 | ### Render existing menu 233 | 234 | To render a menu you can use the `Menu::render()` method as follows: 235 | 236 | ```php 237 | Menu::render('frontend.sidebar'); 238 | ``` 239 | 240 | As you will see in the method definition, there's three more optional parameters to be explained: `public function render(string $name, string $presenter = null, array $bindings = [], bool $specialSidebar = false)`. The `presenter` parameter specify how the menu is being rendered, the `bindings` is a simple way to search and replace title placeholders (more on this below), and the `specialSidebar` is a flag to treat sidebar dropdowns differently by displaying headers above each group instead of collapsible dropdowns (beta feature). 241 | 242 | #### Data binding 243 | 244 | When you define a new menu, you can put placeholders in titles, and then when rendering you can pass bindings to be replaced at runtime. Interesting, right? See the following example: 245 | 246 | ```php 247 | // Define new menu item with title placeholder 248 | $sidebar = Menu::instance('frontend.sidebar'); 249 | $sidebar->url('very/new/url', 'Welcome {user}'); 250 | 251 | // Render menu and bind data on runtime 252 | Menu::render('frontend.sidebar', null, ['user' => 'Omran']); 253 | ``` 254 | 255 | As you can see we defined a new menu item with a `{user}` placeholder, and when we rendered the menu we passed the required data to be bound. It will do search/replace on runtime and so you can pass any dynamic data within menu item titles. 256 | 257 | #### Change default presenter 258 | 259 | You can change default presenters either on menu definition or on menu rendering step, but it's always prefered to do so on runtime rendering to have a stable unchanged menu structure, while keeping layout related changes like presenters on the frontend layer. 260 | 261 | Here's how to change presenters both ways: 262 | 263 | ```php 264 | // Change menu presenter on definition 265 | $sidebar = Menu::instance('frontend.sidebar'); 266 | $sidebar->setView('view-name'); // Set view-based presenter 267 | $sidebar->setPresenter('presenter-name'); // Set class-based presenter 268 | 269 | // Change menu presenter on rendering 270 | Menu::render('frontend.sidebar', 'view-name'); // Set view-based presenter 271 | Menu::render('frontend.sidebar', 'presenter-name'); // Set class-based presenter 272 | ``` 273 | 274 | You don't need to worry about how this package works and how does it know whether the supplied presenter is view-based or class-based, but keep in mind that view-based presenters has precedence over class-based presenters, so this package will search for existing view-presenter with the supplied name, if found it will be used and returned immediately, otherwise it will search secondly for class-based presenters with the supplied name. 275 | 276 | 277 | ## Changelog 278 | 279 | Refer to the [Changelog](CHANGELOG.md) for a full history of the project. 280 | 281 | 282 | ## Support 283 | 284 | The following support channels are available at your fingertips: 285 | 286 | - [Chat on Slack](https://bit.ly/rinvex-slack) 287 | - [Help on Email](mailto:help@rinvex.com) 288 | - [Follow on Twitter](https://twitter.com/rinvex) 289 | 290 | 291 | ## Contributing & Protocols 292 | 293 | Thank you for considering contributing to this project! The contribution guide can be found in [CONTRIBUTING.md](CONTRIBUTING.md). 294 | 295 | Bug reports, feature requests, and pull requests are very welcome. 296 | 297 | - [Versioning](CONTRIBUTING.md#versioning) 298 | - [Pull Requests](CONTRIBUTING.md#pull-requests) 299 | - [Coding Standards](CONTRIBUTING.md#coding-standards) 300 | - [Feature Requests](CONTRIBUTING.md#feature-requests) 301 | - [Git Flow](CONTRIBUTING.md#git-flow) 302 | 303 | 304 | ## Security Vulnerabilities 305 | 306 | If you discover a security vulnerability within this project, please send an e-mail to [help@rinvex.com](help@rinvex.com). All security vulnerabilities will be promptly addressed. 307 | 308 | 309 | ## About Rinvex 310 | 311 | Rinvex is a software solutions startup, specialized in integrated enterprise solutions for SMEs established in Alexandria, Egypt since June 2016. We believe that our drive The Value, The Reach, and The Impact is what differentiates us and unleash the endless possibilities of our philosophy through the power of software. We like to call it Innovation At The Speed Of Life. That’s how we do our share of advancing humanity. 312 | 313 | 314 | ## License 315 | 316 | This software is released under [The MIT License (MIT)](LICENSE). 317 | 318 | (c) 2016-2022 Rinvex LLC, Some rights reserved. 319 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rinvex/laravel-menus", 3 | "description": "Rinvex Menus is a simple menu builder package for Laravel, that supports hierarchical structure, ordering, and styling with full flexibility using presenters for easy styling and custom structure of menu rendering.", 4 | "type": "library", 5 | "keywords": [ 6 | "nav", 7 | "menus", 8 | "laravel", 9 | "navigation", 10 | "bootstrap", 11 | "hierarchy", 12 | "rinvex" 13 | ], 14 | "license": "MIT", 15 | "homepage": "https://rinvex.com", 16 | "support": { 17 | "email": "help@rinvex.com", 18 | "issues": "https://github.com/rinvex/laravel-menus/issues", 19 | "source": "https://github.com/rinvex/laravel-menus", 20 | "docs": "https://github.com/rinvex/laravel-menus/blob/master/README.md" 21 | }, 22 | "authors": [ 23 | { 24 | "name": "Rinvex LLC", 25 | "homepage": "https://rinvex.com", 26 | "email": "help@rinvex.com" 27 | }, 28 | { 29 | "name": "Abdelrahman Omran", 30 | "homepage": "https://omranic.com", 31 | "email": "me@omranic.com", 32 | "role": "Project Lead" 33 | }, 34 | { 35 | "name": "Nicolas Widart", 36 | "email": "n.widart@gmail.com" 37 | }, 38 | { 39 | "name": "Pingpong Labs", 40 | "email": "pingpong.labs@gmail.com" 41 | }, 42 | { 43 | "name": "The Generous Laravel Community", 44 | "homepage": "https://github.com/rinvex/laravel-menus/contributors" 45 | } 46 | ], 47 | "require": { 48 | "php": "^8.1.0", 49 | "illuminate/contracts": "^10.0.0 || ^11.0.0", 50 | "illuminate/routing": "^10.0.0 || ^11.0.0", 51 | "illuminate/support": "^10.0.0 || ^11.0.0", 52 | "illuminate/view": "^10.0.0 || ^11.0.0", 53 | "laravelcollective/html": "^6.3.0", 54 | "rinvex/laravel-support": "^7.0.0" 55 | }, 56 | "require-dev": { 57 | "codedungeon/phpunit-result-printer": "^0.32.0", 58 | "mockery/mockery": "^1.6.0", 59 | "orchestra/testbench": "^8.0.0", 60 | "phpunit/phpunit": "^10.1.0" 61 | }, 62 | "autoload": { 63 | "psr-4": { 64 | "Rinvex\\Menus\\": "src/" 65 | } 66 | }, 67 | "autoload-dev": { 68 | "psr-4": { 69 | "Rinvex\\Menus\\Tests\\": "tests" 70 | } 71 | }, 72 | "scripts": { 73 | "test": "vendor/bin/phpunit" 74 | }, 75 | "config": { 76 | "sort-packages": true, 77 | "preferred-install": "dist", 78 | "optimize-autoloader": true 79 | }, 80 | "extra": { 81 | "laravel": { 82 | "providers": [ 83 | "Rinvex\\Menus\\Providers\\MenusServiceProvider" 84 | ], 85 | "aliases": { 86 | "Menu": "Rinvex\\Menus\\Facades\\Menu" 87 | } 88 | } 89 | }, 90 | "minimum-stability": "dev", 91 | "prefer-stable": true 92 | } 93 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | parameters: 4 | level: 5 5 | paths: 6 | - src 7 | -------------------------------------------------------------------------------- /resources/views/child/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /resources/views/child/item.blade.php: -------------------------------------------------------------------------------- 1 | @if ($item->isDivider()) 2 |
  • 3 | @elseif ($item->isHeader()) 4 | 5 | @else 6 |
  • 7 | 8 | {{ $item->title }} 9 | 10 |
  • 11 | @endif 12 | -------------------------------------------------------------------------------- /resources/views/default.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/item/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | @if($specialSidebar) 2 | 3 | @foreach ($item->childs as $child) 4 | @if ($child->hasChilds()) 5 | @include('rinvex/menus::child.dropdown', ['item' => $child]) 6 | @else 7 | @include('rinvex/menus::item.item', ['item' => $child]) 8 | @endif 9 | @endforeach 10 | @else 11 | 26 | @endif 27 | -------------------------------------------------------------------------------- /resources/views/item/item.blade.php: -------------------------------------------------------------------------------- 1 | @if ($item->isDivider()) 2 |
  • 3 | @elseif ($item->isHeader()) 4 | 5 | @else 6 |
  • getItemAttributes() }}> 7 | getLinkAttributes() !!}> 8 | @if ($item->icon)@endif 9 | {{ $item->title }} 10 | 11 |
  • 12 | @endif 13 | -------------------------------------------------------------------------------- /resources/views/menu.blade.php: -------------------------------------------------------------------------------- 1 | @foreach ($items as $item) 2 | @if ($item->hasChilds()) 3 | @include('rinvex/menus::item.dropdown', compact('item', $specialSidebar)) 4 | @else 5 | @include('rinvex/menus::item.item', compact('item')) 6 | @endif 7 | @endforeach 8 | -------------------------------------------------------------------------------- /resources/views/nav-pills-justified.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/nav-pills-stacked.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/nav-pills.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/nav-tabs-justified.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/nav-tabs.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/navbar-left.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/navbar-right.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Contracts/PresenterContract.php: -------------------------------------------------------------------------------- 1 | items = collect(); 64 | } 65 | 66 | /** 67 | * Find menu item by given key and value. 68 | * 69 | * @param string $key 70 | * @param string $value 71 | * @param callable $callback 72 | * 73 | * @return \Rinvex\Menus\Models\MenuItem|null 74 | */ 75 | public function findBy(string $key, string $value, callable $callback = null): ?MenuItem 76 | { 77 | $item = $this->items->filter(function ($item) use ($key, $value) { 78 | return $item->{$key} === $value; 79 | })->first(); 80 | 81 | (! is_callable($callback) || ! $item) || call_user_func($callback, $item); 82 | 83 | return $item; 84 | } 85 | 86 | /** 87 | * Find menu item by given key and value. 88 | * 89 | * @param string $title 90 | * @param int|null $order 91 | * @param string|null $icon 92 | * @param string|null $type 93 | * @param array $linkAttributes 94 | * @param array $itemAttributes 95 | * @param callable|null $callback 96 | * 97 | * @return \Rinvex\Menus\Models\MenuItem|null 98 | */ 99 | public function findByTitleOrAdd(string $title, int $order = null, string $icon = null, string $type = null, array $linkAttributes = [], array $itemAttributes = [], callable $callback = null): ?MenuItem 100 | { 101 | if (! ($item = $this->findBy('title', $title, $callback))) { 102 | $item = $this->add(compact('type', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')); 103 | ! is_callable($callback) || call_user_func($callback, $item); 104 | } 105 | 106 | return $item; 107 | } 108 | 109 | /** 110 | * Set view factory instance. 111 | * 112 | * @param \Illuminate\View\Factory $views 113 | * 114 | * @return $this 115 | */ 116 | public function setViewFactory(ViewFactory $views) 117 | { 118 | $this->views = $views; 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Set view. 125 | * 126 | * @param string $view 127 | * 128 | * @return $this 129 | */ 130 | public function setView(string $view) 131 | { 132 | $this->view = $view; 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * Set Prefix URL. 139 | * 140 | * @param string $prefixUrl 141 | * 142 | * @return $this 143 | */ 144 | public function setUrlPrefix(string $urlPrefix) 145 | { 146 | $this->urlPrefix = $urlPrefix; 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * Set new presenter class. 153 | * 154 | * @param string $presenter 155 | * 156 | * @return $this 157 | */ 158 | public function setPresenter(string $presenter) 159 | { 160 | $this->presenter = app('rinvex.menus.presenters')->get($presenter); 161 | 162 | return $this; 163 | } 164 | 165 | /** 166 | * Get presenter instance. 167 | * 168 | * @return \Rinvex\Menus\Contracts\PresenterContract 169 | */ 170 | public function getPresenter(): PresenterContract 171 | { 172 | return new $this->presenter(); 173 | } 174 | 175 | /** 176 | * Determine if the given name in the presenter style. 177 | * 178 | * @param string $presenter 179 | * 180 | * @return bool 181 | */ 182 | public function presenterExists(string $presenter): bool 183 | { 184 | return app('rinvex.menus.presenters')->has($presenter); 185 | } 186 | 187 | /** 188 | * Set the resolved item bindings. 189 | * 190 | * @param array $bindings 191 | * 192 | * @return $this 193 | */ 194 | public function setBindings(array $bindings) 195 | { 196 | $this->bindings = $bindings; 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * Resolves a key from the bindings array. 203 | * 204 | * @param string|array $key 205 | * 206 | * @return mixed 207 | */ 208 | public function resolve($key) 209 | { 210 | if (is_array($key)) { 211 | foreach ($key as $k => $v) { 212 | $key[$k] = $this->resolve($v); 213 | } 214 | } elseif (is_string($key)) { 215 | $matches = []; 216 | 217 | // Search for any {placeholders} and replace with their replacement values 218 | preg_match_all('/{[\s]*?([^\s]+)[\s]*?}/i', $key, $matches, PREG_SET_ORDER); 219 | 220 | foreach ($matches as $match) { 221 | if (array_key_exists($match[1], $this->bindings)) { 222 | $key = preg_replace('/'.$match[0].'/', $this->bindings[$match[1]], $key, 1); 223 | } 224 | } 225 | } 226 | 227 | return $key; 228 | } 229 | 230 | /** 231 | * Resolves an array of menu items properties. 232 | * 233 | * @param \Illuminate\Support\Collection &$items 234 | * 235 | * @return void 236 | */ 237 | protected function resolveItems(Collection &$items): void 238 | { 239 | $resolver = function ($property) { 240 | return $this->resolve($property) ?: $property; 241 | }; 242 | 243 | $items->each(function (MenuItem $item) use ($resolver) { 244 | $item->fill(array_map($resolver, $item->properties)); 245 | }); 246 | } 247 | 248 | /** 249 | * Add new child menu. 250 | * 251 | * @param array $properties 252 | * 253 | * @return \Rinvex\Menus\Models\MenuItem 254 | */ 255 | protected function add(array $properties = []): MenuItem 256 | { 257 | $properties['linkAttributes']['id'] = $properties['linkAttributes']['id'] ?? md5(json_encode($properties)); 258 | $this->items->push($item = new MenuItem($properties)); 259 | 260 | return $item; 261 | } 262 | 263 | /** 264 | * Create new menu with dropdown. 265 | * 266 | * @param callable $callback 267 | * @param string $title 268 | * @param int|null $order 269 | * @param string|null $icon 270 | * @param array $linkAttributes 271 | * @param array $itemAttributes 272 | * 273 | * @return \Rinvex\Menus\Models\MenuItem 274 | */ 275 | public function dropdown(callable $callback, string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []): MenuItem 276 | { 277 | $type = 'dropdown'; 278 | 279 | call_user_func($callback, $item = $this->add(compact('type', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes'))); 280 | 281 | return $item; 282 | } 283 | 284 | /** 285 | * Register new menu item using registered route. 286 | * 287 | * @param array $route 288 | * @param string $title 289 | * @param int|null $order 290 | * @param string|null $icon 291 | * @param array $linkAttributes 292 | * @param array $itemAttributes 293 | * 294 | * @return \Rinvex\Menus\Models\MenuItem 295 | */ 296 | public function route(array $route, string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []): MenuItem 297 | { 298 | $type = 'route'; 299 | 300 | return $this->add(compact('type', 'route', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')); 301 | } 302 | 303 | /** 304 | * Register new menu item using url. 305 | * 306 | * @param string $url 307 | * @param string $title 308 | * @param int|null $order 309 | * @param string|null $icon 310 | * @param array $linkAttributes 311 | * @param array $itemAttributes 312 | * 313 | * @return \Rinvex\Menus\Models\MenuItem 314 | */ 315 | public function url(string $url, string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []): MenuItem 316 | { 317 | $type = 'url'; 318 | ! $this->urlPrefix || $url = $this->formatUrl($url); 319 | 320 | return $this->add(compact('type', 'url', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')); 321 | } 322 | 323 | /** 324 | * Add new header item. 325 | * 326 | * @param string $title 327 | * @param int|null $order 328 | * @param string|null $icon 329 | * @param array $linkAttributes 330 | * @param array $itemAttributes 331 | * 332 | * @return \Rinvex\Menus\Models\MenuItem 333 | */ 334 | public function header(string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []): MenuItem 335 | { 336 | $type = 'header'; 337 | 338 | return $this->add(compact('type', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')); 339 | } 340 | 341 | /** 342 | * Add new divider item. 343 | * 344 | * @param int|null $order 345 | * @param array $itemAttributes 346 | * 347 | * @return \Rinvex\Menus\Models\MenuItem 348 | */ 349 | public function divider(int $order = null, array $itemAttributes = []): MenuItem 350 | { 351 | $type = 'divider'; 352 | 353 | return $this->add(compact('type', 'order', 'itemAttributes')); 354 | } 355 | 356 | /** 357 | * Get items count. 358 | * 359 | * @return int 360 | */ 361 | public function count(): int 362 | { 363 | return $this->items->count(); 364 | } 365 | 366 | /** 367 | * Empty the current menu items. 368 | * 369 | * @return $this 370 | */ 371 | public function destroy() 372 | { 373 | $this->items = collect(); 374 | 375 | return $this; 376 | } 377 | 378 | /** 379 | * Get menu items and order it by 'order' key. 380 | * 381 | * @return \Illuminate\Support\Collection 382 | */ 383 | protected function getOrderedItems(): Collection 384 | { 385 | return $this->items->sortBy('properties.order')->each(function (MenuItem $parent) { 386 | $parent->hideWhen(function () use ($parent) { 387 | return in_array($parent->properties['type'], ['dropdown', 'header']) && ! $parent->getChilds()->reduce(function ($carry, MenuItem $child) { 388 | return $carry || ! $child->isHidden(); 389 | }, false); 390 | }); 391 | }); 392 | } 393 | 394 | /** 395 | * Render the menu to HTML tag. 396 | * 397 | * @param string|null $presenter 398 | * @param bool $specialSidebar 399 | * 400 | * @return string 401 | */ 402 | public function render(string $presenter = null, bool $specialSidebar = false): string 403 | { 404 | $this->resolveItems($this->items); 405 | 406 | if (! is_null($this->view)) { 407 | return $this->renderView($presenter, $specialSidebar)->render(); 408 | } 409 | 410 | (! $presenter || ! $this->presenterExists($presenter)) || $this->setPresenter($presenter); 411 | 412 | return $this->renderMenu($specialSidebar); 413 | } 414 | 415 | /** 416 | * Render menu via view presenter. 417 | * 418 | * @param string $view 419 | * @param bool $specialSidebar 420 | * 421 | * @return \Illuminate\Contracts\View\View 422 | */ 423 | protected function renderView(string $view, bool $specialSidebar = false): ViewContract 424 | { 425 | return $this->views->make($view, ['items' => $this->getOrderedItems(), 'specialSidebar' => $specialSidebar]); 426 | } 427 | 428 | /** 429 | * Render the menu. 430 | * 431 | * @param bool $specialSidebar 432 | * 433 | * @return string 434 | */ 435 | protected function renderMenu(bool $specialSidebar = false): string 436 | { 437 | $presenter = $this->getPresenter(); 438 | $menu = $presenter->getOpenTagWrapper(); 439 | 440 | foreach ($this->getOrderedItems() as $item) { 441 | if ($item->isHidden()) { 442 | continue; 443 | } 444 | 445 | if ($item->hasChilds()) { 446 | $menu .= $presenter->getMenuWithDropDownWrapper($item, $specialSidebar); 447 | } elseif ($item->isHeader()) { 448 | $menu .= $presenter->getHeaderWrapper($item); 449 | } elseif ($item->isDivider()) { 450 | $menu .= $presenter->getDividerWrapper(); 451 | } else { 452 | $menu .= $presenter->getMenuWithoutDropdownWrapper($item); 453 | } 454 | } 455 | 456 | $menu .= $presenter->getCloseTagWrapper(); 457 | 458 | return $menu; 459 | } 460 | 461 | /** 462 | * Format URL. 463 | * 464 | * @param string $url 465 | * 466 | * @return string 467 | */ 468 | protected function formatUrl(string $url): string 469 | { 470 | $uri = $this->urlPrefix.$url; 471 | 472 | return $uri === '/' ? '/' : ltrim(rtrim($uri, '/'), '/'); 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /src/Models/MenuItem.php: -------------------------------------------------------------------------------- 1 | fill($properties); 51 | 52 | $this->hideCallbacks = collect(); 53 | $this->childs = collect(); 54 | } 55 | 56 | /** 57 | * Get property. 58 | * 59 | * @param string $key 60 | * 61 | * @return mixed 62 | */ 63 | public function __get($key) 64 | { 65 | return data_get($this->properties, $key); 66 | } 67 | 68 | /** 69 | * Fill the properties. 70 | * 71 | * @param array $properties 72 | * 73 | * @return static 74 | */ 75 | public function fill($properties) 76 | { 77 | $this->properties = array_merge($this->properties, $properties); 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Add new child item. 84 | * 85 | * @param array $properties 86 | * 87 | * @return static 88 | */ 89 | protected function add(array $properties = []) 90 | { 91 | $properties['linkAttributes']['id'] = $properties['linkAttributes']['id'] ?? md5(json_encode($properties)); 92 | $this->childs->push($item = new static($properties)); 93 | 94 | return $item; 95 | } 96 | 97 | /** 98 | * Create new menu with dropdown. 99 | * 100 | * @param callable $callback 101 | * @param string $title 102 | * @param int|null $order 103 | * @param string|null $icon 104 | * @param array $linkAttributes 105 | * @param array $itemAttributes 106 | * 107 | * @return static 108 | */ 109 | public function dropdown(callable $callback, string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []) 110 | { 111 | $type = 'dropdown'; 112 | 113 | call_user_func($callback, $item = $this->add(compact('type', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes'))); 114 | 115 | return $item; 116 | } 117 | 118 | /** 119 | * Register new menu item using registered route. 120 | * 121 | * @param array $route 122 | * @param string $title 123 | * @param int|null $order 124 | * @param string|null $icon 125 | * @param array $linkAttributes 126 | * @param array $itemAttributes 127 | * 128 | * @return static 129 | */ 130 | public function route(array $route, string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []) 131 | { 132 | $type = 'route'; 133 | 134 | return $this->add(compact('type', 'route', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')); 135 | } 136 | 137 | /** 138 | * Register new menu item using url. 139 | * 140 | * @param string $url 141 | * @param string $title 142 | * @param int|null $order 143 | * @param string|null $icon 144 | * @param array $linkAttributes 145 | * @param array $itemAttributes 146 | * 147 | * @return static 148 | */ 149 | public function url(string $url, string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []) 150 | { 151 | $type = 'url'; 152 | 153 | return $this->add(compact('type', 'url', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')); 154 | } 155 | 156 | /** 157 | * Add new header item. 158 | * 159 | * @param string $title 160 | * @param int|null $order 161 | * @param string|null $icon 162 | * @param array $linkAttributes 163 | * @param array $itemAttributes 164 | * 165 | * @return static 166 | */ 167 | public function header(string $title, int $order = null, string $icon = null, array $linkAttributes = [], array $itemAttributes = []) 168 | { 169 | $type = 'header'; 170 | 171 | return $this->add(compact('type', 'title', 'order', 'icon', 'linkAttributes', 'itemAttributes')); 172 | } 173 | 174 | /** 175 | * Add new divider item. 176 | * 177 | * @param int|null $order 178 | * @param array $itemAttributes 179 | * 180 | * @return static 181 | */ 182 | public function divider(int $order = null, array $itemAttributes = []) 183 | { 184 | $type = 'divider'; 185 | 186 | return $this->add(compact('type', 'order', 'itemAttributes')); 187 | } 188 | 189 | /** 190 | * Empty the current item childs. 191 | * 192 | * @return $this 193 | */ 194 | public function destroy() 195 | { 196 | $this->properties = []; 197 | $this->activeWhen = null; 198 | $this->childs = collect(); 199 | $this->hideCallbacks = collect(); 200 | 201 | return $this; 202 | } 203 | 204 | /** 205 | * Get childs. 206 | * 207 | * @return \Illuminate\Support\Collection 208 | */ 209 | public function getChilds(): Collection 210 | { 211 | return $this->childs->sortBy('properties.order'); 212 | } 213 | 214 | /** 215 | * Get url. 216 | * 217 | * @return string 218 | */ 219 | public function getUrl(): string 220 | { 221 | return $this->route ? route($this->route[0], $this->route[1] ?? []) : ($this->url ? url($this->url) : ''); 222 | } 223 | 224 | /** 225 | * Get HTML attribute data. 226 | * 227 | * @return mixed 228 | */ 229 | public function getLinkAttributes() 230 | { 231 | return HTML::attributes($this->linkAttributes); 232 | } 233 | 234 | /** 235 | * Get HTML parent attribute data. 236 | * 237 | * @return mixed 238 | */ 239 | public function getItemAttributes() 240 | { 241 | $itemAttributes = $this->itemAttributes; 242 | 243 | (empty($itemAttributes['class']) && ! $this->isActive()) || $itemAttributes['class'][] = $this->isActive() ? 'active' : ''; 244 | 245 | return HTML::attributes($itemAttributes); 246 | } 247 | 248 | /** 249 | * Check if the current item is divider. 250 | * 251 | * @return bool 252 | */ 253 | public function isDivider(): bool 254 | { 255 | return $this->type === 'divider'; 256 | } 257 | 258 | /** 259 | * Check if the current item is header. 260 | * 261 | * @return bool 262 | */ 263 | public function isHeader(): bool 264 | { 265 | return $this->type === 'header'; 266 | } 267 | 268 | /** 269 | * Check is the current item has sub menu . 270 | * 271 | * @return bool 272 | */ 273 | public function hasChilds(): bool 274 | { 275 | return $this->getChilds()->isNotEmpty(); 276 | } 277 | 278 | /** 279 | * Check the active state for current menu. 280 | * 281 | * @return bool 282 | */ 283 | public function hasActiveOnChild(): bool 284 | { 285 | return $this->hasChilds() ? $this->hasActiveStateFromChilds() : false; 286 | } 287 | 288 | /** 289 | * Set hide callback for current menu item. 290 | * 291 | * @param callable $callback 292 | * 293 | * @return $this 294 | */ 295 | public function hideWhen(callable $callback) 296 | { 297 | $this->hideCallbacks->push($callback); 298 | 299 | return $this; 300 | } 301 | 302 | /** 303 | * Set authorization callback for current menu item. 304 | * 305 | * @param string $ability 306 | * @param mixed $params 307 | * @param string $guard 308 | * 309 | * @return $this 310 | */ 311 | public function ifCan(string $ability, $params = null, $guard = null) 312 | { 313 | $this->hideCallbacks->push(function () use ($ability, $params, $guard) { 314 | return ! auth()->guard($guard)->user()?->can($ability, $params); 315 | }); 316 | 317 | return $this; 318 | } 319 | 320 | /** 321 | * Set condition callback for current menu item. 322 | * 323 | * @param mixed $condition 324 | * 325 | * @return $this 326 | */ 327 | public function if($condition) 328 | { 329 | $this->hideCallbacks->push(function () use ($condition) { 330 | return ! $condition; 331 | }); 332 | 333 | return $this; 334 | } 335 | 336 | /** 337 | * Set authentication callback for current menu item. 338 | * 339 | * @param string $guard 340 | * 341 | * @return $this 342 | */ 343 | public function ifUser($guard = null) 344 | { 345 | $this->hideCallbacks->push(function () use ($guard) { 346 | return ! auth()->guard($guard)->user(); 347 | }); 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * Set authentication callback for current menu item. 354 | * 355 | * @param string $guard 356 | * 357 | * @return $this 358 | */ 359 | public function ifGuest($guard = null) 360 | { 361 | $this->hideCallbacks->push(function () use ($guard) { 362 | return auth()->guard($guard)->user(); 363 | }); 364 | 365 | return $this; 366 | } 367 | 368 | /** 369 | * Check if the menu item is hidden. 370 | * 371 | * @return bool 372 | */ 373 | public function isHidden(): bool 374 | { 375 | return (bool) $this->hideCallbacks->first(function ($callback) { 376 | return call_user_func($callback); 377 | }); 378 | } 379 | 380 | /** 381 | * Get active state for current item. 382 | * 383 | * @return bool 384 | */ 385 | public function isActive(): bool 386 | { 387 | if (is_callable($activeWhen = $this->activeWhen)) { 388 | return call_user_func($activeWhen); 389 | } 390 | 391 | if ($this->route) { 392 | return $this->hasActiveStateFromRoute(); 393 | } 394 | 395 | return $this->hasActiveStateFromUrl(); 396 | } 397 | 398 | /** 399 | * Set active callback. 400 | * 401 | * @param callable $route 402 | * 403 | * @return $this 404 | */ 405 | public function activateWhen(callable $callback) 406 | { 407 | $this->activeWhen = $callback; 408 | 409 | return $this; 410 | } 411 | 412 | /** 413 | * Set active callback on the given route. 414 | * 415 | * @param string $route 416 | * 417 | * @return $this 418 | */ 419 | public function activateOnRoute(string $route) 420 | { 421 | $this->activeWhen = function () use ($route) { 422 | return Str::contains(Route::currentRouteName(), $route); 423 | }; 424 | 425 | return $this; 426 | } 427 | 428 | /** 429 | * Get active status using route. 430 | * 431 | * @return bool 432 | */ 433 | protected function hasActiveStateFromRoute(): bool 434 | { 435 | return Route::is($this->route[0]); 436 | } 437 | 438 | /** 439 | * Get active status using request url. 440 | * 441 | * @return bool 442 | */ 443 | protected function hasActiveStateFromUrl(): bool 444 | { 445 | return Request::is($this->url); 446 | } 447 | 448 | /** 449 | * Check if the item has active state from childs. 450 | * 451 | * @return bool 452 | */ 453 | protected function hasActiveStateFromChilds(): bool 454 | { 455 | return $this->getChilds()->contains(function (self $child) { 456 | return ($child->hasChilds() && $child->hasActiveStateFromChilds()) 457 | || ($child->route && $child->hasActiveStateFromRoute()) 458 | || $child->isActive() || $child->hasActiveStateFromUrl(); 459 | }) ?? false; 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /src/Models/MenuManager.php: -------------------------------------------------------------------------------- 1 | viewFactory = $viewFactory; 60 | $this->callbacks = collect(); 61 | $this->menus = collect(); 62 | $this->router = $router; 63 | } 64 | 65 | /** 66 | * Check if the menu exists. 67 | * 68 | * @param string $name 69 | * 70 | * @return bool 71 | */ 72 | public function has($name): bool 73 | { 74 | return $this->menus->has($name); 75 | } 76 | 77 | /** 78 | * Get instance of the given menu if exists. 79 | * 80 | * @param string $name 81 | * 82 | * @return \Rinvex\Menus\Models\MenuGenerator|null 83 | */ 84 | public function instance($name): ?MenuGenerator 85 | { 86 | return $this->menus->get($name); 87 | } 88 | 89 | /** 90 | * Register a menu. 91 | * 92 | * @param string $name 93 | * @param \Closure $callback 94 | * 95 | * @return void 96 | */ 97 | public function register($name, Closure $callback): void 98 | { 99 | if ($this->has($name)) { 100 | $this->callbacks = $this->callbacks->put($name, $this->callbacks->get($name, collect())->push($callback)); 101 | } else { 102 | $builder = new MenuGenerator(); 103 | 104 | $builder->setViewFactory($this->viewFactory); 105 | 106 | $this->menus->put($name, $builder); 107 | 108 | $this->callbacks = $this->callbacks->put($name, $this->callbacks->get($name, collect())->push($callback)); 109 | } 110 | } 111 | 112 | /** 113 | * Render the menu tag by given name. 114 | * 115 | * @param string $name 116 | * @param string $presenter 117 | * @param array $bindings 118 | * @param bool $specialSidebar 119 | * 120 | * @return string|null 121 | */ 122 | public function render(string $name, string $presenter = null, array $bindings = [], bool $specialSidebar = false): ?string 123 | { 124 | if ($this->has($name)) { 125 | $instance = $this->instance($name); 126 | 127 | $this->callbacks->get($name)->each(function ($callback) use ($instance) { 128 | $reflectionParams = collect((new ReflectionFunction($callback))->getParameters()); 129 | $reflectionParams->shift(); 130 | 131 | collect($reflectionParams)->each(function ($param) use (&$params) { 132 | $name = $param->getType()->getName(); 133 | $params[] = Route::current()->hasParameter($param->getName()) ? Route::current()->parameter($param->getName()) 134 | : ((app()->has($name) ?: class_exists($name) ?: interface_exists($name)) ? app($name) : null); 135 | }); 136 | 137 | $params ? $callback($instance, ...$params) : $callback($instance); 138 | }); 139 | 140 | return $instance->setBindings($bindings)->render($presenter, $specialSidebar); 141 | } 142 | 143 | return null; 144 | } 145 | 146 | /** 147 | * Get all menus. 148 | * 149 | * @return array 150 | */ 151 | public function all(): array 152 | { 153 | return $this->menus->toArray(); 154 | } 155 | 156 | /** 157 | * Get count from all menus. 158 | * 159 | * @return int 160 | */ 161 | public function count(): int 162 | { 163 | return $this->menus->count(); 164 | } 165 | 166 | /** 167 | * Empty the current menus. 168 | */ 169 | public function destroy() 170 | { 171 | $this->menus = collect(); 172 | 173 | return $this; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Presenters/AdminltePresenter.php: -------------------------------------------------------------------------------- 1 | '; 17 | } 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getCloseTagWrapper(): string 23 | { 24 | return ''; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function getDividerWrapper(): string 31 | { 32 | return '
  • '; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getHeaderWrapper(MenuItem $item): string 39 | { 40 | return '
  • '.($item->icon ? ' ' : '').$item->title.'
  • '; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getMenuWithoutDropdownWrapper(MenuItem $item): string 47 | { 48 | return '
  • getItemAttributes().'> 49 | getLinkAttributes().'> 50 | '.($item->icon ? '' : '').' 51 | '.$item->title.' 52 | 53 |
  • '; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function getMenuWithDropDownWrapper(MenuItem $item, bool $specialSidebar = false): string 60 | { 61 | return $specialSidebar 62 | ? $this->getHeaderWrapper($item).$this->getChildMenuItems($item) 63 | : '
  • 64 | 65 | '.($item->icon ? '' : '').' 66 | '.$item->title.' 67 | 68 | 69 | 70 | 71 |
      72 | '.$this->getChildMenuItems($item).' 73 |
    74 |
  • '; 75 | } 76 | 77 | /** 78 | * Get multilevel menu wrapper. 79 | * 80 | * @param \Rinvex\Menus\Models\MenuItem $item 81 | * 82 | * @return string` 83 | */ 84 | public function getMultiLevelDropdownWrapper(MenuItem $item): string 85 | { 86 | return '
  • 87 | 88 | '.($item->icon ? '' : '').' 89 | '.$item->title.' 90 | 91 | 92 | 93 | 94 |
      95 | '.$this->getChildMenuItems($item).' 96 |
    97 |
  • '; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Presenters/BasePresenter.php: -------------------------------------------------------------------------------- 1 | getChilds() as $child) { 24 | if ($child->isHidden()) { 25 | continue; 26 | } 27 | 28 | if ($child->hasChilds()) { 29 | $results .= $this->getMultiLevelDropdownWrapper($child); 30 | } elseif ($child->isHeader()) { 31 | $results .= $this->getHeaderWrapper($child); 32 | } elseif ($child->isDivider()) { 33 | $results .= $this->getDividerWrapper(); 34 | } else { 35 | $results .= $this->getMenuWithoutDropdownWrapper($child); 36 | } 37 | } 38 | 39 | return $results; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Presenters/NavMenuPresenter.php: -------------------------------------------------------------------------------- 1 | '; 17 | } 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getMenuWithDropDownWrapper(MenuItem $item, bool $specialSidebar = false): string 23 | { 24 | return $specialSidebar 25 | ? $this->getHeaderWrapper($item).$this->getChildMenuItems($item) 26 | : ''; 35 | } 36 | 37 | /** 38 | * Get multilevel menu wrapper. 39 | * 40 | * @param \Rinvex\Menus\Models\MenuItem $item 41 | * 42 | * @return string` 43 | */ 44 | public function getMultiLevelDropdownWrapper(MenuItem $item): string 45 | { 46 | return ''; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Presenters/NavPillsPresenter.php: -------------------------------------------------------------------------------- 1 | '; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Presenters/NavTabPresenter.php: -------------------------------------------------------------------------------- 1 | '; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Presenters/NavbarPresenter.php: -------------------------------------------------------------------------------- 1 | '; 17 | } 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getCloseTagWrapper(): string 23 | { 24 | return ''; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function getDividerWrapper(): string 31 | { 32 | return '
  • '; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getHeaderWrapper(MenuItem $item): string 39 | { 40 | return ''; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getMenuWithoutDropdownWrapper(MenuItem $item): string 47 | { 48 | return '
  • getItemAttributes().'> 49 | getLinkAttributes().'> 50 | '.($item->icon ? '' : '').' 51 | '.$item->title.' 52 | 53 |
  • '; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function getMenuWithDropDownWrapper(MenuItem $item, bool $specialSidebar = false): string 60 | { 61 | return $specialSidebar 62 | ? $this->getHeaderWrapper($item).$this->getChildMenuItems($item) 63 | : ''; 72 | } 73 | 74 | /** 75 | * Get multilevel menu wrapper. 76 | * 77 | * @param \Rinvex\Menus\Models\MenuItem $item 78 | * 79 | * @return string` 80 | */ 81 | public function getMultiLevelDropdownWrapper(MenuItem $item): string 82 | { 83 | return ''; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Presenters/NavbarRightPresenter.php: -------------------------------------------------------------------------------- 1 | '; 17 | } 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getMenuWithDropDownWrapper(MenuItem $item, bool $specialSidebar = false): string 23 | { 24 | return $specialSidebar 25 | ? $this->getHeaderWrapper($item).$this->getChildMenuItems($item) 26 | : ''; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Presenters/SidebarMenuPresenter.php: -------------------------------------------------------------------------------- 1 | '; 20 | } 21 | 22 | /** 23 | * Get close tag wrapper. 24 | * 25 | * @return string 26 | */ 27 | public function getCloseTagWrapper(): string 28 | { 29 | return ''; 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function getDividerWrapper(): string 36 | { 37 | return '
  • '; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function getHeaderWrapper(MenuItem $item): string 44 | { 45 | return ''; 46 | } 47 | 48 | /** 49 | * Get menu tag without dropdown wrapper. 50 | * 51 | * @param \Rinvex\Menus\Models\MenuItem $item 52 | * 53 | * @return string 54 | */ 55 | public function getMenuWithoutDropdownWrapper(MenuItem $item): string 56 | { 57 | return '
  • getItemAttributes().'> 58 | getLinkAttributes().'> 59 | '.($item->icon ? '' : '').' 60 | '.$item->title.' 61 | 62 |
  • '; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function getMenuWithDropDownWrapper(MenuItem $item, bool $specialSidebar = false): string 69 | { 70 | $id = Str::random(); 71 | 72 | return $specialSidebar 73 | ? $this->getHeaderWrapper($item).$this->getChildMenuItems($item) 74 | : ''; 87 | } 88 | 89 | /** 90 | * Get multilevel menu wrapper. 91 | * 92 | * @param \Rinvex\Menus\Models\MenuItem $item 93 | * 94 | * @return string` 95 | */ 96 | public function getMultiLevelDropdownWrapper(MenuItem $item): string 97 | { 98 | return $this->getMenuWithDropDownWrapper($item); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Providers/MenusServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(realpath(__DIR__.'/../../resources/views'), 'rinvex/menus'); 22 | 23 | // Register paths to be published by the publish command. 24 | $this->publishViewsFrom(realpath(__DIR__.'/../../resources/views'), 'rinvex/menus'); 25 | 26 | // Register core presenters 27 | $this->app['rinvex.menus.presenters']->put('navbar', \Rinvex\Menus\Presenters\NavbarPresenter::class); 28 | $this->app['rinvex.menus.presenters']->put('navbar-right', \Rinvex\Menus\Presenters\NavbarRightPresenter::class); 29 | $this->app['rinvex.menus.presenters']->put('nav-pills', \Rinvex\Menus\Presenters\NavPillsPresenter::class); 30 | $this->app['rinvex.menus.presenters']->put('nav-tab', \Rinvex\Menus\Presenters\NavTabPresenter::class); 31 | $this->app['rinvex.menus.presenters']->put('sidebar', \Rinvex\Menus\Presenters\SidebarMenuPresenter::class); 32 | $this->app['rinvex.menus.presenters']->put('navmenu', \Rinvex\Menus\Presenters\NavMenuPresenter::class); 33 | $this->app['rinvex.menus.presenters']->put('adminlte', \Rinvex\Menus\Presenters\AdminltePresenter::class); 34 | } 35 | 36 | /** 37 | * Register the service provider. 38 | */ 39 | public function register() 40 | { 41 | // Register menus service 42 | $this->app->singleton('rinvex.menus', MenuManager::class); 43 | 44 | // Register menu presenters service 45 | $this->app->singleton('rinvex.menus.presenters', fn () => collect()); 46 | } 47 | } 48 | --------------------------------------------------------------------------------