├── .editorconfig ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .styleci.yml ├── LICENSE.md ├── README.md ├── SECURITY.md ├── composer.json ├── phpunit.xml ├── src ├── Config │ └── menu.php ├── Facade.php ├── Menu.php ├── MenuBuilder.php ├── MenuItem.php ├── Presenters │ ├── Admin │ │ ├── Adminlte.php │ │ ├── Argon.php │ │ ├── MetronicHorizontal.php │ │ └── Tailwind.php │ ├── Bootstrap3 │ │ ├── Nav.php │ │ ├── NavPills.php │ │ ├── NavTab.php │ │ ├── Navbar.php │ │ ├── NavbarRight.php │ │ └── Sidebar.php │ ├── Foundation │ │ └── Zurb.php │ ├── Presenter.php │ └── PresenterInterface.php ├── Provider.php ├── Resources │ └── views │ │ └── bootstrap3 │ │ ├── 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 │ │ └── style.blade.php └── helpers.php └── tests ├── MenuBuilderTest.php ├── MenuItemTest.php ├── MenuTest.php └── TestCase.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at https://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | 17 | [*.yml] 18 | indent_size = 2 -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | tests: 7 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.stability }} 8 | 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | php: ['8.0', '8.1', '8.2'] 14 | laravel: [9.*, 10.*] 15 | stability: [prefer-lowest, prefer-stable] 16 | include: 17 | - laravel: 9.* 18 | testbench: 7.* 19 | - laravel: 10.* 20 | testbench: 8.* 21 | exclude: 22 | - laravel: 10.* 23 | php: 8.0 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v2 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | extensions: bcmath, ctype, dom, fileinfo, intl, gd, json, mbstring, pdo, pdo_sqlite, openssl, sqlite, xml, zip 34 | coverage: none 35 | 36 | - name: Install dependencies 37 | run: | 38 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 39 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 40 | 41 | - name: Execute tests 42 | run: vendor/bin/phpunit 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.history 3 | /.vscode 4 | /build 5 | /vendor 6 | composer.phar 7 | composer.lock 8 | .DS_Store 9 | .phpunit.result.cache 10 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | 3 | enabled: 4 | - concat_with_spaces -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Pingpong Labs 4 | 5 | Copyright (c) 2016 Nicolas Widart 6 | 7 | Copyright (c) 2019 Akaunting 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Menu and sidebar management package for Laravel 2 | 3 | ![Downloads](https://img.shields.io/packagist/dt/akaunting/laravel-menu) 4 | ![Tests](https://img.shields.io/github/actions/workflow/status/akaunting/laravel-menu/tests.yml?label=tests) 5 | [![StyleCI](https://github.styleci.io/repos/180763610/shield?style=flat&branch=master)](https://styleci.io/repos/180763610) 6 | [![License](https://img.shields.io/github/license/akaunting/laravel-menu)](LICENSE.md) 7 | 8 | This package intends to create and manage menus and sidebars for your Laravel app. It ships with ready-to-go presenters and you can create your own ones. 9 | 10 | ## Getting Started 11 | 12 | ### 1. Install 13 | 14 | Run the following command: 15 | 16 | ```bash 17 | composer require akaunting/laravel-menu 18 | ``` 19 | 20 | ### 2. Register 21 | 22 | Service provider and facade will be registered automatically. If you want to register them manually in `config/app.php`: 23 | 24 | ```php 25 | Akaunting\Menu\Facade::class, 26 | Akaunting\Menu\Provider::class, 27 | ``` 28 | 29 | ### 3. Publish 30 | 31 | Publish config file. 32 | 33 | ```bash 34 | php artisan vendor:publish --tag=menu 35 | ``` 36 | 37 | ### 4. Configure 38 | 39 | You can change the configuration from `config/menu.php` file 40 | 41 | ## Usage 42 | 43 | Check out the [wiki](../../wiki) about the usage and further documentation. 44 | 45 | ## Changelog 46 | 47 | Please see [Releases](../../releases) for more information what has changed recently. 48 | 49 | ## Contributing 50 | 51 | Pull requests are more than welcome. You must follow the PSR coding standards. 52 | 53 | ## Security 54 | 55 | Please review [our security policy](https://github.com/akaunting/laravel-menu/security/policy) on how to report security vulnerabilities. 56 | 57 | ## Credits 58 | 59 | - [Denis Duliçi](https://github.com/denisdulici) 60 | - [All Contributors](../../contributors) 61 | 62 | ## License 63 | 64 | The MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information. 65 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | **PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).** 4 | 5 | ## Reporting a Vulnerability 6 | 7 | If you discover any security related issues, please email security@akaunting.com instead of using the issue tracker. 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "akaunting/laravel-menu", 3 | "description": "Menu and sidebar management package for Laravel", 4 | "keywords": [ 5 | "laravel", 6 | "menu", 7 | "navigation", 8 | "sidebar", 9 | "bootstrap" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Denis Duliçi", 15 | "email": "info@akaunting.com", 16 | "homepage": "https://akaunting.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.0", 22 | "illuminate/config": "^9.0|^10.0", 23 | "illuminate/support": "^9.0|^10.0", 24 | "illuminate/view": "^9.0|^10.0", 25 | "laravelcollective/html": "^6.3" 26 | }, 27 | "require-dev": { 28 | "friendsofphp/php-cs-fixer": "^3.12", 29 | "mockery/mockery": "^1.5", 30 | "orchestra/testbench": "^7.4|^8.0", 31 | "phpunit/phpunit": "^9.5" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Akaunting\\Menu\\": "src" 36 | }, 37 | "files": [ 38 | "src/helpers.php" 39 | ] 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "Akaunting\\Menu\\Tests\\": "tests" 44 | } 45 | }, 46 | "extra": { 47 | "laravel": { 48 | "providers": [ 49 | "Akaunting\\Menu\\Provider" 50 | ], 51 | "aliases": { 52 | "Menu": "Akaunting\\Menu\\Facade" 53 | } 54 | } 55 | }, 56 | "minimum-stability": "dev", 57 | "prefer-stable": true 58 | } 59 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Config/menu.php: -------------------------------------------------------------------------------- 1 | [ 6 | // Boostrap 3 7 | 'bs3-navbar' => \Akaunting\Menu\Presenters\Bootstrap3\Navbar::class, 8 | 'bs3-navbar-right' => \Akaunting\Menu\Presenters\Bootstrap3\NavbarRight::class, 9 | 'bs3-nav-pills' => \Akaunting\Menu\Presenters\Bootstrap3\NavPills::class, 10 | 'bs3-nav-tab' => \Akaunting\Menu\Presenters\Bootstrap3\NavTab::class, 11 | 'bs3-sidebar' => \Akaunting\Menu\Presenters\Bootstrap3\Sidebar::class, 12 | 'bs3-navmenu' => \Akaunting\Menu\Presenters\Bootstrap3\Nav::class, 13 | 14 | // Zurb 15 | 'zurb' => \Akaunting\Menu\Presenters\Foundation\Zurb::class, 16 | 17 | // Admin 18 | 'adminlte' => \Akaunting\Menu\Presenters\Admin\Adminlte::class, 19 | 'argon' => \Akaunting\Menu\Presenters\Admin\Argon::class, 20 | 'metronic-horizontal' => \Akaunting\Menu\Presenters\Admin\MetronicHorizontal::class, 21 | 'tailwind' => \Akaunting\Menu\Presenters\Admin\Tailwind::class, 22 | ], 23 | 24 | 'home_urls' => [ 25 | '/', 26 | ], 27 | 28 | 'ordering' => false, 29 | 30 | ]; 31 | -------------------------------------------------------------------------------- /src/Facade.php: -------------------------------------------------------------------------------- 1 | views = $views; 22 | $this->config = $config; 23 | } 24 | 25 | /** 26 | * Make new menu. 27 | */ 28 | public function make(string $name, Closure $callback): mixed 29 | { 30 | return $this->create($name, $callback); 31 | } 32 | 33 | /** 34 | * Create new menu. 35 | */ 36 | public function create(string $name, Closure $resolver): mixed 37 | { 38 | $builder = new MenuBuilder($name, $this->config); 39 | 40 | $builder->setViewFactory($this->views); 41 | 42 | $this->menu[$name] = $builder; 43 | 44 | return $resolver($builder); 45 | } 46 | 47 | /** 48 | * Check if the menu exists. 49 | */ 50 | public function has(string $name): bool 51 | { 52 | return array_key_exists($name, $this->menu); 53 | } 54 | 55 | /** 56 | * Get instance of the given menu if exists. 57 | */ 58 | public function instance(string $name): ?MenuBuilder 59 | { 60 | return $this->has($name) ? $this->menu[$name] : null; 61 | } 62 | 63 | /** 64 | * Modify a specific menu. 65 | */ 66 | public function modify(string $name, Closure $callback): void 67 | { 68 | $menu = collect($this->menu)->filter(function ($menu) use ($name) { 69 | return $menu->getName() == $name; 70 | })->first(); 71 | 72 | $callback($menu); 73 | } 74 | 75 | /** 76 | * Render the menu tag by given name. 77 | */ 78 | public function get(string $name, ?string $presenter = null, array $bindings = []): ?string 79 | { 80 | return $this->has($name) ? 81 | $this->menu[$name]->setBindings($bindings)->render($presenter) : null; 82 | } 83 | 84 | /** 85 | * Render the menu tag by given name. 86 | */ 87 | public function render(string $name, ?string $presenter = null, array $bindings = []): ?string 88 | { 89 | return $this->get($name, $presenter, $bindings); 90 | } 91 | 92 | /** 93 | * Get a stylesheet for enable multilevel menu. 94 | */ 95 | public function style(): mixed 96 | { 97 | return $this->views->make('menu::bootstrap3.style')->render(); 98 | } 99 | 100 | /** 101 | * Get all menus. 102 | */ 103 | public function all(): array 104 | { 105 | return $this->menu; 106 | } 107 | 108 | /** 109 | * Count menus. 110 | */ 111 | public function count(): int 112 | { 113 | return count($this->menu); 114 | } 115 | 116 | /** 117 | * Empty the current menus. 118 | */ 119 | public function destroy(): void 120 | { 121 | $this->menu = []; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/MenuBuilder.php: -------------------------------------------------------------------------------- 1 | menu = $menu; 95 | $this->config = $config; 96 | } 97 | 98 | /** 99 | * Get menu name. 100 | * 101 | * @return string 102 | */ 103 | public function getName() 104 | { 105 | return $this->menu; 106 | } 107 | 108 | /** 109 | * Find menu item by title. 110 | * 111 | * @param string $title 112 | * @param callable|null $callback 113 | * @return mixed 114 | */ 115 | public function whereTitle($title, callable $callback = null) 116 | { 117 | $item = $this->findBy('title', $title); 118 | 119 | if (is_callable($callback)) { 120 | return call_user_func($callback, $item); 121 | } 122 | 123 | return $item; 124 | } 125 | 126 | /** 127 | * Find menu item by key and value. 128 | * 129 | * @param string $key 130 | * @param string $value 131 | * @return \Akaunting\Menu\MenuItem 132 | */ 133 | public function findBy($key, $value) 134 | { 135 | return collect($this->items)->filter(function ($item) use ($key, $value) { 136 | return $item->{$key} == $value; 137 | })->first(); 138 | } 139 | 140 | /** 141 | * Remove menu item by title. 142 | * 143 | * @param string $title 144 | * @return void 145 | */ 146 | public function removeByTitle($title) 147 | { 148 | $this->removeBy('title', $title); 149 | } 150 | 151 | /** 152 | * Remove menu item by key and value. 153 | * 154 | * @param string $key 155 | * @param string $value 156 | * @return void 157 | */ 158 | public function removeBy($key, $value) 159 | { 160 | $this->items = collect($this->items)->reject(function ($item) use ($key, $value) { 161 | return $item->{$key} == $value; 162 | })->values()->all(); 163 | } 164 | 165 | /** 166 | * Set view factory instance. 167 | * 168 | * @param ViewFactory $views 169 | * 170 | * @return $this 171 | */ 172 | public function setViewFactory(ViewFactory $views) 173 | { 174 | $this->views = $views; 175 | 176 | return $this; 177 | } 178 | 179 | /** 180 | * Set view. 181 | * 182 | * @param string $view 183 | * 184 | * @return $this 185 | */ 186 | public function setView($view) 187 | { 188 | $this->view = $view; 189 | 190 | return $this; 191 | } 192 | 193 | /** 194 | * Set Prefix URL. 195 | * 196 | * @param string $prefixUrl 197 | * 198 | * @return $this 199 | */ 200 | public function setPrefixUrl($prefixUrl) 201 | { 202 | $this->prefixUrl = $prefixUrl; 203 | 204 | return $this; 205 | } 206 | 207 | /** 208 | * Set Prefix URL. 209 | * 210 | * @param string $fragmentUrl 211 | * 212 | * @return $this 213 | */ 214 | public function setFragmentUrl($fragmentUrl) 215 | { 216 | $this->fragmentUrl = $fragmentUrl; 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * Set styles. 223 | * 224 | * @param array $styles 225 | */ 226 | public function setStyles(array $styles) 227 | { 228 | $this->styles = $styles; 229 | } 230 | 231 | /** 232 | * Set new presenter class. 233 | * 234 | * @param string $presenter 235 | */ 236 | public function setPresenter($presenter) 237 | { 238 | $this->presenter = $presenter; 239 | } 240 | 241 | /** 242 | * Get presenter instance. 243 | * 244 | * @return \Akaunting\Menu\Presenters\PresenterInterface 245 | */ 246 | public function getPresenter() 247 | { 248 | return new $this->presenter(); 249 | } 250 | 251 | /** 252 | * Set new presenter class by given style name. 253 | * 254 | * @param string $name 255 | * 256 | * @return self 257 | */ 258 | public function style($name) 259 | { 260 | if ($this->hasStyle($name)) { 261 | $this->setPresenter($this->getStyle($name)); 262 | } 263 | 264 | return $this; 265 | } 266 | 267 | /** 268 | * Determine if the given name in the presenter style. 269 | * 270 | * @param $name 271 | * 272 | * @return bool 273 | */ 274 | public function hasStyle($name) 275 | { 276 | return array_key_exists($name, $this->getStyles()); 277 | } 278 | 279 | /** 280 | * Get style aliases. 281 | * 282 | * @return mixed 283 | */ 284 | public function getStyles() 285 | { 286 | return $this->styles ?: $this->config->get('menu.styles'); 287 | } 288 | 289 | /** 290 | * Get the presenter class name by given alias name. 291 | * 292 | * @param $name 293 | * 294 | * @return mixed 295 | */ 296 | public function getStyle($name) 297 | { 298 | $style = $this->getStyles(); 299 | 300 | return $style[$name]; 301 | } 302 | 303 | /** 304 | * Set new presenter class from given alias name. 305 | * 306 | * @param $name 307 | */ 308 | public function setPresenterFromStyle($name) 309 | { 310 | $this->setPresenter($this->getStyle($name)); 311 | } 312 | 313 | /** 314 | * Set the resolved item bindings 315 | * 316 | * @param array $bindings 317 | * @return $this 318 | */ 319 | public function setBindings(array $bindings) 320 | { 321 | $this->bindings = $bindings; 322 | 323 | return $this; 324 | } 325 | 326 | /** 327 | * Resolves a key from the bindings array. 328 | * 329 | * @param string|array $key 330 | * @return mixed 331 | */ 332 | public function resolve($key) 333 | { 334 | if (is_array($key)) { 335 | foreach ($key as $k => $v) { 336 | $key[$k] = $this->resolve($v); 337 | } 338 | } elseif (is_string($key)) { 339 | $matches = []; 340 | 341 | preg_match_all('/{[\s]*?([^\s]+)[\s]*?}/i', $key, $matches, PREG_SET_ORDER); 342 | 343 | foreach ($matches as $match) { 344 | if (array_key_exists($match[1], $this->bindings)) { 345 | $key = preg_replace('/' . $match[0] . '/', $this->bindings[$match[1]], $key, 1); 346 | } 347 | } 348 | } 349 | 350 | return $key; 351 | } 352 | 353 | /** 354 | * Resolves an array of menu items properties. 355 | * 356 | * @param array &$items 357 | * @return void 358 | */ 359 | protected function resolveItems(array &$items) 360 | { 361 | $resolver = function ($property) { 362 | return $this->resolve($property) ?: $property; 363 | }; 364 | 365 | $totalItems = count($items); 366 | for ($i = 0; $i < $totalItems; $i++) { 367 | $items[$i]->fill(array_map($resolver, $items[$i]->getProperties())); 368 | } 369 | } 370 | 371 | /** 372 | * Add new child menu. 373 | * 374 | * @param array $attributes 375 | * 376 | * @return \Akaunting\Menu\MenuItem 377 | */ 378 | public function add(array $attributes = []) 379 | { 380 | $item = MenuItem::make($attributes); 381 | 382 | $this->items[] = $item; 383 | 384 | return $item; 385 | } 386 | 387 | /** 388 | * Create new menu with dropdown. 389 | * 390 | * @param $title 391 | * @param callable $callback 392 | * @param array $attributes 393 | * @param string|null $fragment 394 | * 395 | * @return $this 396 | */ 397 | public function dropdown($title, \Closure $callback, $order = null, array $attributes = [], $fragment = null) 398 | { 399 | $fragment = $fragment ?: $this->fragmentUrl; 400 | 401 | $properties = compact('title', 'order', 'attributes', 'fragment'); 402 | 403 | if (func_num_args() == 3) { 404 | $arguments = func_get_args(); 405 | 406 | $title = Arr::get($arguments, 0); 407 | $attributes = Arr::get($arguments, 2); 408 | 409 | $properties = compact('title', 'attributes'); 410 | } 411 | 412 | $item = MenuItem::make($properties); 413 | 414 | call_user_func($callback, $item); 415 | 416 | $this->items[] = $item; 417 | 418 | return $item; 419 | } 420 | 421 | /** 422 | * Register new menu item using registered route. 423 | * 424 | * @param $route 425 | * @param $title 426 | * @param array $parameters 427 | * @param array $attributes 428 | * @param string|null $fragment 429 | * 430 | * @return static 431 | */ 432 | public function route($route, $title, $parameters = [], $order = null, $attributes = [], $fragment = null) 433 | { 434 | if (func_num_args() == 4) { 435 | $arguments = func_get_args(); 436 | 437 | return $this->add([ 438 | 'route' => [Arr::get($arguments, 0), Arr::get($arguments, 2)], 439 | 'title' => Arr::get($arguments, 1), 440 | 'attributes' => Arr::get($arguments, 3), 441 | ]); 442 | } 443 | 444 | $route = [$route, $parameters]; 445 | 446 | $fragment = $fragment ?: $this->fragmentUrl; 447 | 448 | $item = MenuItem::make(compact('route', 'title', 'parameters', 'attributes', 'order', 'fragment')); 449 | 450 | $this->items[] = $item; 451 | 452 | return $item; 453 | } 454 | 455 | /** 456 | * Format URL. 457 | * 458 | * @param string $url 459 | * 460 | * @return string 461 | */ 462 | protected function formatUrl($url) 463 | { 464 | $url = !is_null($this->prefixUrl) ? $this->prefixUrl . $url : $url; 465 | $url = !is_null($this->fragmentUrl) ? $url . '#' . $this->fragmentUrl : $url; 466 | 467 | return $url == '/' ? '/' : ltrim(rtrim($url, '/'), '/'); 468 | } 469 | 470 | /** 471 | * Register new menu item using url. 472 | * 473 | * @param $url 474 | * @param $title 475 | * @param array $attributes 476 | * @param string|null $fragment 477 | * 478 | * @return static 479 | */ 480 | public function url($url, $title, $order = 0, $attributes = [], $fragment = null) 481 | { 482 | if (func_num_args() == 3) { 483 | $arguments = func_get_args(); 484 | 485 | return $this->add([ 486 | 'url' => $this->formatUrl(Arr::get($arguments, 0)), 487 | 'title' => Arr::get($arguments, 1), 488 | 'attributes' => Arr::get($arguments, 2), 489 | ]); 490 | } 491 | 492 | $url = $this->formatUrl($url); 493 | 494 | $fragment = $fragment ?: $this->fragmentUrl; 495 | 496 | $item = MenuItem::make(compact('url', 'title', 'order', 'attributes', 'fragment')); 497 | 498 | $this->items[] = $item; 499 | 500 | return $item; 501 | } 502 | 503 | /** 504 | * Add new divider item. 505 | * 506 | * @param int $order 507 | * @return \Akaunting\Menu\MenuItem 508 | */ 509 | public function addDivider($order = null) 510 | { 511 | $this->items[] = new MenuItem([ 512 | 'name' => 'divider', 513 | 'order' => $order, 514 | ]); 515 | 516 | return $this; 517 | } 518 | 519 | /** 520 | * Add new header item. 521 | * 522 | * @return \Akaunting\Menu\MenuItem 523 | */ 524 | public function addHeader($title, $order = null) 525 | { 526 | $this->items[] = new MenuItem([ 527 | 'name' => 'header', 528 | 'title' => $title, 529 | 'order' => $order, 530 | ]); 531 | 532 | return $this; 533 | } 534 | 535 | /** 536 | * Alias for "addHeader" method. 537 | * 538 | * @param string $title 539 | * 540 | * @return $this 541 | */ 542 | public function header($title) 543 | { 544 | return $this->addHeader($title); 545 | } 546 | 547 | /** 548 | * Alias for "addDivider" method. 549 | * 550 | * @return $this 551 | */ 552 | public function divider() 553 | { 554 | return $this->addDivider(); 555 | } 556 | 557 | /** 558 | * Get items count. 559 | * 560 | * @return int 561 | */ 562 | public function count(): int 563 | { 564 | return count($this->items); 565 | } 566 | 567 | /** 568 | * Empty the current menu items. 569 | */ 570 | public function destroy() 571 | { 572 | $this->items = []; 573 | 574 | return $this; 575 | } 576 | 577 | /** 578 | * Render the menu to HTML tag. 579 | * 580 | * @param string $presenter 581 | * 582 | * @return string 583 | */ 584 | public function render($presenter = null) 585 | { 586 | $this->resolveItems($this->items); 587 | 588 | if (!is_null($this->view)) { 589 | return $this->renderView($presenter); 590 | } 591 | 592 | if ($this->hasStyle($presenter)) { 593 | $this->setPresenterFromStyle($presenter); 594 | } 595 | 596 | if (!is_null($presenter) && !$this->hasStyle($presenter)) { 597 | $this->setPresenter($presenter); 598 | } 599 | 600 | return $this->renderMenu(); 601 | } 602 | 603 | /** 604 | * Render menu via view presenter. 605 | * 606 | * @return \Illuminate\View\View 607 | */ 608 | public function renderView($presenter = null) 609 | { 610 | return $this->views->make($presenter ?: $this->view, [ 611 | 'items' => $this->getOrderedItems(), 612 | ]); 613 | } 614 | 615 | /** 616 | * Get original items. 617 | * 618 | * @return array 619 | */ 620 | public function getItems() 621 | { 622 | return $this->items; 623 | } 624 | 625 | /** 626 | * Get menu items as laravel collection instance. 627 | * 628 | * @return \Illuminate\Support\Collection 629 | */ 630 | public function toCollection() 631 | { 632 | return collect($this->items); 633 | } 634 | 635 | /** 636 | * Get menu items as array. 637 | * 638 | * @return array 639 | */ 640 | public function toArray() 641 | { 642 | return $this->toCollection()->toArray(); 643 | } 644 | 645 | /** 646 | * Enable menu ordering. 647 | * 648 | * @return self 649 | */ 650 | public function enableOrdering() 651 | { 652 | $this->ordering = true; 653 | 654 | return $this; 655 | } 656 | 657 | /** 658 | * Disable menu ordering. 659 | * 660 | * @return self 661 | */ 662 | public function disableOrdering() 663 | { 664 | $this->ordering = false; 665 | 666 | return $this; 667 | } 668 | 669 | /** 670 | * Get menu items and order it by 'order' key. 671 | * 672 | * @return array 673 | */ 674 | public function getOrderedItems() 675 | { 676 | if (config('menu.ordering') || $this->ordering) { 677 | return $this->toCollection()->sortBy(function ($item) { 678 | return $item->order; 679 | })->all(); 680 | } 681 | 682 | return $this->items; 683 | } 684 | 685 | /** 686 | * Render the menu. 687 | * 688 | * @return string 689 | */ 690 | protected function renderMenu() 691 | { 692 | $presenter = $this->getPresenter(); 693 | $menu = $presenter->getOpenTagWrapper(); 694 | 695 | foreach ($this->getOrderedItems() as $item) { 696 | if ($item->hidden()) { 697 | continue; 698 | } 699 | 700 | if ($item->hasSubMenu()) { 701 | $menu .= $presenter->getMenuWithDropDownWrapper($item); 702 | } elseif ($item->isHeader()) { 703 | $menu .= $presenter->getHeaderWrapper($item); 704 | } elseif ($item->isDivider()) { 705 | $menu .= $presenter->getDividerWrapper(); 706 | } else { 707 | $menu .= $presenter->getMenuWithoutDropdownWrapper($item); 708 | } 709 | } 710 | 711 | $menu .= $presenter->getCloseTagWrapper(); 712 | 713 | return $menu; 714 | } 715 | } 716 | -------------------------------------------------------------------------------- /src/MenuItem.php: -------------------------------------------------------------------------------- 1 | properties = $properties; 112 | $this->fill($properties); 113 | } 114 | 115 | /** 116 | * Set the icon property when the icon is defined in the link attributes. 117 | * 118 | * @param array $properties 119 | * 120 | * @return array 121 | */ 122 | protected static function setIconAttribute(array $properties) 123 | { 124 | $icon = Arr::get($properties, 'attributes.icon'); 125 | if (!is_null($icon)) { 126 | $properties['icon'] = $icon; 127 | 128 | Arr::forget($properties, 'attributes.icon'); 129 | 130 | return $properties; 131 | } 132 | 133 | return $properties; 134 | } 135 | 136 | /** 137 | * Get random name. 138 | * 139 | * @param array $attributes 140 | * 141 | * @return string 142 | */ 143 | protected static function getRandomName(array $attributes) 144 | { 145 | return substr(md5(Arr::get($attributes, 'title', Str::random(6))), 0, 5); 146 | } 147 | 148 | /** 149 | * Create new static instance. 150 | * 151 | * @param array $properties 152 | * 153 | * @return static 154 | */ 155 | public static function make(array $properties) 156 | { 157 | $properties = self::setIconAttribute($properties); 158 | 159 | return new static($properties); 160 | } 161 | 162 | /** 163 | * Fill the attributes. 164 | * 165 | * @param array $attributes 166 | */ 167 | public function fill($attributes) 168 | { 169 | foreach ($attributes as $key => $value) { 170 | if (in_array($key, $this->fillable)) { 171 | $this->{$key} = $value; 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * Create new menu child item using array. 178 | * 179 | * @param $attributes 180 | * 181 | * @return $this 182 | */ 183 | public function child($attributes) 184 | { 185 | $this->childs[] = static::make($attributes); 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Register new child menu with dropdown. 192 | * 193 | * @param $title 194 | * @param callable $callback 195 | * @param int $order 196 | * @param array $attributes 197 | * @param string|null $fragment 198 | * 199 | * @return $this 200 | */ 201 | public function dropdown($title, \Closure $callback, $order = 0, array $attributes = [], $fragment = null) 202 | { 203 | $properties = compact('title', 'order', 'attributes', 'fragment'); 204 | 205 | if (func_num_args() === 3) { 206 | $arguments = func_get_args(); 207 | 208 | $title = Arr::get($arguments, 0); 209 | $attributes = Arr::get($arguments, 2); 210 | 211 | $properties = compact('title', 'attributes'); 212 | } 213 | 214 | $child = static::make($properties); 215 | 216 | call_user_func($callback, $child); 217 | 218 | $this->childs[] = $child; 219 | 220 | return $child; 221 | } 222 | 223 | /** 224 | * Create new menu item and set the action to route. 225 | * 226 | * @param $route 227 | * @param $title 228 | * @param array $parameters 229 | * @param array $attributes 230 | * @param string|null $fragment 231 | * 232 | * @return MenuItem 233 | */ 234 | public function route($route, $title, $parameters = [], $order = 0, $attributes = [], $fragment = null) 235 | { 236 | if (func_num_args() === 4) { 237 | $arguments = func_get_args(); 238 | 239 | return $this->add([ 240 | 'route' => [Arr::get($arguments, 0), Arr::get($arguments, 2)], 241 | 'title' => Arr::get($arguments, 1), 242 | 'attributes' => Arr::get($arguments, 3), 243 | ]); 244 | } 245 | 246 | $route = [$route, $parameters]; 247 | 248 | return $this->add(compact('route', 'title', 'order', 'attributes', 'fragment')); 249 | } 250 | 251 | /** 252 | * Create new menu item and set the action to url. 253 | * 254 | * @param $url 255 | * @param $title 256 | * @param array $attributes 257 | * @param string|null $fragment 258 | * 259 | * @return MenuItem 260 | */ 261 | public function url($url, $title, $order = 0, $attributes = [], $fragment = null) 262 | { 263 | if (func_num_args() === 3) { 264 | $arguments = func_get_args(); 265 | 266 | return $this->add([ 267 | 'url' => Arr::get($arguments, 0), 268 | 'title' => Arr::get($arguments, 1), 269 | 'attributes' => Arr::get($arguments, 2), 270 | ]); 271 | } 272 | 273 | return $this->add(compact('url', 'title', 'order', 'attributes', 'fragment')); 274 | } 275 | 276 | /** 277 | * Add new child item. 278 | * 279 | * @param array $properties 280 | * 281 | * @return $this 282 | */ 283 | public function add(array $properties) 284 | { 285 | $item = static::make($properties); 286 | 287 | $this->childs[] = $item; 288 | 289 | return $item; 290 | } 291 | 292 | /** 293 | * Add new divider. 294 | * 295 | * @param int $order 296 | * 297 | * @return self 298 | */ 299 | public function addDivider($order = null) 300 | { 301 | $item = static::make(['name' => 'divider', 'order' => $order]); 302 | 303 | $this->childs[] = $item; 304 | 305 | return $item; 306 | } 307 | 308 | /** 309 | * Alias method instead "addDivider". 310 | * 311 | * @param int $order 312 | * 313 | * @return MenuItem 314 | */ 315 | public function divider($order = null) 316 | { 317 | return $this->addDivider($order); 318 | } 319 | 320 | /** 321 | * Add dropdown header. 322 | * 323 | * @param $title 324 | * 325 | * @return $this 326 | */ 327 | public function addHeader($title) 328 | { 329 | $item = static::make([ 330 | 'name' => 'header', 331 | 'title' => $title, 332 | ]); 333 | 334 | $this->childs[] = $item; 335 | 336 | return $item; 337 | } 338 | 339 | /** 340 | * Same with "addHeader" method. 341 | * 342 | * @param $title 343 | * 344 | * @return $this 345 | */ 346 | public function header($title) 347 | { 348 | return $this->addHeader($title); 349 | } 350 | 351 | /** 352 | * Get childs. 353 | * 354 | * @return array 355 | */ 356 | public function getChilds() 357 | { 358 | if (config('menu.ordering')) { 359 | return collect($this->childs)->sortBy('order')->all(); 360 | } 361 | 362 | return $this->childs; 363 | } 364 | 365 | /** 366 | * Get url. 367 | * 368 | * @return string 369 | */ 370 | public function getUrl() 371 | { 372 | if ($this->route !== null) { 373 | $url = route($this->route[0], $this->route[1]); 374 | 375 | return !is_null($this->fragment) ? $url . '#' . $this->fragment : $url; 376 | } 377 | 378 | if (empty($this->url)) { 379 | return url("/#"); 380 | } 381 | 382 | return url($this->url); 383 | } 384 | 385 | /** 386 | * Get request url. 387 | * 388 | * @return string 389 | */ 390 | public function getRequest() 391 | { 392 | return ltrim(str_replace(url('/'), '', $this->getUrl()), '/'); 393 | } 394 | 395 | /** 396 | * Get icon. 397 | * 398 | * @param null|string $default 399 | * 400 | * @return string 401 | */ 402 | public function getIcon($default = null) 403 | { 404 | if ($this->icon !== null && $this->icon !== '') { 405 | return ''; 406 | } 407 | if ($default === null) { 408 | return $default; 409 | } 410 | 411 | return ''; 412 | } 413 | 414 | /** 415 | * Get properties. 416 | * 417 | * @return array 418 | */ 419 | public function getProperties() 420 | { 421 | return $this->properties; 422 | } 423 | 424 | /** 425 | * Get HTML attribute data. 426 | * 427 | * @return mixed 428 | */ 429 | public function getAttributes() 430 | { 431 | $attributes = $this->attributes ? $this->attributes : []; 432 | 433 | Arr::forget($attributes, ['active', 'icon', 'search_keywords']); 434 | 435 | return HTML::attributes($attributes); 436 | } 437 | 438 | /** 439 | * Check is the current item divider. 440 | * 441 | * @return bool 442 | */ 443 | public function isDivider() 444 | { 445 | return $this->is('divider'); 446 | } 447 | 448 | /** 449 | * Check is the current item divider. 450 | * 451 | * @return bool 452 | */ 453 | public function isHeader() 454 | { 455 | return $this->is('header'); 456 | } 457 | 458 | /** 459 | * Check is the current item divider. 460 | * 461 | * @param $name 462 | * 463 | * @return bool 464 | */ 465 | public function is($name) 466 | { 467 | return $this->name == $name; 468 | } 469 | 470 | /** 471 | * Check is the current item has sub menu . 472 | * 473 | * @return bool 474 | */ 475 | public function hasSubMenu() 476 | { 477 | return !empty($this->childs); 478 | } 479 | 480 | /** 481 | * Same with hasSubMenu. 482 | * 483 | * @return bool 484 | */ 485 | public function hasChilds() 486 | { 487 | return $this->hasSubMenu(); 488 | } 489 | 490 | /** 491 | * Check the active state for current menu. 492 | * 493 | * @return mixed 494 | */ 495 | public function hasActiveOnChild() 496 | { 497 | if ($this->inactive()) { 498 | return false; 499 | } 500 | 501 | return $this->hasChilds() ? $this->getActiveStateFromChilds() : false; 502 | } 503 | 504 | /** 505 | * Get active state from child menu items. 506 | * 507 | * @return bool 508 | */ 509 | public function getActiveStateFromChilds() 510 | { 511 | foreach ($this->getChilds() as $child) { 512 | if ($child->inactive()) { 513 | continue; 514 | } 515 | 516 | if ($child->hasChilds()) { 517 | if ($child->getActiveStateFromChilds()) { 518 | return true; 519 | } 520 | } elseif ($child->isActive()) { 521 | return true; 522 | } elseif ($child->hasRoute() && $child->getActiveStateFromRoute()) { 523 | return true; 524 | } elseif ($child->getActiveStateFromUrl()) { 525 | return true; 526 | } 527 | } 528 | 529 | return false; 530 | } 531 | 532 | /** 533 | * Get inactive state. 534 | * 535 | * @return bool 536 | */ 537 | public function inactive() 538 | { 539 | $inactive = $this->getInactiveAttribute(); 540 | 541 | if (is_bool($inactive)) { 542 | return $inactive; 543 | } 544 | 545 | if ($inactive instanceof \Closure) { 546 | return call_user_func($inactive); 547 | } 548 | 549 | return false; 550 | } 551 | 552 | /** 553 | * Get active attribute. 554 | * 555 | * @return string 556 | */ 557 | public function getActiveAttribute() 558 | { 559 | return Arr::get($this->attributes, 'active'); 560 | } 561 | 562 | /** 563 | * Get inactive attribute. 564 | * 565 | * @return string 566 | */ 567 | public function getInactiveAttribute() 568 | { 569 | return Arr::get($this->attributes, 'inactive'); 570 | } 571 | 572 | /** 573 | * Get active state for current item. 574 | * 575 | * @return mixed 576 | */ 577 | public function isActive() 578 | { 579 | if ($this->inactive()) { 580 | return false; 581 | } 582 | 583 | $active = $this->getActiveAttribute(); 584 | 585 | if (is_bool($active)) { 586 | return $active; 587 | } 588 | 589 | if ($active instanceof \Closure) { 590 | return call_user_func($active); 591 | } 592 | 593 | if ($this->hasRoute()) { 594 | return $this->getActiveStateFromRoute(); 595 | } 596 | 597 | return $this->getActiveStateFromUrl(); 598 | } 599 | 600 | /** 601 | * Determine the current item using route. 602 | * 603 | * @return bool 604 | */ 605 | protected function hasRoute() 606 | { 607 | return !empty($this->route); 608 | } 609 | 610 | /** 611 | * Get active status using route. 612 | * 613 | * @return bool 614 | */ 615 | protected function getActiveStateFromRoute() 616 | { 617 | $url = str_replace(url('/') . '/', '', $this->getUrl()); 618 | $url = str_replace('#' . (string) $this->fragment, '', $url); 619 | 620 | return $this->checkActiveState($url); 621 | } 622 | 623 | /** 624 | * Get active status using request url. 625 | * 626 | * @return bool 627 | */ 628 | protected function getActiveStateFromUrl() 629 | { 630 | $url = str_replace('#' . (string) $this->fragment, '', (string) $this->url); 631 | 632 | return $this->checkActiveState($url); 633 | } 634 | 635 | /** 636 | * Check the active state. 637 | * 638 | * @return bool 639 | */ 640 | protected function checkActiveState($url) 641 | { 642 | if (empty($url) || in_array($url, config('menu.home_urls', ['/']))) { 643 | return Request::is($url); 644 | } else { 645 | return Request::is($url, $url . '/*'); 646 | } 647 | } 648 | 649 | /** 650 | * Set order value. 651 | * 652 | * @param int $order 653 | * @return self 654 | */ 655 | public function order($order) 656 | { 657 | $this->order = $order; 658 | 659 | return $this; 660 | } 661 | 662 | /** 663 | * Set hide condition for current menu item. 664 | * 665 | * @param Closure 666 | * @return boolean 667 | */ 668 | public function hideWhen(Closure $callback) 669 | { 670 | $this->hideWhen = $callback; 671 | 672 | return $this; 673 | } 674 | 675 | /** 676 | * Determine whether the menu item is hidden. 677 | * 678 | * @return boolean 679 | */ 680 | public function hidden() 681 | { 682 | if (is_null($this->hideWhen)) { 683 | return false; 684 | } 685 | 686 | return call_user_func($this->hideWhen) == true; 687 | } 688 | 689 | /** 690 | * Get the instance as an array. 691 | * 692 | * @return array 693 | */ 694 | public function toArray() 695 | { 696 | return $this->getProperties(); 697 | } 698 | 699 | /** 700 | * Get property. 701 | * 702 | * @param string $key 703 | * 704 | * @return string|null 705 | */ 706 | public function __get($key) 707 | { 708 | return isset($this->$key) ? $this->$key : null; 709 | } 710 | } 711 | -------------------------------------------------------------------------------- /src/Presenters/Admin/Adminlte.php: -------------------------------------------------------------------------------- 1 | ' . PHP_EOL; 15 | } 16 | 17 | /** 18 | * {@inheritdoc }. 19 | */ 20 | public function getCloseTagWrapper() 21 | { 22 | return PHP_EOL . '' . PHP_EOL; 23 | } 24 | 25 | /** 26 | * {@inheritdoc }. 27 | */ 28 | public function getMenuWithoutDropdownWrapper($item) 29 | { 30 | return 'getActiveState($item) . '>getAttributes() . '>' . $item->getIcon() . ' ' . $item->title . '' . PHP_EOL; 31 | } 32 | 33 | /** 34 | * {@inheritdoc }. 35 | */ 36 | public function getActiveState($item, $state = ' class="active"') 37 | { 38 | return $item->isActive() ? $state : null; 39 | } 40 | 41 | /** 42 | * Get active state on child items. 43 | * 44 | * @param $item 45 | * @param string $state 46 | * 47 | * @return null|string 48 | */ 49 | public function getActiveStateOnChild($item, $state = 'active') 50 | { 51 | return $item->hasActiveOnChild() ? $state : null; 52 | } 53 | 54 | /** 55 | * {@inheritdoc }. 56 | */ 57 | public function getDividerWrapper() 58 | { 59 | return '
  • '; 60 | } 61 | 62 | /** 63 | * {@inheritdoc }. 64 | */ 65 | public function getHeaderWrapper($item) 66 | { 67 | return '
  • ' . $item->title . '
  • '; 68 | } 69 | 70 | /** 71 | * {@inheritdoc }. 72 | */ 73 | public function getMenuWithDropDownWrapper($item) 74 | { 75 | return '
  • 76 | 77 | ' . $item->getIcon() . ' ' . $item->title . ' 78 | 79 | 80 | 81 | 82 | 85 |
  • ' 86 | . PHP_EOL; 87 | } 88 | 89 | /** 90 | * Get multilevel menu wrapper. 91 | * 92 | * @param \Akaunting\Menu\MenuItem $item 93 | * 94 | * @return string` 95 | */ 96 | public function getMultiLevelDropdownWrapper($item) 97 | { 98 | return '
  • 99 | 100 | ' . $item->getIcon() . ' ' . $item->title . ' 101 | 102 | 103 | 104 | 105 | 108 |
  • ' 109 | . PHP_EOL; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Presenters/Admin/Argon.php: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | ' . PHP_EOL; 30 | } 31 | 32 | /** 33 | * {@inheritdoc }. 34 | */ 35 | public function getMenuWithoutDropdownWrapper($item) 36 | { 37 | $html = '' . PHP_EOL; 43 | 44 | return $html; 45 | } 46 | 47 | /** 48 | * {@inheritdoc }. 49 | */ 50 | public function getActiveState($item, $state = ' active') 51 | { 52 | return $item->isActive() ? $state : ''; 53 | } 54 | 55 | /** 56 | * Get active state on child items. 57 | * 58 | * @param $item 59 | * @param string $state 60 | * 61 | * @return null|string 62 | */ 63 | public function getActiveStateOnChild($item, $state = ' active show') 64 | { 65 | return $item->hasActiveOnChild() ? $state : null; 66 | } 67 | 68 | /** 69 | * Get active state on child items. 70 | * 71 | * @param $item 72 | * @param string $state 73 | * 74 | * @return null|string 75 | */ 76 | public function getShowStateOnChild($item, $state = ' show') 77 | { 78 | return $item->hasActiveOnChild() ? $state : null; 79 | } 80 | 81 | /** 82 | * {@inheritdoc }. 83 | */ 84 | public function getDividerWrapper() 85 | { 86 | return '
    '; 87 | } 88 | 89 | /** 90 | * {@inheritdoc }. 91 | */ 92 | public function getHeaderWrapper($item) 93 | { 94 | return ''; 95 | } 96 | 97 | /** 98 | * {@inheritdoc }. 99 | */ 100 | public function getMenuWithDropDownWrapper($item) 101 | { 102 | $id = Str::slug($item->title); 103 | 104 | return '' 115 | . PHP_EOL; 116 | } 117 | 118 | /** 119 | * Get multilevel menu wrapper. 120 | * 121 | * @param \Akaunting\Menu\MenuItem $item 122 | * 123 | * @return string` 124 | */ 125 | public function getMultiLevelDropdownWrapper($item) 126 | { 127 | $id = Str::slug($item->title); 128 | 129 | return '' 140 | . PHP_EOL; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Presenters/Admin/MetronicHorizontal.php: -------------------------------------------------------------------------------- 1 | ' . PHP_EOL; 21 | } 22 | 23 | /** 24 | * {@inheritdoc } 25 | */ 26 | public function getCloseTagWrapper() 27 | { 28 | return PHP_EOL . '' . PHP_EOL; 29 | } 30 | 31 | /** 32 | * {@inheritdoc } 33 | */ 34 | public function getMenuWithoutDropdownWrapper($item) 35 | { 36 | return '
  • getActiveState($item) . '>' . $item->getIcon() . '' . $item->title . '
  • '; 37 | } 38 | 39 | /** 40 | * {@inheritdoc } 41 | */ 42 | public function getActiveState($item) 43 | { 44 | return \Request::is($item->getRequest()) ? ' class="m-menu__item m-menu__item--rel active"' : 'class="m-menu__item m-menu__item--rel"'; 45 | } 46 | 47 | /** 48 | * {@inheritdoc } 49 | */ 50 | public function getDividerWrapper() 51 | { 52 | return ''; 53 | } 54 | 55 | /** 56 | * {@inheritdoc } 57 | */ 58 | public function getMenuWithDropDownWrapper($item) 59 | { 60 | if ($item->title == '...') { 61 | return '' . PHP_EOL; 74 | } else { 75 | return '' . PHP_EOL; 91 | } 92 | } 93 | 94 | public function getMultiLevelDropdownWrapper($item) 95 | { 96 | return '' . PHP_EOL; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Presenters/Admin/Tailwind.php: -------------------------------------------------------------------------------- 1 | 16 | 17 |
    18 | 19 | 28 |
    29 | ' . PHP_EOL; 30 | } 31 | 32 | /** 33 | * {@inheritdoc }. 34 | */ 35 | public function getMenuWithoutDropdownWrapper($item) 36 | { 37 | return '
  • 38 | getAttributes() . '> 39 | ' . $this->getIcon($item) . ' 40 | ' . $item->title . ' 41 | 42 | 43 |
  • ' 44 | . PHP_EOL; 45 | } 46 | 47 | /** 48 | * {@inheritdoc }. 49 | */ 50 | public function getActiveState($item, $state = 'active-menu') 51 | { 52 | return $item->isActive() ? $state : ''; 53 | } 54 | 55 | /** 56 | * Get active state on child items. 57 | * 58 | * @param $item 59 | * @param string $state 60 | * 61 | * @return null|string 62 | */ 63 | public function getActiveStateOnChild($item, $state = 'open') 64 | { 65 | return $item->hasActiveOnChild() ? $state : ''; 66 | } 67 | 68 | /** 69 | * Get active state on child items. 70 | * 71 | * @param $item 72 | * @param string $state 73 | * 74 | * @return null|string 75 | */ 76 | public function getShowStateOnChild($item, $state = 'open') 77 | { 78 | return $item->hasActiveOnChild() ? $state : ' '; 79 | } 80 | 81 | /** 82 | * {@inheritdoc }. 83 | */ 84 | public function getDividerWrapper() 85 | { 86 | return '
    '; 87 | } 88 | 89 | /** 90 | * {@inheritdoc }. 91 | */ 92 | public function getHeaderWrapper($item) 93 | { 94 | return ''; 95 | } 96 | 97 | /** 98 | * {@inheritdoc }. 99 | */ 100 | public function getMenuWithDropDownWrapper($item) 101 | { 102 | $id = Str::slug($item->title); 103 | 104 | return ' 105 |
    getActiveStateOnChild($item) . '> 106 | 107 |
    108 | ' . $this->getIcon($item) . ' 109 | ' . $item->title . ' 110 | ' . $this->getChevron($item) . ' 111 |
    112 |
    113 | 118 |
    ' 119 | . PHP_EOL; 120 | } 121 | 122 | /** 123 | * Get multilevel menu wrapper. 124 | * 125 | * @param \Akaunting\Menu\MenuItem $item 126 | * 127 | * @return string` 128 | */ 129 | public function getMultiLevelDropdownWrapper($item) 130 | { 131 | $id = Str::slug($item->title); 132 | 133 | return '
    getActiveStateOnChild($item) . '> 134 | 135 |
    136 | ' . $this->getIcon($item) . ' 137 | ' . $item->title . ' 138 | 139 | ' . $this->getChevron($item) . ' 140 |
    141 |
    142 | 147 |
    ' 148 | . PHP_EOL; 149 | } 150 | 151 | public function iconState($item, $state = '') 152 | { 153 | return $item->isActive() ? $state : '-outlined'; 154 | } 155 | 156 | public function iconChildState($item, $state = '') 157 | { 158 | return $item->hasActiveOnChild() ? $state : '-outlined'; 159 | } 160 | 161 | public function getClass($item) 162 | { 163 | $class = 'flex items-center text-purple'; 164 | 165 | $attributes = $item->attributes; 166 | 167 | if (!empty($attributes['class'])) { 168 | $class .= ' ' . $attributes['class']; 169 | } 170 | 171 | return $class; 172 | } 173 | 174 | public function getIcon($item) 175 | { 176 | if (empty($item->icon)) { 177 | return ''; 178 | } 179 | 180 | $state = empty($item->getChilds()) ? $this->iconState($item) : $this->iconChildState($item); 181 | 182 | return '
    183 | ' . $item->icon . ' 184 |
    ' . PHP_EOL; 185 | } 186 | 187 | public function getChevron($item) 188 | { 189 | $state = $this->chevronState($item); 190 | 191 | return 'expand' . $state . '' . PHP_EOL; 192 | } 193 | 194 | public function chevronState($item, $state = '_less') 195 | { 196 | return $item->hasActiveOnChild() ? $state : '_more'; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Presenters/Bootstrap3/Nav.php: -------------------------------------------------------------------------------- 1 | ' . PHP_EOL; 13 | } 14 | 15 | /** 16 | * {@inheritdoc }. 17 | */ 18 | public function getMenuWithDropDownWrapper($item) 19 | { 20 | return '' 29 | . PHP_EOL; 30 | } 31 | 32 | /** 33 | * Get multilevel menu wrapper. 34 | * 35 | * @param \Akaunting\Menu\MenuItem $item 36 | * 37 | * @return string` 38 | */ 39 | public function getMultiLevelDropdownWrapper($item) 40 | { 41 | return '' 50 | . PHP_EOL; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Presenters/Bootstrap3/NavPills.php: -------------------------------------------------------------------------------- 1 | ' . PHP_EOL; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Presenters/Bootstrap3/NavTab.php: -------------------------------------------------------------------------------- 1 | ' . PHP_EOL; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Presenters/Bootstrap3/Navbar.php: -------------------------------------------------------------------------------- 1 | ' . PHP_EOL; 15 | } 16 | 17 | /** 18 | * {@inheritdoc }. 19 | */ 20 | public function getCloseTagWrapper() 21 | { 22 | return PHP_EOL . '' . PHP_EOL; 23 | } 24 | 25 | /** 26 | * {@inheritdoc }. 27 | */ 28 | public function getMenuWithoutDropdownWrapper($item) 29 | { 30 | return 'getActiveState($item) . '>getAttributes() . '>' . $item->getIcon() . ' ' . $item->title . '' . PHP_EOL; 31 | } 32 | 33 | /** 34 | * {@inheritdoc }. 35 | */ 36 | public function getActiveState($item, $state = ' class="active"') 37 | { 38 | return $item->isActive() ? $state : null; 39 | } 40 | 41 | /** 42 | * Get active state on child items. 43 | * 44 | * @param $item 45 | * @param string $state 46 | * 47 | * @return null|string 48 | */ 49 | public function getActiveStateOnChild($item, $state = 'active') 50 | { 51 | return $item->hasActiveOnChild() ? $state : null; 52 | } 53 | 54 | /** 55 | * {@inheritdoc }. 56 | */ 57 | public function getDividerWrapper() 58 | { 59 | return '
  • '; 60 | } 61 | 62 | /** 63 | * {@inheritdoc }. 64 | */ 65 | public function getHeaderWrapper($item) 66 | { 67 | return ''; 68 | } 69 | 70 | /** 71 | * {@inheritdoc }. 72 | */ 73 | public function getMenuWithDropDownWrapper($item) 74 | { 75 | return '' 84 | . PHP_EOL; 85 | } 86 | 87 | /** 88 | * Get multilevel menu wrapper. 89 | * 90 | * @param \Akaunting\Menu\MenuItem $item 91 | * 92 | * @return string` 93 | */ 94 | public function getMultiLevelDropdownWrapper($item) 95 | { 96 | return '' 105 | . PHP_EOL; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Presenters/Bootstrap3/NavbarRight.php: -------------------------------------------------------------------------------- 1 | ' . PHP_EOL; 13 | } 14 | 15 | /** 16 | * {@inheritdoc }. 17 | */ 18 | public function getMenuWithDropDownWrapper($item) 19 | { 20 | return '' 29 | . PHP_EOL; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Presenters/Bootstrap3/Sidebar.php: -------------------------------------------------------------------------------- 1 | '; 18 | } 19 | 20 | /** 21 | * Get close tag wrapper. 22 | * 23 | * @return string 24 | */ 25 | public function getCloseTagWrapper() 26 | { 27 | return ''; 28 | } 29 | 30 | /** 31 | * Get menu tag without dropdown wrapper. 32 | * 33 | * @param \Akaunting\Menu\MenuItem $item 34 | * 35 | * @return string 36 | */ 37 | public function getMenuWithoutDropdownWrapper($item) 38 | { 39 | return 'getActiveState($item) . '> 40 | getAttributes() . '>' 41 | . $item->getIcon() . ' ' . $item->title . '' . PHP_EOL; 42 | } 43 | 44 | /** 45 | * {@inheritdoc }. 46 | */ 47 | public function getActiveState($item, $state = ' class="active"') 48 | { 49 | return $item->isActive() ? $state : null; 50 | } 51 | 52 | /** 53 | * Get active state on child items. 54 | * 55 | * @param $item 56 | * @param string $state 57 | * 58 | * @return null|string 59 | */ 60 | public function getActiveStateOnChild($item, $state = 'active') 61 | { 62 | return $item->hasActiveOnChild() ? $state : null; 63 | } 64 | 65 | /** 66 | * {@inheritdoc }. 67 | */ 68 | public function getDividerWrapper() 69 | { 70 | return '
  • '; 71 | } 72 | 73 | /** 74 | * {@inheritdoc }. 75 | */ 76 | public function getHeaderWrapper($item) 77 | { 78 | return ''; 79 | } 80 | 81 | /** 82 | * {@inheritdoc }. 83 | */ 84 | public function getMenuWithDropDownWrapper($item) 85 | { 86 | $id = Str::random(); 87 | 88 | return ' 89 | 101 | ' . PHP_EOL; 102 | } 103 | 104 | /** 105 | * Get multilevel menu wrapper. 106 | * 107 | * @param \Akaunting\Menu\MenuItem $item 108 | * 109 | * @return string` 110 | */ 111 | public function getMultiLevelDropdownWrapper($item) 112 | { 113 | return $this->getMenuWithDropDownWrapper($item); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Presenters/Foundation/Zurb.php: -------------------------------------------------------------------------------- 1 | 15 | ' . PHP_EOL; 24 | } 25 | 26 | /** 27 | * {@inheritdoc } 28 | */ 29 | public function getMenuWithoutDropdownWrapper($item) 30 | { 31 | return 'getActiveState($item) . '>' . $item->title . ''; 32 | } 33 | 34 | /** 35 | * {@inheritdoc } 36 | */ 37 | public function getActiveState($item) 38 | { 39 | return \Request::is($item->getRequest()) ? ' class="is-active"' : null; 40 | } 41 | 42 | /** 43 | * {@inheritdoc } 44 | */ 45 | public function getDividerWrapper() 46 | { 47 | return '
  • '; 48 | } 49 | 50 | /** 51 | * {@inheritdoc } 52 | */ 53 | public function getMenuWithDropDownWrapper($item) 54 | { 55 | return '' . PHP_EOL; 61 | } 62 | 63 | 64 | /** 65 | * {@inheritdoc } 66 | */ 67 | public function getMultiLevelDropdownWrapper($item) 68 | { 69 | return '
  • 70 | ' . $item->title . ' 71 | 74 |
  • ' . PHP_EOL; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Presenters/Presenter.php: -------------------------------------------------------------------------------- 1 | getChilds() as $child) { 91 | if ($child->hidden()) { 92 | continue; 93 | } 94 | 95 | if ($child->hasSubMenu()) { 96 | $results .= $this->getMultiLevelDropdownWrapper($child); 97 | } elseif ($child->isHeader()) { 98 | $results .= $this->getHeaderWrapper($child); 99 | } elseif ($child->isDivider()) { 100 | $results .= $this->getDividerWrapper(); 101 | } else { 102 | $results .= $this->getMenuWithoutDropdownWrapper($child); 103 | } 104 | } 105 | 106 | return $results; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Presenters/PresenterInterface.php: -------------------------------------------------------------------------------- 1 | publishes([ 18 | __DIR__ . '/Config/menu.php' => config_path('menu.php'), 19 | __DIR__ . '/Resources/views' => base_path('resources/views/vendor/akaunting/menu'), 20 | ], 'menu'); 21 | 22 | $this->app->singleton('menu', function ($app) { 23 | return new Menu($app['view'], $app['config']); 24 | }); 25 | 26 | if (file_exists($file = app_path('Support/menus.php'))) { 27 | require_once($file); 28 | } 29 | } 30 | 31 | /** 32 | * Register the application services. 33 | * 34 | * @return void 35 | */ 36 | public function register() 37 | { 38 | $this->registerHtmlPackage(); 39 | 40 | $this->mergeConfigFrom(__DIR__ . '/Config/menu.php', 'menu'); 41 | 42 | $this->loadViewsFrom(__DIR__ . '/Resources/views', 'menu'); 43 | } 44 | 45 | /** 46 | * Register "iluminate/html" package. 47 | */ 48 | private function registerHtmlPackage() 49 | { 50 | $this->app->register('Collective\Html\HtmlServiceProvider'); 51 | 52 | $aliases = [ 53 | 'HTML' => 'Collective\Html\HtmlFacade', 54 | 'Form' => 'Collective\Html\FormFacade', 55 | ]; 56 | 57 | AliasLoader::getInstance($aliases)->register(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/child/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/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 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/default.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/item/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/item/item.blade.php: -------------------------------------------------------------------------------- 1 | @if ($item->isDivider()) 2 |
  • 3 | @elseif ($item->isHeader()) 4 | 5 | @else 6 |
  • 7 | getAttributes() !!}> 8 | {!! $item->getIcon() !!} 9 | {{ $item->title }} 10 | 11 |
  • 12 | @endif 13 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/menu.blade.php: -------------------------------------------------------------------------------- 1 | @foreach ($items as $item) 2 | @if ($item->hasChilds()) 3 | @include('menu::bootstrap3.item.dropdown', compact('item')) 4 | @else 5 | @include('menu::bootstrap3.item.item', compact('item')) 6 | @endif 7 | @endforeach 8 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/nav-pills-justified.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/nav-pills-stacked.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/nav-pills.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/nav-tabs-justified.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/nav-tabs.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/navbar-left.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/navbar-right.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/Resources/views/bootstrap3/style.blade.php: -------------------------------------------------------------------------------- 1 | 50 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | get($name); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/MenuBuilderTest.php: -------------------------------------------------------------------------------- 1 | url('hello', 'world')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/MenuItemTest.php: -------------------------------------------------------------------------------- 1 | menu = app(Menu::class); 21 | } 22 | 23 | /** @test */ 24 | public function it_can_make_an_empty_menu_item() 25 | { 26 | $menuItem = MenuItem::make([]); 27 | 28 | $this->assertInstanceOf(MenuItem::class, $menuItem); 29 | } 30 | 31 | /** @test */ 32 | public function it_can_set_properties_on_menu_item() 33 | { 34 | $properties = [ 35 | 'url' => 'my.url', 36 | 'route' => 'my.route', 37 | 'title' => 'My Menu item', 38 | 'name' => 'my-menu-item', 39 | 'icon' => 'fa fa-user', 40 | 'parent' => 1, 41 | 'attributes' => [], 42 | 'active' => false, 43 | 'order' => 1, 44 | ]; 45 | 46 | $menuItem = MenuItem::make($properties); 47 | 48 | $this->assertEquals($properties, $menuItem->getProperties()); 49 | } 50 | 51 | /** @test */ 52 | public function it_can_fill_a_menu_item_with_allowed_properties() 53 | { 54 | $properties = [ 55 | 'url' => 'my.url', 56 | 'route' => 'my.route', 57 | 'title' => 'My Menu item', 58 | 'name' => 'my-menu-item', 59 | 'icon' => 'fa fa-user', 60 | 'parent' => 1, 61 | 'attributes' => [], 62 | 'active' => false, 63 | 'order' => 1, 64 | ]; 65 | 66 | $menuItem = MenuItem::make($properties); 67 | 68 | $this->assertEquals('my.url', $menuItem->url); 69 | $this->assertEquals('my.route', $menuItem->route); 70 | $this->assertEquals('My Menu item', $menuItem->title); 71 | $this->assertEquals('my-menu-item', $menuItem->name); 72 | $this->assertEquals('fa fa-user', $menuItem->icon); 73 | $this->assertSame(1, $menuItem->parent); 74 | $this->assertSame([], $menuItem->attributes); 75 | $this->assertFalse($menuItem->active); 76 | $this->assertSame(1, $menuItem->order); 77 | } 78 | 79 | /** @test */ 80 | public function it_can_set_icon_via_attributes() 81 | { 82 | $menuItem = MenuItem::make(['attributes' => ['icon' => 'fa fa-user']]); 83 | 84 | $this->assertEquals('fa fa-user', $menuItem->icon); 85 | } 86 | 87 | /** @test */ 88 | public function it_can_add_a_child_menu_item() 89 | { 90 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 91 | $menuItem->child(['title' => 'Child Item']); 92 | 93 | $this->assertCount(1, $menuItem->getChilds()); 94 | } 95 | 96 | /** @test */ 97 | public function it_can_get_ordered_children() 98 | { 99 | $this->app['config']->set('menu.ordering', true); 100 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 101 | $menuItem->child(['title' => 'Child Item', 'order' => 10]); 102 | $menuItem->child(['title' => 'First Child Item', 'order' => 1]); 103 | 104 | $children = $menuItem->getChilds(); 105 | $this->assertEquals('First Child Item', $children[1]->title); 106 | $this->assertEquals('Child Item', $children[0]->title); 107 | } 108 | 109 | /** @test */ 110 | public function it_can_create_a_dropdown_menu_item() 111 | { 112 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 113 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 114 | $sub->url('settings/account', 'Account'); 115 | $sub->url('settings/password', 'Password'); 116 | }); 117 | $this->assertCount(1, $menuItem->getChilds()); 118 | $this->assertCount(2, $menuItem->getChilds()[0]->getChilds()); 119 | } 120 | 121 | /** @test */ 122 | public function it_can_make_a_simple_route_menu_item() 123 | { 124 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 125 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 126 | $sub->route('settings.account', 'Account', ['user_id' => 1]); 127 | }); 128 | $children = $menuItem->getChilds()[0]->getChilds(); 129 | 130 | $this->assertCount(1, $children); 131 | $childMenuItem = Arr::first($children); 132 | $this->assertEquals('settings.account', $childMenuItem->route[0]); 133 | $this->assertEquals(['user_id' => 1], $childMenuItem->route[1]); 134 | } 135 | 136 | /** @test */ 137 | public function it_can_make_a_route_menu_item() 138 | { 139 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 140 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 141 | $sub->route('settings.account', 'Account', ['user_id' => 1], 1, ['my-attr' => 'value']); 142 | }); 143 | $children = $menuItem->getChilds()[0]->getChilds(); 144 | 145 | $this->assertCount(1, $children); 146 | $childMenuItem = Arr::first($children); 147 | $this->assertEquals('settings.account', $childMenuItem->route[0]); 148 | $this->assertEquals(['user_id' => 1], $childMenuItem->route[1]); 149 | $this->assertSame(1, $childMenuItem->order); 150 | $this->assertEquals(['my-attr' => 'value'], $childMenuItem->attributes); 151 | } 152 | 153 | /** @test */ 154 | public function it_can_make_a_simple_url_menu_item() 155 | { 156 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 157 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 158 | $sub->url('settings/account', 'Account'); 159 | }); 160 | $children = $menuItem->getChilds()[0]->getChilds(); 161 | 162 | $this->assertCount(1, $children); 163 | $childMenuItem = Arr::first($children); 164 | $this->assertEquals('settings/account', $childMenuItem->url); 165 | $this->assertEquals('Account', $childMenuItem->title); 166 | } 167 | 168 | /** @test */ 169 | public function it_can_make_a_url_menu_item() 170 | { 171 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 172 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 173 | $sub->url('settings/account', 'Account', 1, ['my-attr' => 'value']); 174 | }); 175 | $children = $menuItem->getChilds()[0]->getChilds(); 176 | 177 | $this->assertCount(1, $children); 178 | $childMenuItem = Arr::first($children); 179 | $this->assertEquals('settings/account', $childMenuItem->url); 180 | $this->assertEquals('Account', $childMenuItem->title); 181 | $this->assertSame(1, $childMenuItem->order); 182 | $this->assertEquals(['my-attr' => 'value'], $childMenuItem->attributes); 183 | } 184 | 185 | /** @test */ 186 | public function it_can_add_a_menu_item_divider() 187 | { 188 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 189 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 190 | $sub->url('settings/account', 'Account'); 191 | $sub->divider(); 192 | }); 193 | 194 | $children = $menuItem->getChilds()[0]->getChilds(); 195 | 196 | $this->assertCount(2, $children); 197 | $dividerMenuItem = $children[1]; 198 | $this->assertEquals('divider', $dividerMenuItem->name); 199 | $this->assertTrue($dividerMenuItem->isDivider()); 200 | } 201 | 202 | /** @test */ 203 | public function it_can_add_a_header_menu_item() 204 | { 205 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 206 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 207 | $sub->header('User Stuff'); 208 | $sub->url('settings/account', 'Account'); 209 | }); 210 | 211 | $children = $menuItem->getChilds()[0]->getChilds(); 212 | 213 | $this->assertCount(2, $children); 214 | $headerItem = $children[0]; 215 | $this->assertEquals('header', $headerItem->name); 216 | $this->assertEquals('User Stuff', $headerItem->title); 217 | $this->assertTrue($headerItem->isHeader()); 218 | } 219 | 220 | /** @test */ 221 | public function it_can_get_the_correct_url_for_url_type() 222 | { 223 | $menuItem = MenuItem::make(['url' => 'settings/account', 'title' => 'Parent Item']); 224 | 225 | $this->assertEquals('http://localhost/settings/account', $menuItem->getUrl()); 226 | } 227 | 228 | /** @test */ 229 | public function it_can_get_the_correct_url_for_route_type() 230 | { 231 | $this->app['router']->get('settings/account', ['as' => 'settings.account']); 232 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 233 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 234 | $sub->route('settings.account', 'Account'); 235 | }); 236 | $children = $menuItem->getChilds()[0]->getChilds(); 237 | $childMenuItem = Arr::first($children); 238 | 239 | $this->assertEquals('http://localhost/settings/account', $childMenuItem->getUrl()); 240 | } 241 | 242 | /** @test */ 243 | public function it_can_get_request_uri() 244 | { 245 | $menuItem = MenuItem::make(['url' => 'settings/account', 'title' => 'Parent Item']); 246 | 247 | $this->assertEquals('settings/account', $menuItem->getRequest()); 248 | } 249 | 250 | /** @test */ 251 | public function it_can_get_the_icon_html_attribute() 252 | { 253 | $menuItem = MenuItem::make(['url' => 'settings/account', 'title' => 'Parent Item', 'icon' => 'fa fa-user']); 254 | 255 | $this->assertEquals('', $menuItem->getIcon()); 256 | } 257 | 258 | /** @test */ 259 | public function it_returns_no_icon_if_none_exist() 260 | { 261 | $menuItem = MenuItem::make(['url' => 'settings/account', 'title' => 'Parent Item']); 262 | 263 | $this->assertNull($menuItem->getIcon()); 264 | } 265 | 266 | /** @test */ 267 | public function it_returns_default_icon_if_none_exist() 268 | { 269 | $menuItem = MenuItem::make(['url' => 'settings/account', 'title' => 'Parent Item']); 270 | 271 | $this->assertEquals('', $menuItem->getIcon('fa fa-user')); 272 | } 273 | 274 | /** @test */ 275 | public function it_can_get_item_properties() 276 | { 277 | $menuItem = MenuItem::make(['url' => 'settings/account', 'title' => 'Parent Item']); 278 | 279 | $this->assertEquals(['url' => 'settings/account', 'title' => 'Parent Item'], $menuItem->getProperties()); 280 | } 281 | 282 | /** @test */ 283 | public function it_can_get_item_html_attributes() 284 | { 285 | $menuItem = MenuItem::make(['url' => 'settings/account', 'title' => 'Parent Item', 'attributes' => ['my-attr' => 'value']]); 286 | 287 | $this->assertEquals(' my-attr="value"', $menuItem->getAttributes()); 288 | } 289 | 290 | /** @test */ 291 | public function it_can_check_for_a_submenu() 292 | { 293 | $menuItem = MenuItem::make(['title' => 'Parent Item']); 294 | $menuItem->dropdown('Dropdown item', function (MenuItem $sub) { 295 | $sub->header('User Stuff'); 296 | $sub->url('settings/account', 'Account'); 297 | }); 298 | 299 | $this->assertTrue($menuItem->hasSubMenu()); 300 | $this->assertTrue($menuItem->hasChilds()); 301 | } 302 | 303 | public function it_can_check_active_state_on_item() 304 | { 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /tests/MenuTest.php: -------------------------------------------------------------------------------- 1 | menu = app(Menu::class); 20 | } 21 | 22 | /** @test */ 23 | public function it_generates_an_empty_menu() 24 | { 25 | $this->menu->create('test', function (MenuBuilder $menu) { 26 | }); 27 | 28 | $expected = << 31 | 32 | 33 | 34 | TEXT; 35 | 36 | self::assertEquals($expected, $this->menu->get('test')); 37 | } 38 | 39 | /** @test */ 40 | public function it_makes_is_an_alias_for_create() 41 | { 42 | $this->menu->make('test', function (MenuBuilder $menu) { 43 | }); 44 | 45 | $expected = << 48 | 49 | 50 | 51 | TEXT; 52 | 53 | self::assertEquals($expected, $this->menu->get('test')); 54 | } 55 | 56 | /** @test */ 57 | public function it_render_is_an_alias_of_get() 58 | { 59 | $this->menu->make('test', function (MenuBuilder $menu) { 60 | }); 61 | 62 | $expected = << 65 | 66 | 67 | 68 | TEXT; 69 | 70 | self::assertEquals($expected, $this->menu->render('test')); 71 | } 72 | 73 | /** @test */ 74 | public function it_can_get_the_instance_of_a_menu() 75 | { 76 | $this->menu->create('test', function (MenuBuilder $menu) { 77 | }); 78 | 79 | $this->assertInstanceOf(MenuBuilder::class, $this->menu->instance('test')); 80 | } 81 | 82 | /** @test */ 83 | public function it_can_modify_a_menu_instance() 84 | { 85 | $this->menu->create('test', function (MenuBuilder $menu) { 86 | }); 87 | 88 | $this->menu->modify('test', function (MenuBuilder $builder) { 89 | $builder->url('hello', 'world'); 90 | }); 91 | 92 | $this->assertCount(1, $this->menu->instance('test')); 93 | } 94 | 95 | /** @test */ 96 | public function it_gets_a_partial_for_dropdown_styles() 97 | { 98 | $this->menu->create('test', function (MenuBuilder $menu) { 99 | }); 100 | 101 | $this->assertStringContainsString('.dropdown-submenu', $this->menu->style()); 102 | } 103 | 104 | /** @test */ 105 | public function it_can_get_all_menus() 106 | { 107 | $this->menu->create('main', function (MenuBuilder $menu) { 108 | }); 109 | $this->menu->create('footer', function (MenuBuilder $menu) { 110 | }); 111 | 112 | $this->assertCount(2, $this->menu->all()); 113 | } 114 | 115 | /** @test */ 116 | public function it_can_count_menus() 117 | { 118 | $this->menu->create('main', function (MenuBuilder $menu) { 119 | }); 120 | $this->menu->create('footer', function (MenuBuilder $menu) { 121 | }); 122 | 123 | $this->assertEquals(2, $this->menu->count()); 124 | } 125 | 126 | /** @test */ 127 | public function it_can_destroy_all_menus() 128 | { 129 | $this->menu->create('main', function (MenuBuilder $menu) { 130 | }); 131 | $this->menu->create('footer', function (MenuBuilder $menu) { 132 | }); 133 | 134 | $this->assertCount(2, $this->menu->all()); 135 | $this->menu->destroy(); 136 | $this->assertCount(0, $this->menu->all()); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('menu', [ 32 | 'styles' => [ 33 | 'bs3-navbar' => \Akaunting\Menu\Presenters\Bootstrap3\Navbar::class, 34 | 'bs3-navbar-right' => \Akaunting\Menu\Presenters\Bootstrap3\NavbarRight::class, 35 | 'bs3-nav-pills' => \Akaunting\Menu\Presenters\Bootstrap3\NavPills::class, 36 | 'bs3-nav-tab' => \Akaunting\Menu\Presenters\Bootstrap3\NavTab::class, 37 | 'bs3-sidebar' => \Akaunting\Menu\Presenters\Bootstrap3\Sidebar::class, 38 | 'bs3-navmenu' => \Akaunting\Menu\Presenters\Bootstrap3\Nav::class, 39 | ], 40 | 41 | 'ordering' => false, 42 | ]); 43 | } 44 | } 45 | --------------------------------------------------------------------------------