Skip to content
This repository was archived by the owner on Mar 12, 2024. It is now read-only.

Commit 1d3af21

Browse files
committed
recursive menu
Signed-off-by: Dieter Coopman <dieter@deltasolutions.be>
1 parent a0b2592 commit 1d3af21

File tree

7 files changed

+161
-93
lines changed

7 files changed

+161
-93
lines changed

resources/views/menu-ui/menu.blade.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
<x-jet-label for="name" value="{{ __('Menu parent') }}"/>
2929
<select wire:model.defer="menu.parent_id" id="parent_id" name="parent_id" autocomplete="activity_id" class="mt-1 block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm">
3030
<option value="0">{{ __('Choose') }}</option>
31-
@foreach($parents as $id => $name)
32-
<option value="{{ $id }}">{{ $name }}</option>
31+
@foreach($parents as $id => $label)
32+
<option value="{{ $id }}">{{ $label }}</option>
3333
@endforeach
3434
</select>
3535
<x-jet-input-error for="permission.parent" class="mt-2"/>
@@ -43,7 +43,7 @@
4343
</x-jet-form-section>
4444
<x-jet-section-border/>
4545

46-
<x-jet-form-section submit="updatePermission">
46+
<x-jet-form-section submit="updateMenu">
4747

4848
<x-slot name="title">
4949
{{ __('Perks') }}
@@ -53,15 +53,21 @@
5353
{{ __('Perks are additional features ') }}
5454
</x-slot>
5555
<x-slot name="form">
56+
<div class="col-span-6 sm:col-span-4">
57+
<x-jet-label for="sort_order" value="{{ __('Sort order') }}"/>
58+
<x-jet-input id="sort_order" type="text" class="mt-1 block w-full" wire:model.defer="menu.sort_order" autocomplete="sort order "/>
59+
</div>
5660
<div class="col-span-6 sm:col-span-4">
5761
<x-jet-label for="permission" value="{{ __('Permission') }}"/>
5862
<x-jet-input id="permission" type="text" class="mt-1 block w-full" wire:model.debounce.250ms="menu.permission" autocomplete="route"/>
5963
</div>
64+
@if(empty($menu->parent_id))
6065
<div class="col-span-6 sm:col-span-4">
6166
<x-jet-label for="icon" value="{{ __('Menu icon') }}" />
6267
<x-jet-input id="icon" type="text" class="mt-1 block w-full" wire:model.defer="menu.icon" autocomplete="icon"/>
6368
<x-jet-input-error for="menu.icon" class="mt-2"/>
6469
</div>
70+
@endif
6571
</x-slot>
6672
<x-slot name="actions">
6773
<x-jet-action-message class="mr-3" on="saved">
Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1-
<div class="w-60">
1+
<ul class="rounded-md ring-1 ring-black ring-opacity-5 py-1 bg-white z-50 w-48 rounded-md shadow-lg bg-white rounded-sm transform scale-0 group-hover:scale-100 absolute transition duration-150 ease-in-out origin-right">
22
@foreach($menu->menu as $__menu)
3-
<x-jet-dropdown-link href="{{ route('users.index') }}">
4-
{{ $__menu->name }}
5-
</x-jet-dropdown-link>
6-
@if ($__menu->menu->count())
7-
<div class="absolute z-50 mt-2">
8-
@include('LLoadoutEnforce-views::menu-ui.navigation-item', array('menu' => $__menu))
9-
</div>
3+
@if (!is_array($__menu->menu) && $__menu->menu->count())
4+
<li class="rounded-sm relative px-4 py-2 hover:bg-gray-100">
5+
<button class="w-full text-left flex items-center outline-none focus:outline-none">
6+
<span class="pr-1 flex-1">{{ $__menu->name }}</span>
7+
<span class="mr-auto">
8+
<svg class="fill-current h-4 w-4 transition duration-150 ease-in-out" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
9+
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
10+
</svg>
11+
</span>
12+
</button>
13+
<ul class="bg-white border rounded-sm absolute top-0 right-0 transition duration-150 ease-in-out origin-top-left min-w-32">
14+
@include('LLoadoutEnforce-views::menu-ui.navigation-item', array('menu' => $__menu))
15+
</ul>
16+
</li>
17+
@else
18+
<li class="px-3 py-1 hover:bg-gray-100 block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out">
19+
<a class="" href="{{ Route::has($__menu->route) ? route($__menu->route) : '#' }}">{{ $__menu->name }}</a></li>
1020
@endif
1121
@endforeach
12-
</div>
22+
</ul>
23+
Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,60 @@
1-
@foreach($menus as $menu)
2-
<div class="hidden sm:flex sm:items-center sm:ml-6">
3-
<div class="ml-3 relative text-sm">
4-
<x-jet-dropdown align="right" width="60">
5-
<x-slot name="trigger">
1+
<div class="inline-flex">
2+
@foreach($menus as $key => $menu)
3+
<div wire:key="menu-{{ $key }}" class="hidden sm:flex sm:items-center sm:ml-10 border-b-2 border-transparent hover:text-gray-700 hover:border-gray-300">
4+
<div class="relative text-sm">
5+
<div class="group inline-block">
6+
<i class="absolute top-1.5 -left-3.5 text-gray-500" style="width: 15px;height:15px">{!! $menu->icon !!}</i>
7+
<a href="{{ Route::has($menu->route) ? route($menu->route) : '#' }}" class="inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 focus:outline-none focus:text-gray-700 transition duration-150 ease-in-out">
68
{{ $menu->name }}
7-
</x-slot>
8-
<x-slot name="content">
9+
</a>
10+
@if($menu->menu->count() > 0)
911
@include("LLoadoutEnforce-views::menu-ui.navigation-item", ['menu' => $menu])
10-
</x-slot>
11-
</x-jet-dropdown>
12+
@endif
13+
</div>
1214
</div>
1315
</div>
1416
@endforeach
15-
<div class="hidden sm:flex sm:items-center sm:ml-6">
16-
<div class="ml-3 relative text-sm">
17-
<x-jet-dropdown align="right" width="60" :active="request()->routeIs('users.*')">
18-
<x-slot name="trigger">
19-
{{ __('User management') }}
20-
</x-slot>
21-
<x-slot name="content">
22-
<div class="w-60">
23-
<x-jet-dropdown-link href="{{ route('users.index') }}">
24-
{{ __('Manage users') }}
25-
</x-jet-dropdown-link>
26-
<x-jet-dropdown-link href="{{ route('users.roles') }}">
27-
{{ __('Manage roles') }}
28-
</x-jet-dropdown-link>
29-
<x-jet-dropdown-link href="{{ route('users.access') }}">
30-
{{ __('Manage access') }}
31-
</x-jet-dropdown-link>
32-
</div>
33-
</x-slot>
34-
</x-jet-dropdown>
35-
</div>
36-
</div>
37-
<div class="hidden sm:flex sm:items-center sm:ml-6">
38-
<div class="ml-3 relative text-sm">
39-
<x-jet-dropdown align="right" width="60" :active="request()->routeIs('users')">
40-
<x-slot name="trigger">
41-
{{ __('Developer menu') }}
42-
</x-slot>
43-
<x-slot name="content">
44-
<div class="w-60">
45-
<x-jet-dropdown-link href="{{ route('permissions') }}">
46-
{{ __('Permissions') }}
47-
</x-jet-dropdown-link>
48-
<x-jet-dropdown-link href="{{ route('menus') }}">
49-
{{ __('Menus') }}
50-
</x-jet-dropdown-link>
51-
</div>
52-
</x-slot>
53-
</x-jet-dropdown>
54-
</div>
55-
</div>
17+
</div>
18+
19+
<style>
20+
/* since nested groupes are not supported we have to use
21+
regular css for the nested dropdowns
22+
*/
23+
li > ul {
24+
transform: translatex(100%) scale(0)
25+
}
26+
27+
li:hover > ul {
28+
transform: translatex(101%) scale(1)
29+
}
30+
31+
li > button svg {
32+
transform: rotate(-90deg)
33+
}
34+
35+
li:hover > button svg {
36+
transform: rotate(-270deg)
37+
}
38+
39+
/* Below styles fake what can be achieved with the tailwind config
40+
you need to add the group-hover variant to scale and define your custom
41+
min width style.
42+
See https://codesandbox.io/s/tailwindcss-multilevel-dropdown-y91j7?file=/index.html
43+
for implementation with config file
44+
*/
45+
.group:hover .group-hover\:scale-100 {
46+
transform: scale(1)
47+
}
5648
49+
.group:hover .group-hover\:-rotate-180 {
50+
transform: rotate(180deg)
51+
}
5752
53+
.scale-0 {
54+
transform: scale(0)
55+
}
5856
57+
.min-w-32 {
58+
min-width: 8rem
59+
}
60+
</style>

