singleloginsystem/resources/views/components/dashboard-sidebar.blade.php
= 48d2c1a47e feature: ConnectionProvider integration
- Added `ConnectionProviderData`, `NavItemData`, and `NavSubItemData` DTOs for structured data handling.
- Implemented CRUD operations for `ConnectionProviderService`, including transaction-safe create, update, and delete methods.
- Introduced Livewire components for ConnectionProvider list, edit, and create pages with dynamic and reusable UI elements.
- Refactored routes to consolidate paths under the `apps` prefix and updated navigation to dynamically include new sections for providers.
- Enhanced sidebar with structured submenu for ConnectionProviders and Settings using dynamic collections.
- Updated existing Create and Edit templates to use consistent route naming and validation handling.
- Replaced hardcoded navigation items with dynamically generated structures leveraging the `NavItemData` collection.
2026-05-15 09:18:40 +00:00

246 lines
11 KiB
PHP

<?php
use App\Data\Ui\Sidebar\NavItemData;
use App\Data\Ui\Sidebar\NavSubItemData;
use Livewire\Component;
use Spatie\LaravelData\DataCollection;
new class extends Component {
/** Collapsed = icons-only mode */
public bool $collapsed = false;
public function mount(): void
{
$this->collapsed = session('sidebar_collapsed', false);
}
public function toggle(): void
{
$this->collapsed = !$this->collapsed;
session(['sidebar_collapsed' => $this->collapsed]);
}
/**
* @return DataCollection<int, NavItemData>
*/
public function navItems(): DataCollection
{
return NavItemData::collect([
new NavItemData(
label: 'Dashboard',
icon: 'lucide.house',
active_pattern: 'dashboard',
route: 'dashboard',
),
new NavItemData(
label: 'Apps',
icon: 'lucide.grid-2x2-plus',
active_pattern: 'apps.*',
sub_menu: NavSubItemData::collect([
new NavSubItemData(
label: 'All Apps',
route: 'apps.index',
active_pattern: 'apps.index'
),
new NavSubItemData(
label: 'Providers',
route: 'apps.connectionProviders.index',
active_pattern: 'apps.connectionProviders.*'
)
], DataCollection::class)
),
new NavItemData(
label: 'Settings',
icon: 'lucide.settings',
active_pattern: 'settings.*',
sub_menu: NavSubItemData::collect([
new NavSubItemData(
label: 'Profile',
route: 'profile.edit',
active_pattern: 'settings.profile',
),
new NavSubItemData(
label: 'Security',
route: 'security.edit',
active_pattern: 'settings.security',
),
], DataCollection::class)
),
], DataCollection::class);
}
};
?>
<aside
x-data="{ collapsed: @entangle('collapsed') }"
:class="collapsed ? 'w-12' : 'w-64'"
class="relative flex flex-col h-screen bg-white border-r border-gray-100
transition-[width] duration-300 ease-in-out shrink-0 overflow-hidden"
:aria-expanded="String(!collapsed)"
>
<div class="flex items-center h-20 px-2 border-b border-gray-100 shrink-0">
<a
href="{{ route('dashboard') }}"
wire:navigate
class="shrink-0 flex items-center justify-center w-8 h-8
rounded-lg bg-blue-600 text-gray-50
shadow-sm focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-cyan-400"
aria-label="Go to dashboard"
>
<x-mary-icon name='lucide.shield-cog' class="w-6 h-6"/>
</a>
<span
x-cloak
x-show="!collapsed"
x-transition:enter="transition-all duration-200 delay-150 ease-out"
x-transition:enter-start="opacity-0 -translate-x-2"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="transition-opacity duration-100"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="ml-3 text-sm font-semibold text-gray-900 whitespace-nowrap tracking-tight"
>
<h1 class="text-lg">Company SSO</h1>
<h2 class="text-sm font-light">Admin Panel</h2>
</span>
</div>
<nav
class="flex-1 py-3 px-2 space-y-1 overflow-y-auto overflow-x-hidden"
aria-label="Main navigation"
>
@foreach ($this->navItems() as $item)
@php $isActive = request()->routeIs($item->active_pattern); @endphp
@if ($item->sub_menu)
{{-- ITEM WITH SUBMENU --}}
<div x-data="{ expanded: {{ $isActive ? 'true' : 'false' }} }">
<button
@click="expanded = !expanded; if(collapsed) collapsed = false"
@class([
'group relative w-full flex items-center justify-between px-2 py-2.5 rounded-lg',
'text-sm font-medium transition-colors duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400',
'bg-gray-100 text-gray-900' => $isActive,
'text-gray-500 hover:bg-gray-50 hover:text-gray-800' => ! $isActive,
])
aria-haspopup="true"
:aria-expanded="String(expanded)"
>
<div class="flex items-center gap-3">
<x-mary-icon :name="$item->icon" class="w-4 h-4 shrink-0"/>
<span
x-cloak
x-show="!collapsed"
class="whitespace-nowrap overflow-hidden leading-none"
>
{{ $item->label }}
</span>
</div>
{{-- Chevron Arrow (Hidden when collapsed) --}}
<span x-cloak x-show="!collapsed" class="shrink-0 transition-transform duration-200"
:class="expanded ? 'rotate-90' : ''">
<x-mary-icon name="lucide.chevron-right" class="w-4 h-4"/>
</span>
{{-- Tooltip (Collapsed Mode) --}}
<span x-cloak x-show="collapsed" role="tooltip"
class="pointer-events-none absolute left-full ml-3 z-50 whitespace-nowrap rounded-md bg-gray-900 px-2.5 py-1.5 text-xs font-medium text-white shadow-xl opacity-0 group-hover:opacity-100 transition-opacity duration-150 delay-75">
{{ $item->label }}
<span
class="absolute right-full top-1/2 -translate-y-1/2 border-[5px] border-transparent border-r-gray-900"
aria-hidden="true"></span>
</span>
</button>
{{-- Submenu Dropdown --}}
<div x-cloak x-show="expanded && !collapsed" x-collapse>
<div class="mt-1 space-y-1 pl-9 pr-2">
@foreach ($item->sub_menu as $subItem)
@php $isSubActive = request()->routeIs($subItem->active_pattern); @endphp
<a
href="{{ route($subItem->route) }}"
wire:navigate
@class([
'block px-3 py-2 rounded-md text-sm font-medium transition-colors',
'bg-gray-100 text-blue-700' => $isSubActive,
'text-gray-500 hover:bg-gray-50 hover:text-gray-900' => ! $isSubActive,
])
>
{{ $subItem->label }}
</a>
@endforeach
</div>
</div>
</div>
@else
{{-- STANDARD ITEM --}}
<a
href="{{ route($item->route) }}"
wire:navigate
@class([
'group relative flex items-center gap-3 px-2 py-2.5 rounded-lg',
'text-sm font-medium transition-colors duration-150',
'bg-gray-100 text-gray-900' => $isActive,
'text-gray-500 hover:bg-gray-50 hover:text-gray-800' => ! $isActive,
])
aria-current="{{ $isActive ? 'page' : 'false' }}"
>
@if ($isActive)
<span class="absolute left-0 inset-y-2 w-0.5 rounded-r-full bg-blue-600"
aria-hidden="true"></span>
@endif
<x-mary-icon :name="$item->icon" class="w-4 h-4 shrink-0"/>
<span
x-cloak
x-show="!collapsed"
class="whitespace-nowrap overflow-hidden leading-none"
>
{{ $item->label }}
</span>
{{-- Tooltip (Collapsed Mode) --}}
<span x-cloak x-show="collapsed" role="tooltip"
class="pointer-events-none absolute left-full ml-3 z-50 whitespace-nowrap rounded-md bg-gray-900 px-2.5 py-1.5 text-xs font-medium text-white shadow-xl opacity-0 group-hover:opacity-100 transition-opacity duration-150 delay-75">
{{ $item->label }}
<span
class="absolute right-full top-1/2 -translate-y-1/2 border-[5px] border-transparent border-r-gray-900"
aria-hidden="true"></span>
</span>
</a>
@endif
@endforeach
</nav>
{{-- Toggle Footer --}}
<div class="border-t border-gray-100 px-2 py-3 shrink-0">
<button
wire:click="toggle"
type="button"
class="group w-full flex items-center gap-3 px-2 py-2.5 rounded-lg
text-sm font-medium text-gray-400
hover:bg-gray-50 hover:text-gray-700
transition-colors duration-150
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-400"
:aria-label="collapsed ? 'Expand sidebar' : 'Collapse sidebar'"
>
<span class="flex-shrink-0 flex items-center justify-center w-5 h-5" aria-hidden="true">
<svg :class="collapsed ? 'rotate-180' : 'rotate-0'"
class="w-4 h-4 transition-transform duration-300 ease-in-out" fill="none" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/>
</svg>
</span>
<span x-cloak x-show="!collapsed" class="whitespace-nowrap">
Collapse
</span>
</button>
</div>
</aside>