diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..6ea4c08 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,76 @@ +name: 🐛 Bug Report +description: Create a report to help us improve +title: 'bug: ' + +body: + - type: checkboxes + id: prerequisites + attributes: + label: Prerequisites + description: Please ensure you have completed all of the following. + options: + - label: I have searched for [existing issues](https://github.com/KyleMassacre/laravel-menus/issues) that already report this problem, without success. + required: true + + - type: dropdown + id: affected-versions + attributes: + label: Version + description: Which version(s) of Laravel Menus does this issue impact? + options: + - v8.x + multiple: true + validations: + required: true + + - type: dropdown + id: affected-laravel-versions + attributes: + label: Version + description: Which version(s) of Laravel does this issue impact? + options: + - v10.x + multiple: false + validations: + required: true + + - type: textarea + id: current-behavior + attributes: + label: Current Behavior + description: A clear description of what the bug is and how it manifests. + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: A clear description of what you expected to happen. + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to Reproduce + description: Please explain the steps required to duplicate this issue. + placeholder: | + 1. + 2. + 3. + validations: + required: true + + - type: input + id: reproduction-url + attributes: + label: Code Reproduction URL + description: Please reproduce this issue in a blank Laravel Invoices starter application and provide a link to the repo. + placeholder: https://github.com/... + + - type: textarea + id: additional-information + attributes: + label: Additional Information + description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..526d2e8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,53 @@ +name: 💡 Feature Request +description: Suggest an idea for Laravel Invoices +title: 'feat: ' + +body: + - type: checkboxes + id: prerequisites + attributes: + label: Prerequisites + description: Please ensure you have completed all of the following. + options: + - label: I have searched for [existing issues](https://github.com/KyleMassacre/laravel-menus/issues) that already report this problem, without success. + required: true + + - type: textarea + id: description + attributes: + label: Describe the Feature Request + description: A clear and concise description of what the feature does. + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Describe the Use Case + description: A clear and concise use case for what problem this feature would solve. + validations: + required: true + + - type: textarea + id: proposed-solution + attributes: + label: Describe Preferred Solution + description: A clear and concise description of what you how you want this feature to be added to Laravel Invoices. + + - type: textarea + id: alternatives-considered + attributes: + label: Describe Alternatives + description: A clear and concise description of any alternative solutions or features you have considered. + + - type: textarea + id: related-code + attributes: + label: Related Code + description: If you are able to illustrate the feature request with an example, please provide a sample Laravel Invoices application. Try out our [Getting Started Wizard](https://ionicframework.com/start#basics) to quickly spin up an Laravel Invoices starter app. + + - type: textarea + id: additional-information + attributes: + label: Additional Information + description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to implement, Stack Overflow links, forum links, etc. diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel.yml new file mode 100644 index 0000000..5d1a855 --- /dev/null +++ b/.github/workflows/laravel.yml @@ -0,0 +1,37 @@ +name: Laravel + +on: + push: + branches: [ "master", "develop" ] + pull_request: + branches: [ "master", "develop" ] + +jobs: + run: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ ubuntu-latest ] + php-versions: ['8.1', '8.2','8.3'] + name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} + steps: + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + - uses: actions/checkout@v3 + with: + fetch-depth: 5 + - name: Copy .env + run: php -r "file_exists('.env') || copy('.env.example', '.env');" + - name: Install Dependencies + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + - name: Execute tests (Unit and Feature tests) via PHPUnit + env: + DB_CONNECTION: sqlite + DB_DATABASE: database/database.sqlite + run: vendor/bin/phpunit + - name: action-scrutinizer + uses: sudo-bot/action-scrutinizer@latest + with: + cli-args: "--format=php-clover build/logs/clover.xml --revision=${{ github.event.pull_request.head.sha || github.sha }}" + diff --git a/.gitignore b/.gitignore index fbcfdaa..c4d326d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ composer.phar composer.lock build -.php_cs.cache +.php-cs-fixer.cache +.idea/ +.phpunit.cache/ diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 83% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 8bfd25f..3e6ad8d 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,14 +1,15 @@ in(__DIR__) ->exclude([ 'vendor', - ]) -; + ]); -return PhpCsFixer\Config::create() - ->setFinder($finder) +return (new Config()) ->setRules([ '@PSR2' => true, // Concatenation should be used with at least one whitespace around. @@ -16,9 +17,9 @@ // Unused use statements must be removed. 'ordered_imports' => true, // Removes extra empty lines. - 'no_extra_consecutive_blank_lines' => true, + 'no_extra_blank_lines' => true, // An empty line feed should precede a return statement. - 'blank_line_before_return' => true, + 'blank_line_before_statement' => true, // Unused use statements must be removed. 'no_unused_imports' => true, // Remove trailing whitespace at the end of blank lines. @@ -34,12 +35,14 @@ // Remove duplicated semicolons. 'no_empty_statement' => true, // PHP multi-line arrays should have a trailing comma. - 'trailing_comma_in_multiline_array' => true, + 'trailing_comma_in_multiline' => true, // There should be no empty lines after class opening brace. 'no_blank_lines_after_class_opening' => true, // There should not be blank lines between docblock and the documented element. 'no_blank_lines_after_phpdoc' => true, // Phpdocs should start and end with content, excluding the very first and last line of the docblocks. 'phpdoc_trim' => true, + 'normalize_index_brace' => true, + 'whitespace_after_comma_in_array' => true, ]) -; + ->setFinder($finder); diff --git a/.scrutinizer.yml b/.scrutinizer.yml index d9998be..4a8a885 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -7,3 +7,11 @@ coding_style: tools: external_code_coverage: true php_code_coverage: true +build: + nodes: + analysis: + environment: + php: 8.1.0 + tests: + override: + - php-scrutinizer-run diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ebf1b2..f89b54d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All Notable changes to `laravel-menus` will be documented in this file. ## Next +## 7.0.0 - 2024-03-30 + +### Added +- Laravel 10 support + ## 6.0.0 - 2020-11-11 ### Added diff --git a/README.md b/README.md index 633aee3..3f3dcaa 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,81 @@ # Laravel Menus -[![Latest Version on Packagist](https://img.shields.io/packagist/v/nwidart/laravel-menus.svg?style=flat-square)](https://packagist.org/packages/nwidart/laravel-menus) +## Usage Example + +To create menus in your Laravel application, you should create a file at `app/Support/menus.php`. This file will be automatically loaded by the package's service provider. + +### Basic Menu Creation + +```php +add([ + 'route' => 'home', + 'title' => 'Home', + 'icon' => 'fa fa-home fa-fw me-2', + ]); + + // Add a menu item with a URL instead of a route + $menu->add([ + 'url' => '/about', + 'title' => 'About', + 'icon' => 'fa fa-info fa-fw me-2', + ]); + + // Add a dropdown menu + $menu->dropdown('User', function ($sub) { + $sub->add([ + 'route' => 'profile', + 'title' => 'Profile', + 'icon' => 'fa fa-user fa-fw me-2', + ]); + $sub->add([ + 'route' => 'logout', + 'title' => 'Logout', + 'icon' => 'fa fa-sign-out fa-fw me-2', + ]); + }); +}); +``` + +### Rendering Menus + +To render a menu in your blade templates: + +```php +{!! Menu::render('main-menu') !!} +``` + +Or with a specific presenter: + +```php +{!! Menu::render('main-menu', 'App\\Http\\Presenters\\MainMenuPresenter') !!} +``` + +### Important Notes + +1. Ensure your routes are defined before creating menus that reference them +2. For route-based menu items, use `'route' => 'route.name'` or `'route' => ['route.name', ['param' => 'value']]` for routes with parameters +3. For URL-based menu items, use `'url' => '/path/to/page'` + +# Laravel Menus + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/kylemassacre/laravel-menus.svg?style=flat-square)](https://packagist.org/packages/kylemassacre/laravel-menus) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) -[![Build Status](https://img.shields.io/travis/nWidart/laravel-menus/master.svg?style=flat-square)](https://travis-ci.org/nWidart/laravel-menus) -[![Scrutinizer Coverage](https://img.shields.io/scrutinizer/coverage/g/nWidart/laravel-menus.svg?style=flat-square)](https://scrutinizer-ci.com/g/nWidart/laravel-menus/?branch=master) -[![SensioLabsInsight](https://img.shields.io/sensiolabs/i/6b187410-e586-465f-a137-2d1fbf7ac724.svg?style=flat-square)](https://insight.sensiolabs.com/projects/6b187410-e586-465f-a137-2d1fbf7ac724) -[![Quality Score](https://img.shields.io/scrutinizer/g/nWidart/laravel-menus.svg?style=flat-square)](https://scrutinizer-ci.com/g/nWidart/laravel-menus) -[![Total Downloads](https://img.shields.io/packagist/dt/nwidart/laravel-menus.svg?style=flat-square)](https://packagist.org/packages/nwidart/laravel-menus) - -| **Laravel** | **laravel-menus** | -|---|---| -| 5.4 | ^0.5 | -| 5.5 | ^1.0 | -| 5.6 | ^2.0 | -| 5.7 | ^3.0 | -| 5.8 | ^4.0 | -| 6.0 | ^5.0 | -| 8.0 | ^7.0 | - -`nwidart/laravel-menus` is a laravel package which created to manage menus. It has a feature called presenters which enables easy styling and custom structure of menu rendering. - -This package is a re-published, re-organised and maintained version of [pingpong/menus](https://github.com/pingpong-labs/menus), which isn't maintained anymore. This package is used in [AsgardCMS](https://asgardcms.com/). +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/kylemassacre/laravel-menus/laravel.yml) +[![Scrutinizer Coverage](https://img.shields.io/scrutinizer/coverage/g/kylemassacre/laravel-menus.svg?style=flat-square)](https://scrutinizer-ci.com/g/kylemassacre/laravel-menus/?branch=master) +![Scrutinizer quality (GitHub/Bitbucket) with branch](https://img.shields.io/scrutinizer/quality/g/kylemassacre/laravel-menus/master?style=flat-square) +[![Total Downloads](https://img.shields.io/packagist/dt/kylemassacre/laravel-menus.svg?style=flat-square)](https://packagist.org/packages/kylemassacre/laravel-menus) + +`kylemassacre/laravel-menus` is a laravel package which created to manage menus. It has a feature called presenters which enables easy styling and custom structure of menu rendering. + +This package is a re-published, re-organised and maintained version of [nwidart/laravel-menus](https://github.com/nWidart/laravel-menus), which isn't maintained anymore. This package is used in [AsgardCMS](https://asgardcms.com/). With one big added bonus that the original package didn't have: **tests**. @@ -29,7 +84,7 @@ With one big added bonus that the original package didn't have: **tests**. You'll find installation instructions and full documentation on https://nwidart.com/laravel-menus/. ## Credits - +- [Kyle Ellis](https://github.com/kylemassacre) - [Nicolas Widart](https://github.com/nwidart) - [gravitano](https://github.com/gravitano) - [All Contributors](../../contributors) diff --git a/Tests/BaseTestCase.php b/Tests/BaseTestCase.php index 4f7b585..d9e2ef8 100644 --- a/Tests/BaseTestCase.php +++ b/Tests/BaseTestCase.php @@ -1,18 +1,16 @@ setUpDatabase(); } protected function getPackageProviders($app) @@ -23,23 +21,17 @@ protected function getPackageProviders($app) ]; } - /** - * Set up the environment. - * - * @param \Illuminate\Foundation\Application $app - */ protected function getEnvironmentSetUp($app) { $app['config']->set('menus', [ 'styles' => [ - 'navbar' => \Nwidart\Menus\Presenters\Bootstrap\NavbarPresenter::class, - 'navbar-right' => \Nwidart\Menus\Presenters\Bootstrap\NavbarRightPresenter::class, - 'nav-pills' => \Nwidart\Menus\Presenters\Bootstrap\NavPillsPresenter::class, - 'nav-tab' => \Nwidart\Menus\Presenters\Bootstrap\NavTabPresenter::class, - 'sidebar' => \Nwidart\Menus\Presenters\Bootstrap\SidebarMenuPresenter::class, - 'navmenu' => \Nwidart\Menus\Presenters\Bootstrap\NavMenuPresenter::class, + 'navbar' => \KyleMassacre\Menus\Presenters\Bootstrap\NavbarPresenter::class, + 'navbar-right' => \KyleMassacre\Menus\Presenters\Bootstrap\NavbarRightPresenter::class, + 'nav-pills' => \KyleMassacre\Menus\Presenters\Bootstrap\NavPillsPresenter::class, + 'nav-tab' => \KyleMassacre\Menus\Presenters\Bootstrap\NavTabPresenter::class, + 'sidebar' => \KyleMassacre\Menus\Presenters\Bootstrap\SidebarMenuPresenter::class, + 'navmenu' => \KyleMassacre\Menus\Presenters\Bootstrap\NavMenuPresenter::class, ], - 'ordering' => false, ]); } diff --git a/Tests/MenuBuilderTest.php b/Tests/MenuBuilderTest.php index 89121ee..8d74920 100644 --- a/Tests/MenuBuilderTest.php +++ b/Tests/MenuBuilderTest.php @@ -1,10 +1,10 @@ 'Parent Item']); $menuItem->child(['title' => 'Child Item']); - $this->assertCount(1, $menuItem->getChilds()); + $this->assertCount(1, $menuItem->getChildren()); } /** @test */ @@ -97,7 +97,7 @@ public function it_can_get_ordered_children() $menuItem->child(['title' => 'Child Item', 'order' => 10]); $menuItem->child(['title' => 'First Child Item', 'order' => 1]); - $children = $menuItem->getChilds(); + $children = $menuItem->getChildren(); $this->assertEquals('First Child Item', $children[1]->title); $this->assertEquals('Child Item', $children[0]->title); } @@ -110,8 +110,8 @@ public function it_can_create_a_dropdown_menu_item() $sub->url('settings/account', 'Account'); $sub->url('settings/password', 'Password'); }); - $this->assertCount(1, $menuItem->getChilds()); - $this->assertCount(2, $menuItem->getChilds()[0]->getChilds()); + $this->assertCount(1, $menuItem->getChildren()); + $this->assertCount(2, $menuItem->getChildren()[0]->getChilds()); } /** @test */ @@ -121,7 +121,7 @@ public function it_can_make_a_simple_route_menu_item() $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { $sub->route('settings.account', 'Account', ['user_id' => 1]); }); - $children = $menuItem->getChilds()[0]->getChilds(); + $children = $menuItem->getChildren()[0]->getChilds(); $this->assertCount(1, $children); $childMenuItem = Arr::first($children); @@ -136,7 +136,7 @@ public function it_can_make_a_route_menu_item() $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { $sub->route('settings.account', 'Account', ['user_id' => 1], 1, ['my-attr' => 'value']); }); - $children = $menuItem->getChilds()[0]->getChilds(); + $children = $menuItem->getChildren()[0]->getChilds(); $this->assertCount(1, $children); $childMenuItem = Arr::first($children); @@ -153,7 +153,7 @@ public function it_can_make_a_simple_url_menu_item() $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { $sub->url('settings/account', 'Account'); }); - $children = $menuItem->getChilds()[0]->getChilds(); + $children = $menuItem->getChildren()[0]->getChilds(); $this->assertCount(1, $children); $childMenuItem = Arr::first($children); @@ -168,7 +168,7 @@ public function it_can_make_a_url_menu_item() $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { $sub->url('settings/account', 'Account', 1, ['my-attr' => 'value']); }); - $children = $menuItem->getChilds()[0]->getChilds(); + $children = $menuItem->getChildren()[0]->getChilds(); $this->assertCount(1, $children); $childMenuItem = Arr::first($children); @@ -187,7 +187,7 @@ public function it_can_add_a_menu_item_divider() $sub->divider(); }); - $children = $menuItem->getChilds()[0]->getChilds(); + $children = $menuItem->getChildren()[0]->getChilds(); $this->assertCount(2, $children); $dividerMenuItem = $children[1]; @@ -204,7 +204,7 @@ public function it_can_add_a_header_menu_item() $sub->url('settings/account', 'Account'); }); - $children = $menuItem->getChilds()[0]->getChilds(); + $children = $menuItem->getChildren()[0]->getChilds(); $this->assertCount(2, $children); $headerItem = $children[0]; @@ -229,7 +229,7 @@ public function it_can_get_the_correct_url_for_route_type() $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { $sub->route('settings.account', 'Account'); }); - $children = $menuItem->getChilds()[0]->getChilds(); + $children = $menuItem->getChildren()[0]->getChilds(); $childMenuItem = Arr::first($children); $this->assertEquals('http://localhost/settings/account', $childMenuItem->getUrl()); diff --git a/Tests/MenuTest.php b/Tests/MenuTest.php index fb50338..699446b 100644 --- a/Tests/MenuTest.php +++ b/Tests/MenuTest.php @@ -1,9 +1,9 @@ menu->destroy(); $this->assertCount(0, $this->menu->all()); } + + /** @test */ + public function it_still_generates_empty_menu_after_adding_dropdown() + { + $this->menu->create('test', function (MenuBuilder $menu) { + $menu->dropdown('Test', function ($sub) { + + })->hideWhen(function () { + return true; + }); + }); + + $expected = << + + + +TEXT; + + self::assertEquals($expected, $this->menu->get('test')); + } + + /** @test */ + public function it_still_generates_empty_menu_after_adding_item() + { + $this->menu->create('test', function (MenuBuilder $menu) { + $menu->url('/', 'Test') + ->hideWhen(function () { + return true; + }); + }); + + $expected = << + + + +TEXT; + + self::assertEquals($expected, $this->menu->get('test')); + } } diff --git a/app/Support/menus.php b/app/Support/menus.php new file mode 100644 index 0000000..c04dcd3 --- /dev/null +++ b/app/Support/menus.php @@ -0,0 +1,41 @@ +name('login'); +} + +// Create menu after routes are defined +Menu::create('main-guest', function (MenuBuilder $menu) { + $menu->setPresenter(MainMenuPresenter::class); + $menu->add([ + 'route' => 'login', + 'title' => 'Login', + 'icon' => 'fa fa-sign-in fa-fw me-2', + ]); +}); + +// You can also create additional menus +Menu::create('main-auth', function (MenuBuilder $menu) { + $menu->setPresenter(MainMenuPresenter::class); + $menu->add([ + 'url' => '/dashboard', + 'title' => 'Dashboard', + 'icon' => 'fa fa-dashboard fa-fw me-2', + ]); + + $menu->add([ + 'route' => ['profile.show', []], + 'title' => 'Profile', + 'icon' => 'fa fa-user fa-fw me-2', + ]); +}); + diff --git a/composer.json b/composer.json index 8df2336..bc03434 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "nwidart/laravel-menus", + "name": "kylemassacre/laravel-menus", "description": "Laravel Menu management", "keywords": [ "laravel", @@ -17,29 +17,33 @@ { "name": "Pingpong Labs", "email": "pingpong.labs@gmail.com" + }, + { + "name": "Kyle Ellis", + "email": "ky.ellis83@gmail.com" } ], "require": { - "php": ">=7.3", - "illuminate/support": "^8.0", - "illuminate/config": "^8.0", - "illuminate/view": "^8.0", - "laravelcollective/html": "6.2.*" + "php": "^8.0", + "illuminate/support": "^9.21|^10.0|^12.0", + "illuminate/config": "^9.21|^10.0|^12.0", + "illuminate/view": "^9.21|^10.0|^12.0", + "spatie/laravel-html": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^8.5", - "mockery/mockery": "~1.0", - "orchestra/testbench": "^6.2", - "friendsofphp/php-cs-fixer": "^2.16" + "phpunit/phpunit": "^10.1", + "mockery/mockery": "^1.6.0", + "orchestra/testbench": "^8.10.0|^9.0", + "friendsofphp/php-cs-fixer": "^3.40.0" }, "autoload": { "psr-4": { - "Nwidart\\Menus\\": "src" + "KyleMassacre\\Menus\\": "src" } }, "autoload-dev": { "psr-4": { - "Nwidart\\Menus\\Tests\\": "Tests" + "KyleMassacre\\Menus\\Tests\\": "Tests" } }, "extra": { @@ -48,12 +52,19 @@ }, "laravel": { "providers": [ - "Nwidart\\Menus\\MenusServiceProvider" + "KyleMassacre\\Menus\\MenusServiceProvider" ], "aliases": { - "Menu": "Nwidart\\Menus\\Facades\\Menu" + "Menu": "KyleMassacre\\Menus\\Facades\\Menu" } } }, - "minimum-stability": "stable" + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "php-cs-fixer": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix --config=./.php-cs-fixer.dist.php" + }, + "suggest": { + "spatie/laravel-html": "A modern alternative to laravelcollective/html which is no longer supported" + } } diff --git a/config/config.php b/config/config.php index 9fb361b..6ccbc8e 100644 --- a/config/config.php +++ b/config/config.php @@ -3,14 +3,15 @@ return [ 'styles' => [ - 'navbar' => \Nwidart\Menus\Presenters\Bootstrap\NavbarPresenter::class, - 'navbar-right' => \Nwidart\Menus\Presenters\Bootstrap\NavbarRightPresenter::class, - 'nav-pills' => \Nwidart\Menus\Presenters\Bootstrap\NavPillsPresenter::class, - 'nav-tab' => \Nwidart\Menus\Presenters\Bootstrap\NavTabPresenter::class, - 'sidebar' => \Nwidart\Menus\Presenters\Bootstrap\SidebarMenuPresenter::class, - 'navmenu' => \Nwidart\Menus\Presenters\Bootstrap\NavMenuPresenter::class, - 'adminlte' => \Nwidart\Menus\Presenters\Admin\AdminltePresenter::class, - 'zurbmenu' => \Nwidart\Menus\Presenters\Foundation\ZurbMenuPresenter::class, + 'navbar' => \KyleMassacre\Menus\Presenters\Bootstrap\NavbarPresenter::class, + 'navbar-right' => \KyleMassacre\Menus\Presenters\Bootstrap\NavbarRightPresenter::class, + 'nav-pills' => \KyleMassacre\Menus\Presenters\Bootstrap\NavPillsPresenter::class, + 'nav-tab' => \KyleMassacre\Menus\Presenters\Bootstrap\NavTabPresenter::class, + 'sidebar' => \KyleMassacre\Menus\Presenters\Bootstrap\SidebarMenuPresenter::class, + 'navmenu' => \KyleMassacre\Menus\Presenters\Bootstrap\NavMenuPresenter::class, + 'adminlte' => \KyleMassacre\Menus\Presenters\Admin\AdminltePresenter::class, + 'zurbmenu' => \KyleMassacre\Menus\Presenters\Foundation\ZurbMenuPresenter::class, + 'metronic' => \KyleMassacre\Menus\Presenters\Metronic\MetronicHorizontalMenuPresenter::class, ], 'ordering' => false, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 785aa48..db1748d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,29 +1,32 @@ - - - - Tests - - - - - src/ - - - - - - - - - + + + + + + + + + + + Tests + + + + + + + + src/ + + diff --git a/src/Contracts/MenuItemContract.php b/src/Contracts/MenuItemContract.php new file mode 100644 index 0000000..6347748 --- /dev/null +++ b/src/Contracts/MenuItemContract.php @@ -0,0 +1,205 @@ +childs[] = $item; + + return $item; + } + + abstract public function addDivider($order = null): static; + + abstract public function divider($order = null): static; + + abstract public function addHeader(string $title): static; + + abstract public function header(string $title): static; + + abstract public function addBadge(string $type, string $text): static; + + abstract public function getChildren(): array; + + abstract public function getUrl(): string; + + abstract public function getRequest(): string; + + abstract public function getBadge(): string; + + abstract public function getIcon(null|string $default): ?string; + + abstract public function getProperties(): array; + + abstract public function getAttributes(): mixed; + + abstract public function is(string $name): bool; + + abstract public function hasSubMenu(): bool; + + abstract public function hasActiveOnChild(): mixed; + + abstract public function getActiveStateFromChildren(): bool; + + abstract public function inactive(): bool; + + abstract public function getActiveAttribute(): string; + + abstract public function getInactiveAttribute(): string; + + abstract public function isActive(): mixed; + + abstract protected function hasRoute(): mixed; + + abstract protected function getActiveStateFromRoute(): bool; + + abstract protected function getActiveStateFromUrl(): bool; + + abstract public function order(int $order): self; + + abstract public function hasBadge(): bool; + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray(): array + { + return $this->getProperties(); + } + + /** + * Get property. + * + * @param string $key + * + * @return string|null + */ + public function __get(string $key): ?string + { + $return = null; + if (property_exists($this, $key)) { + $return = $this->$key ?? null; + } + + return $return; + } + + public function __call($name, $arguments) + { + if (str($name)->is('is*')) { + $slice = str($name)->after('is')->lower(); + + return $this->is($slice); + } + } +} diff --git a/src/Facades/Menu.php b/src/Facades/Menu.php index df331c2..bf9eb13 100644 --- a/src/Facades/Menu.php +++ b/src/Facades/Menu.php @@ -1,10 +1,10 @@ $type, + 'text' => $text, + 'name' => 'badge', + ); + + return $this->add($properties); + } + /** * Add new header item. * - * @return \Nwidart\Menus\MenuItem + * @return \KyleMassacre\Menus\MenuItem */ public function addHeader($title, $order = null) { @@ -633,11 +646,11 @@ protected function renderMenu() $presenter = $this->getPresenter(); $menu = $presenter->getOpenTagWrapper(); - foreach ($this->getOrderedItems() as $item) { - if ($item->hidden()) { - continue; - } + $items = array_filter($this->getOrderedItems(), function ($item) { + return !$item->hidden(); + }); + foreach ($items as $item) { if ($item->hasSubMenu()) { $menu .= $presenter->getMenuWithDropDownWrapper($item); } elseif ($item->isHeader()) { diff --git a/src/MenuItem.php b/src/MenuItem.php index 00c34ee..4afcab1 100644 --- a/src/MenuItem.php +++ b/src/MenuItem.php @@ -1,13 +1,12 @@ properties = $properties; $this->fill($properties); } - /** - * Set the icon property when the icon is defined in the link attributes. - * - * @param array $properties - * - * @return array - */ - protected static function setIconAttribute(array $properties) - { - $icon = Arr::get($properties, 'attributes.icon'); - if (!is_null($icon)) { - $properties['icon'] = $icon; - - Arr::forget($properties, 'attributes.icon'); - - return $properties; - } - - return $properties; - } - - /** - * Get random name. - * - * @param array $attributes - * - * @return string - */ - protected static function getRandomName(array $attributes) - { - return substr(md5(Arr::get($attributes, 'title', Str::random(6))), 0, 5); - } - - /** - * Create new static instance. - * - * @param array $properties - * - * @return static - */ - public static function make(array $properties) - { - $properties = self::setIconAttribute($properties); - - return new static($properties); - } - /** * Fill the attributes. * * @param array $attributes */ - public function fill($attributes) + public function fill(array $attributes): void { foreach ($attributes as $key => $value) { if (in_array($key, $this->fillable)) { @@ -136,11 +52,11 @@ public function fill($attributes) /** * Create new menu child item using array. * - * @param $attributes + * @param array $attributes * - * @return $this + * @return self */ - public function child($attributes) + public function child(array $attributes): self { $this->childs[] = static::make($attributes); @@ -151,11 +67,12 @@ public function child($attributes) * Register new child menu with dropdown. * * @param $title - * @param callable $callback - * + * @param \Closure $callback + * @param int $order + * @param array $attributes * @return $this */ - public function dropdown($title, \Closure $callback, $order = 0, array $attributes = array()) + public function dropdown(string $title, \Closure $callback, int $order = 0, array $attributes = array()): static { $properties = compact('title', 'order', 'attributes'); @@ -185,9 +102,9 @@ public function dropdown($title, \Closure $callback, $order = 0, array $attribut * @param array $parameters * @param array $attributes * - * @return MenuItem + * @return MenuItemContract */ - public function route($route, $title, $parameters = array(), $order = 0, $attributes = array()) + public function route($route, $title, array $parameters = array(), $order = 0, array $attributes = array()): static { if (func_num_args() === 4) { $arguments = func_get_args(); @@ -211,9 +128,9 @@ public function route($route, $title, $parameters = array(), $order = 0, $attrib * @param $title * @param array $attributes * - * @return MenuItem + * @return static */ - public function url($url, $title, $order = 0, $attributes = array()) + public function url($url, $title, $order = 0, array $attributes = array()): static { if (func_num_args() === 3) { $arguments = func_get_args(); @@ -228,22 +145,6 @@ public function url($url, $title, $order = 0, $attributes = array()) return $this->add(compact('url', 'title', 'order', 'attributes')); } - /** - * Add new child item. - * - * @param array $properties - * - * @return $this - */ - public function add(array $properties) - { - $item = static::make($properties); - - $this->childs[] = $item; - - return $item; - } - /** * Add new divider. * @@ -251,7 +152,7 @@ public function add(array $properties) * * @return self */ - public function addDivider($order = null) + public function addDivider($order = null): static { $item = static::make(array('name' => 'divider', 'order' => $order)); @@ -267,7 +168,7 @@ public function addDivider($order = null) * * @return MenuItem */ - public function divider($order = null) + public function divider($order = null): static { return $this->addDivider($order); } @@ -279,7 +180,7 @@ public function divider($order = null) * * @return $this */ - public function addHeader($title) + public function addHeader($title): static { $item = static::make(array( 'name' => 'header', @@ -298,17 +199,39 @@ public function addHeader($title) * * @return $this */ - public function header($title) + public function header($title): static { return $this->addHeader($title); } + public function addBadge(string $type, $text): static + { + $properties = array( + 'type' => $type, + 'text' => $text, + 'name' => 'badge', + ); + $item = static::make($properties); + $this->badge = $properties; + + return $item; + } + + /** + * @deprecated See `getChildren` + * @return array + */ + public function getChilds(): array + { + return $this->getChildren(); + } + /** * Get childs. * * @return array */ - public function getChilds() + public function getChildren(): array { if (config('menus.ordering')) { return collect($this->childs)->sortBy('order')->all(); @@ -322,7 +245,7 @@ public function getChilds() * * @return string */ - public function getUrl() + public function getUrl(): string { if ($this->route !== null) { return route($this->route[0], $this->route[1]); @@ -340,19 +263,32 @@ public function getUrl() * * @return string */ - public function getRequest() + public function getRequest(): string { return ltrim(str_replace(url('/'), '', $this->getUrl()), '/'); } + /** + * @return string + */ + public function getBadge(): string + { + if ($this->hasBadge()) { + extract($this->badge); + + return '' . $text . ''; + } + return ''; + } + /** * Get icon. * - * @param null|string $default + * @param string|null $default * - * @return string + * @return string|null */ - public function getIcon($default = null) + public function getIcon(string $default = null): ?string { if ($this->icon !== null && $this->icon !== '') { return ''; @@ -369,7 +305,7 @@ public function getIcon($default = null) * * @return array */ - public function getProperties() + public function getProperties(): array { return $this->properties; } @@ -377,15 +313,26 @@ public function getProperties() /** * Get HTML attribute data. * - * @return mixed + * @return string */ - public function getAttributes() + public function getAttributes(): string { - $attributes = $this->attributes ? $this->attributes : []; + $attributes = $this->attributes ?: []; Arr::forget($attributes, ['active', 'icon']); - return HTML::attributes($attributes); + $attributeString = collect($attributes) + ->map(function ($value, $key) { + if (is_bool($value)) { + return $value ? $key : ''; + } + + return $key . '="' . e($value) . '"'; + }) + ->filter() + ->implode(' '); + + return $attributeString ? ' ' . $attributeString : ''; } /** @@ -393,7 +340,7 @@ public function getAttributes() * * @return bool */ - public function isDivider() + public function isDivider(): bool { return $this->is('divider'); } @@ -403,7 +350,7 @@ public function isDivider() * * @return bool */ - public function isHeader() + public function isHeader(): bool { return $this->is('header'); } @@ -415,7 +362,7 @@ public function isHeader() * * @return bool */ - public function is($name) + public function is($name): bool { return $this->name == $name; } @@ -425,17 +372,17 @@ public function is($name) * * @return bool */ - public function hasSubMenu() + public function hasSubMenu(): bool { return !empty($this->childs); } /** - * Same with hasSubMenu. + * @deprecated Same with hasSubMenu. * * @return bool */ - public function hasChilds() + public function hasChilds(): bool { return $this->hasSubMenu(); } @@ -445,23 +392,18 @@ public function hasChilds() * * @return mixed */ - public function hasActiveOnChild() + public function hasActiveOnChild(): mixed { if ($this->inactive()) { return false; } - return $this->hasChilds() ? $this->getActiveStateFromChilds() : false; + return $this->hasSubMenu() && $this->getActiveStateFromChildren(); } - /** - * Get active state from child menu items. - * - * @return bool - */ - public function getActiveStateFromChilds() + public function getActiveStateFromChildren(): bool { - foreach ($this->getChilds() as $child) { + foreach ($this->getChildren() as $child) { if ($child->inactive()) { continue; } @@ -481,15 +423,29 @@ public function getActiveStateFromChilds() return false; } + /** + * @deprecated See `getActiveStateFromChildren()` + * Get active state from child menu items. + * + * @return bool + */ + public function getActiveStateFromChilds(): bool + { + return $this->getActiveStateFromChildren(); + } + /** * Get inactive state. * * @return bool */ - public function inactive() + public function inactive(): bool { $inactive = $this->getInactiveAttribute(); + if ($inactive === '1' || $inactive === '0') { + return (bool)$inactive; + } if (is_bool($inactive)) { return $inactive; } @@ -506,9 +462,9 @@ public function inactive() * * @return string */ - public function getActiveAttribute() + public function getActiveAttribute(): string { - return Arr::get($this->attributes, 'active'); + return Arr::get($this->attributes, 'active', ''); } /** @@ -516,9 +472,9 @@ public function getActiveAttribute() * * @return string */ - public function getInactiveAttribute() + public function getInactiveAttribute(): string { - return Arr::get($this->attributes, 'inactive'); + return Arr::get($this->attributes, 'inactive', ''); } /** @@ -526,7 +482,7 @@ public function getInactiveAttribute() * * @return mixed */ - public function isActive() + public function isActive(): mixed { if ($this->inactive()) { return false; @@ -534,6 +490,9 @@ public function isActive() $active = $this->getActiveAttribute(); + if ($active === '1' || $active === '0') { + return (bool)$active; + } if (is_bool($active)) { return $active; } @@ -554,7 +513,7 @@ public function isActive() * * @return bool */ - protected function hasRoute() + protected function hasRoute(): bool { return !empty($this->route); } @@ -564,7 +523,7 @@ protected function hasRoute() * * @return bool */ - protected function getActiveStateFromRoute() + protected function getActiveStateFromRoute(): bool { return Request::is(str_replace(url('/') . '/', '', $this->getUrl())); } @@ -574,7 +533,7 @@ protected function getActiveStateFromRoute() * * @return bool */ - protected function getActiveStateFromUrl() + protected function getActiveStateFromUrl(): bool { return Request::is($this->url); } @@ -585,59 +544,15 @@ protected function getActiveStateFromUrl() * @param int $order * @return self */ - public function order($order) + public function order(int $order): self { $this->order = $order; return $this; } - /** - * Set hide condition for current menu item. - * - * @param Closure - * @return boolean - */ - public function hideWhen(Closure $callback) - { - $this->hideWhen = $callback; - - return $this; - } - - /** - * Determine whether the menu item is hidden. - * - * @return boolean - */ - public function hidden() - { - if (is_null($this->hideWhen)) { - return false; - } - - return call_user_func($this->hideWhen) == true; - } - - /** - * Get the instance as an array. - * - * @return array - */ - public function toArray() - { - return $this->getProperties(); - } - - /** - * Get property. - * - * @param string $key - * - * @return string|null - */ - public function __get($key) + public function hasBadge(): bool { - return isset($this->$key) ? $this->$key : null; + return !empty($this->badge); } } diff --git a/src/MenusServiceProvider.php b/src/MenusServiceProvider.php index dc6cc5f..a588d11 100644 --- a/src/MenusServiceProvider.php +++ b/src/MenusServiceProvider.php @@ -1,6 +1,6 @@ registerHtmlPackage(); + $this->registerSpatieHtmlPackage(); $this->app->singleton('menus', function ($app) { return new Menu($app['view'], $app['config']); @@ -46,15 +46,15 @@ public function register() } /** - * Register "iluminate/html" package. + * Register "spatie/laravel-html" package. */ - private function registerHtmlPackage() + private function registerSpatieHtmlPackage() { - $this->app->register('Collective\Html\HtmlServiceProvider'); + $this->app->register('Spatie\Html\HtmlServiceProvider'); $aliases = [ - 'HTML' => 'Collective\Html\HtmlFacade', - 'Form' => 'Collective\Html\FormFacade', + 'HTML' => 'Spatie\Html\Facades\Html', + 'Form' => 'Spatie\Html\Facades\Form', ]; AliasLoader::getInstance($aliases)->register(); diff --git a/src/Presenters/Admin/AdminltePresenter.php b/src/Presenters/Admin/AdminltePresenter.php index d20076a..dea4351 100644 --- a/src/Presenters/Admin/AdminltePresenter.php +++ b/src/Presenters/Admin/AdminltePresenter.php @@ -1,39 +1,40 @@ ' . PHP_EOL; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getCloseTagWrapper() + public function getCloseTagWrapper(): ?string { return PHP_EOL . '' . PHP_EOL; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getMenuWithoutDropdownWrapper($item) + public function getMenuWithoutDropdownWrapper(MenuItemContract $item): ?string { return 'getActiveState($item) . '>getAttributes() . '>' . $item->getIcon() . ' ' . $item->title . '' . PHP_EOL; } /** - * {@inheritdoc }. + * {@inheritdoc}. */ - public function getActiveState($item, $state = ' class="active"') + public function getActiveState(MenuItemContract $item, $state = ' class="active"'): mixed { return $item->isActive() ? $state : null; } @@ -46,31 +47,31 @@ public function getActiveState($item, $state = ' class="active"') * * @return null|string */ - public function getActiveStateOnChild($item, $state = 'active') + public function getActiveStateOnChild(MenuItemContract $item, string $state = 'active'): ?string { return $item->hasActiveOnChild() ? $state : null; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getDividerWrapper() + public function getDividerWrapper(): ?string { return '
  • '; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getHeaderWrapper($item) + public function getHeaderWrapper(MenuItemContract $item): ?string { return '
  • ' . $item->title . '
  • '; } /** - * {@inheritdoc }. + * {@inheritdoc}. */ - public function getMenuWithDropDownWrapper($item) + public function getMenuWithDropDownWrapper(MenuItemContract $item): ?string { return '
  • @@ -89,11 +90,11 @@ public function getMenuWithDropDownWrapper($item) /** * Get multilevel menu wrapper. * - * @param \Nwidart\Menus\MenuItem $item + * @param MenuItemContract $item * - * @return string` + * @return string */ - public function getMultiLevelDropdownWrapper($item) + public function getMultiLevelDropdownWrapper(MenuItemContract $item): string { return '
  • diff --git a/src/Presenters/Bootstrap/NavMenuPresenter.php b/src/Presenters/Bootstrap/NavMenuPresenter.php index afeafa1..a8d3829 100644 --- a/src/Presenters/Bootstrap/NavMenuPresenter.php +++ b/src/Presenters/Bootstrap/NavMenuPresenter.php @@ -1,11 +1,11 @@ ' . PHP_EOL; } diff --git a/src/Presenters/Bootstrap/NavTabPresenter.php b/src/Presenters/Bootstrap/NavTabPresenter.php index 249fc04..c5d7d8b 100644 --- a/src/Presenters/Bootstrap/NavTabPresenter.php +++ b/src/Presenters/Bootstrap/NavTabPresenter.php @@ -1,13 +1,13 @@ ' . PHP_EOL; } diff --git a/src/Presenters/Bootstrap/NavbarPresenter.php b/src/Presenters/Bootstrap/NavbarPresenter.php index 1a4f175..841d9ff 100644 --- a/src/Presenters/Bootstrap/NavbarPresenter.php +++ b/src/Presenters/Bootstrap/NavbarPresenter.php @@ -1,39 +1,40 @@ ' . PHP_EOL; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getCloseTagWrapper() + public function getCloseTagWrapper(): ?string { return PHP_EOL . '' . PHP_EOL; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getMenuWithoutDropdownWrapper($item) + public function getMenuWithoutDropdownWrapper(MenuItemContract $item): ?string { return 'getActiveState($item) . '>getAttributes() . '>' . $item->getIcon() . ' ' . $item->title . '
  • ' . PHP_EOL; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getActiveState($item, $state = ' class="active"') + public function getActiveState(MenuItemContract $item, string $state = ' class="active"'): ?string { return $item->isActive() ? $state : null; } @@ -46,31 +47,31 @@ public function getActiveState($item, $state = ' class="active"') * * @return null|string */ - public function getActiveStateOnChild($item, $state = 'active') + public function getActiveStateOnChild(MenuItemContract $item, string $state = 'active'): ?string { return $item->hasActiveOnChild() ? $state : null; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getDividerWrapper() + public function getDividerWrapper(): ?string { return '
  • '; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getHeaderWrapper($item) + public function getHeaderWrapper(MenuItemContract $item): ?string { return ''; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getMenuWithDropDownWrapper($item) + public function getMenuWithDropDownWrapper(MenuItemContract $item): ?string { return '
  • '; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getHeaderWrapper($item) + public function getHeaderWrapper(MenuItemContract $item): ?string { return ''; } /** - * {@inheritdoc }. + * {@inheritdoc} */ - public function getMenuWithDropDownWrapper($item) + public function getMenuWithDropDownWrapper(MenuItemContract $item): ?string { $id = Str::random(); @@ -104,11 +105,11 @@ public function getMenuWithDropDownWrapper($item) /** * Get multilevel menu wrapper. * - * @param \Nwidart\Menus\MenuItem $item + * @param MenuItemContract $item * - * @return string` + * @return string */ - public function getMultiLevelDropdownWrapper($item) + public function getMultiLevelDropdownWrapper(MenuItemContract $item): string { return $this->getMenuWithDropDownWrapper($item); } diff --git a/src/Presenters/Foundation/ZurbMenuPresenter.php b/src/Presenters/Foundation/ZurbMenuPresenter.php index 7e1dcea..63b0b58 100644 --- a/src/Presenters/Foundation/ZurbMenuPresenter.php +++ b/src/Presenters/Foundation/ZurbMenuPresenter.php @@ -1,15 +1,16 @@ ' . PHP_EOL; } @@ -26,7 +27,7 @@ public function getCloseTagWrapper() /** * {@inheritdoc } */ - public function getMenuWithoutDropdownWrapper($item) + public function getMenuWithoutDropdownWrapper(MenuItemContract $item): ?string { return 'getActiveState($item) . '>' . $item->title . ''; } @@ -34,7 +35,7 @@ public function getMenuWithoutDropdownWrapper($item) /** * {@inheritdoc } */ - public function getActiveState($item) + public function getActiveState(MenuItemContract $item): ?string { return \Request::is($item->getRequest()) ? ' class="is-active"' : null; } @@ -42,7 +43,7 @@ public function getActiveState($item) /** * {@inheritdoc } */ - public function getDividerWrapper() + public function getDividerWrapper(): ?string { return '
  • '; } @@ -50,7 +51,7 @@ public function getDividerWrapper() /** * {@inheritdoc } */ - public function getMenuWithDropDownWrapper($item) + public function getMenuWithDropDownWrapper(MenuItemContract $item): ?string { return '
  • ' . $item->title . ' diff --git a/src/Presenters/Metronic/MetronicHorizontalMenuPresenter.php b/src/Presenters/Metronic/MetronicHorizontalMenuPresenter.php index 213a89b..4e15352 100644 --- a/src/Presenters/Metronic/MetronicHorizontalMenuPresenter.php +++ b/src/Presenters/Metronic/MetronicHorizontalMenuPresenter.php @@ -6,16 +6,17 @@ * Description: Generate horizontal menu to metronic theme */ -namespace App\Presenters; +namespace KyleMassacre\Menus\Presenters\Metronic; -use Nwidart\Menus\Presenters\Presenter; +use KyleMassacre\Menus\Contracts\MenuItemContract; +use KyleMassacre\Menus\Presenters\Presenter; class MetronicHorizontalMenuPresenter extends Presenter { /** * {@inheritdoc } */ - public function getOpenTagWrapper() + public function getOpenTagWrapper(): ?string { return PHP_EOL . '' . PHP_EOL; } @@ -31,7 +32,7 @@ public function getCloseTagWrapper() /** * {@inheritdoc } */ - public function getMenuWithoutDropdownWrapper($item) + public function getMenuWithoutDropdownWrapper(MenuItemContract $item): ?string { return '
  • getActiveState($item) . '>' . $item->getIcon() . '' . $item->title . '
  • '; } @@ -39,7 +40,7 @@ public function getMenuWithoutDropdownWrapper($item) /** * {@inheritdoc } */ - public function getActiveState($item) + public function getActiveState(MenuItemContract $item): string { return \Request::is($item->getRequest()) ? ' class="m-menu__item m-menu__item--rel active"' : 'class="m-menu__item m-menu__item--rel"'; } @@ -47,7 +48,7 @@ public function getActiveState($item) /** * {@inheritdoc } */ - public function getDividerWrapper() + public function getDividerWrapper(): ?string { return ''; } @@ -55,7 +56,7 @@ public function getDividerWrapper() /** * {@inheritdoc } */ - public function getMenuWithDropDownWrapper($item) + public function getMenuWithDropDownWrapper(MenuItemContract $item): ?string { if ($item->title == '...') { return '