src/Http/Livewire/Menu.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Menu extends Component
99

1010
public $menu;
1111
public $parents;
12+
public $htmlicon;
1213

1314
protected function rules()
1415
{
@@ -17,7 +18,8 @@ protected function rules()
1718
'menu.route' => 'nullable|string',
1819
'menu.icon' => 'nullable|string',
1920
'menu.permission' => 'nullable|string',
20-
'menu.parent_id' => 'nullable|integer'
21+
'menu.parent_id' => 'nullable|integer',
22+
'menu.sort_order' => 'nullable|integer',
2123
];
2224
}
2325

@@ -39,7 +41,7 @@ public function updatedMenuPermission()
3941

4042
public function render()
4143
{
42-
$menus = \LLoadoutEnforce\Models\Menu::whereNull('parent_id')->with('menu')->get();
44+
$menus = $this->menusWithPrefix();
4345
return view('LLoadoutEnforce-views::menu-ui.menu', compact('menus'));
4446

4547
}
@@ -49,7 +51,7 @@ public function delete()
4951
$this->menu->delete();
5052
}
5153

52-
public function updatePermission()
54+
public function updateMenu()
5355
{
5456
$this->validate();
5557
$this->menu->save();
@@ -58,4 +60,11 @@ public function updatePermission()
5860

5961
}
6062

63+
private function menusWithPrefix()
64+
{
65+
return \LLoadoutEnforce\Models\Menu::with('menu')->get()->transform(function($menu){
66+
67+
});
68+
}
69+
6170
}

src/Http/Livewire/Navigation.php

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,61 @@
66

77
class Navigation extends Component
88
{
9-
public $menus;
10-
protected $listeners = ['menuUpdated' => 'mount'];
9+
protected $listeners = ['menuUpdated' => 'render'];
1110

12-
13-
14-
public function mount()
11+
public function render()
1512
{
16-
$this->getMenus();
13+
$menus = $this->getMenus();
14+
return view('LLoadoutEnforce-views::menu-ui.navigation', compact('menus'));
1715
}
1816

19-
public function render()
17+
private function getMenus()
2018
{
21-
return view('LLoadoutEnforce-views::menu-ui.navigation');
19+
$menus = \LLoadoutEnforce\Models\Menu::whereNull('parent_id')->with('menu')->get()->toBase();
20+
$menus->push($this->buildUsermenu());
21+
$menus->push($this->buildDevelopermenu());
22+
return $menus;
2223
}
2324

24-
private function getMenus()
25+
private function buildUsermenu()
2526
{
26-
$this->menus = \LLoadoutEnforce\Models\Menu::whereNull('parent_id')->with('menu')->get();
27+
$menu = new \LLoadoutEnforce\Models\Menu();
28+
$menu->name = __('User management');
29+
30+
$subMenu = new \LLoadoutEnforce\Models\Menu();
31+
$subMenu->name = __('Manage users');
32+
$subMenu->route = 'users.index';
33+
$menu->menu[] = $subMenu;
34+
35+
$subMenu = new \LLoadoutEnforce\Models\Menu();
36+
$subMenu->name = __('Manage roles');
37+
$subMenu->route = 'users.roles';
38+
$menu->menu[] = $subMenu;
39+
40+
$subMenu = new \LLoadoutEnforce\Models\Menu();
41+
$subMenu->name = __('Manage access');
42+
$subMenu->route = 'users.access';
43+
$menu->menu[] = $subMenu;
44+
45+
return $menu;
2746
}
2847

48+
private function buildDevelopermenu()
49+
{
50+
$menu = new \LLoadoutEnforce\Models\Menu();
51+
$menu->name = __('Developer menu');
52+
53+
$subMenu = new \LLoadoutEnforce\Models\Menu();
54+
$subMenu->name = __('Permissions');
55+
$subMenu->route = 'permissions';
56+
$menu->menu[] = $subMenu;
57+
58+
$subMenu = new \LLoadoutEnforce\Models\Menu();
59+
$subMenu->name = __('Menus');
60+
$subMenu->route = 'menus';
61+
$menu->menu[] = $subMenu;
62+
63+
return $menu;
64+
}
2965

3066
}

src/LLoadoutEnforceServiceProvider.php

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,10 @@ public function configurePackage(Package $package): void
2424
$this->loadAssets()->publishAssets();
2525
}
2626

27-
public function boot()
28-
{
29-
Livewire::component('access', Access::class);
30-
Livewire::component('permission', Permission::class);
31-
Livewire::component('permissions-table', PermissionsTable::class);
32-
33-
Livewire::component('menu', Menu::class);
34-
Livewire::component('menus-table', MenusTable::class);
35-
Livewire::component('navigation', Navigation::class);
36-
37-
Livewire::component('roles-table', RolesTable::class);
38-
Livewire::component('users-table', UsersTable::class);
39-
Livewire::component('user', User::class);
40-
Livewire::component('role', Role::class);
41-
}
42-
43-
4427
private function loadAssets()
4528
{
4629
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'LLoadoutEnforce-views');
30+
$this->loadLivewireComponents();
4731
$this->loadRoutesFrom(__DIR__ . '/routes/web.php');
4832
return $this;
4933
}
@@ -69,4 +53,20 @@ private function publishAssets()
6953

7054
return $this;
7155
}
56+
57+
private function loadLivewireComponents()
58+
{
59+
Livewire::component('access', Access::class);
60+
Livewire::component('permission', Permission::class);
61+
Livewire::component('permissions-table', PermissionsTable::class);
62+
63+
Livewire::component('menu', Menu::class);
64+
Livewire::component('menus-table', MenusTable::class);
65+
Livewire::component('navigation', Navigation::class);
66+
67+
Livewire::component('roles-table', RolesTable::class);
68+
Livewire::component('users-table', UsersTable::class);
69+
Livewire::component('user', User::class);
70+
Livewire::component('role', Role::class);
71+
}
7272
}

src/Models/Menu.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ class Menu extends Model
1212
*/
1313
public function menu()
1414
{
15-
return $this->hasMany(Menu::class, 'parent_id', 'id')->with('menu');
15+
return $this->hasMany(Menu::class, 'parent_id', 'id')->withCount('parent')->with('menu');
16+
}
17+
18+
public function parent(){
19+
return $this->belongsTo(Menu::class,'parent_id','id');
1620
}
1721
}

0 commit comments

Comments
 (0)