Files
Virtnet.bond/themes/lucentui/views/components/navigation/index.blade.php
Joseph.Rawlings 8baf7d6c7d feat: Enhance UI components and add command palette
- Updated dropdown component styles and transitions for improved UX.
- Refined checkbox, input, radio, select, and textarea components with consistent error messaging and styling.
- Introduced a new card component for better layout options.
- Added a stat card component for displaying metrics with trends.
- Implemented a command palette for global search and navigation, accessible via Cmd/Ctrl+K.
- Enhanced modal and navigation components with new styles and transitions.
- Improved layout styles for better responsiveness and visual appeal.
2026-04-22 10:40:33 -04:00

251 lines
14 KiB
PHP

<script data-navigate-track>
// Apply lazy loading to images - runs on initial load and Livewire navigation
(function() {
const applyLazyLoading = () => {
document.querySelectorAll('img:not([loading])')
.forEach(img => img.loading = 'lazy');
};
// Run immediately if DOM is ready, otherwise wait
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', applyLazyLoading);
} else {
applyLazyLoading();
}
// Also run on Livewire navigation
document.addEventListener('livewire:navigated', applyLazyLoading);
})();
</script>
<style>
/* ── Brand text animated gradient ── */
.text-flow-effect {
background: linear-gradient(
90deg,
hsl(var(--color-primary)) 0%,
hsl(var(--color-secondary)) 40%,
hsl(var(--color-primary)) 60%,
hsl(var(--color-secondary)) 100%
);
background-size: 300% auto;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: textFlowAnimation 6s ease-in-out infinite;
}
@keyframes textFlowAnimation {
0% { background-position: 0% center; }
50% { background-position: 300% center; }
100% { background-position: 0% center; }
}
/* ── Nav link animated underline ── */
.nav-link-modern {
position: relative;
}
.nav-link-modern::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
border-radius: 1px;
background: hsl(var(--color-primary));
transition: width 0.25s ease, left 0.25s ease;
}
.nav-link-modern:hover::after {
width: 60%;
left: 20%;
}
/* ── Avatar ring using theme colors ── */
.avatar-ring {
padding: 2px;
background: linear-gradient(135deg, hsl(var(--color-primary)), hsl(var(--color-secondary)));
border-radius: 9999px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.avatar-ring:hover {
transform: scale(1.05);
box-shadow: 0 0 10px hsl(var(--color-primary) / 0.3);
}
.avatar-ring img {
display: block;
border-radius: 9999px;
border: 2px solid hsl(var(--color-background-secondary));
}
</style>
<nav x-data="{ scrolled: false, slideOverOpen: false }"
x-init="
window.addEventListener('scroll', () => { scrolled = window.scrollY > 20 });
$watch('slideOverOpen', value => { document.documentElement.style.overflow = value ? 'hidden' : '' });
"
:class="{
'shadow-elevated border-primary/20 backdrop-blur-xl bg-background-secondary/70': scrolled,
'border-neutral bg-background-secondary': !scrolled
}"
class="w-full px-4 lg:px-8 border-b flex flex-col lg:flex-row justify-between fixed top-0 z-20 h-auto transition-all duration-300">
<div class="relative z-50 w-full h-auto">
<div class="flex flex-row items-center justify-between w-full h-16">
<!-- Logo -->
<div class="flex flex-row items-center">
<a href="{{ route('home') }}" class="flex items-center h-10 gap-2.5 group" wire:navigate>
<x-logo class="h-10 transition-transform duration-200 group-hover:scale-[1.03]" />
<span class="text-xl font-extrabold tracking-tight leading-none flex items-center text-flow-effect">{{ config('app.name') }}</span>
</a>
<!-- Desktop menu: lg and above -->
<div class="hidden lg:flex flex-row items-center ml-8 gap-0.5">
@foreach (\App\Classes\Navigation::getLinks() as $nav)
@if (isset($nav['children']) && count($nav['children']) > 0)
<div class="relative">
<x-dropdown>
<x-slot:trigger>
<span class="nav-link-modern flex flex-row items-center px-3 py-2 text-sm font-semibold whitespace-nowrap text-base hover:text-primary transition-colors duration-200">{{ $nav['name'] }}</span>
</x-slot:trigger>
<x-slot:content>
@foreach ($nav['children'] as $child)
<x-navigation.link :href="route($child['route'], $child['params'] ?? null)"
:spa="isset($child['spa']) ? $nav['spa'] : true">
{{ $child['name'] }}
</x-navigation.link>
@endforeach
</x-slot:content>
</x-dropdown>
</div>
@else
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)"
:spa="isset($nav['spa']) ? $nav['spa'] : true"
class="nav-link-modern flex items-center px-3 py-2">
{{ $nav['name'] }}
</x-navigation.link>
@endif
@endforeach
</div>
</div>
<!-- Right-side controls -->
<div class="flex items-center gap-3">
<livewire:components.cart />
<!-- Command palette trigger (Cmd/Ctrl+K) -->
<button type="button"
@click="$dispatch('open-command-palette')"
class="hidden md:inline-flex items-center gap-2 h-10 px-3 rounded-lg text-sm text-base/70 hover:text-base hover:bg-neutral/40 border border-neutral/50 hover:border-primary/40 transition-all duration-200"
aria-label="Open command palette">
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24" fill="currentColor"><path d="M18.031 16.617 22.314 20.9l-1.414 1.414-4.283-4.283A8.5 8.5 0 1 1 18.03 16.617Zm-2.006-.742A6.5 6.5 0 1 0 7 9.5a6.5 6.5 0 0 0 9.025 6.375l.885.885Z"/></svg>
<span class="hidden lg:inline text-xs">Search</span>
<kbd class="hidden lg:inline-block text-[10px] font-semibold px-1.5 py-0.5 rounded border border-neutral/50 bg-background/50">⌘K</kbd>
</button>
<!-- Dark/Light mode toggle (left of user icon / login & register) -->
<button @click="darkMode = !darkMode" type="button"
class="w-10 h-10 flex items-center justify-center rounded-lg hover:bg-neutral/50 transition-colors duration-200"
:aria-label="darkMode ? 'Switch to light mode' : 'Switch to dark mode'">
<svg x-show="!darkMode" xmlns="http://www.w3.org/2000/svg" class="size-4 text-base" viewBox="0 0 24 24" fill="currentColor"><path d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z"/></svg>
<svg x-show="darkMode" x-cloak xmlns="http://www.w3.org/2000/svg" class="size-4 text-base" viewBox="0 0 24 24" fill="currentColor"><path d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7Z"/></svg>
</button>
<!-- User account dropdown (desktop) -->
@if(auth()->check())
<div class="hidden lg:block">
<x-dropdown>
<x-slot:trigger>
<div class="flex items-center gap-2.5 cursor-pointer group">
<div class="avatar-ring">
<img src="{{ auth()->user()->avatar }}" class="w-8 h-8" alt="avatar" />
</div>
<span class="text-sm font-semibold text-base group-hover:text-primary transition-colors duration-200">{{ Auth::user()->first_name }}</span>
</div>
</x-slot:trigger>
<x-slot:content>
@foreach (\App\Classes\Navigation::getAccountDropdownLinks() as $nav)
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)" :spa="isset($nav['spa']) ? $nav['spa'] : true">
{{ $nav['name'] }}
</x-navigation.link>
@endforeach
<div class="border-t border-neutral/50 mt-1 pt-1">
<livewire:auth.logout />
</div>
</x-slot:content>
</x-dropdown>
</div>
@else
<div class="hidden lg:flex items-center gap-2.5">
<a href="{{ route('login') }}" wire:navigate>
<button class="px-4 py-2 text-sm font-medium text-base hover:text-primary rounded-lg transition-all duration-200">
{{ __('navigation.login') }}
</button>
</a>
@if(!config('settings.registration_disabled', false))
<a href="{{ route('register') }}" wire:navigate>
<button class="px-5 py-2 text-sm font-semibold text-white bg-gradient-to-r from-primary to-secondary rounded-lg transition-all duration-200 shadow-md shadow-primary/15 hover:shadow-lg hover:shadow-primary/25 hover:brightness-110 active:scale-[0.98]">
{{ __('navigation.register') }}
</button>
</a>
@endif
</div>
@endif
<!-- Mobile menu toggle -->
<button @click="slideOverOpen = !slideOverOpen"
class="relative w-10 h-10 flex lg:hidden items-center justify-center rounded-xl bg-background hover:bg-neutral/30 transition-all duration-200 group"
aria-label="Toggle Menu">
<span x-show="!slideOverOpen" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 rotate-90 scale-75" x-transition:enter-end="opacity-100 rotate-0 scale-100" class="absolute inset-0 flex items-center justify-center">
<x-ri-menu-fill class="size-5 text-base/80 group-hover:text-foreground transition-colors duration-200" />
</span>
<span x-show="slideOverOpen" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 -rotate-90 scale-75" x-transition:enter-end="opacity-100 rotate-0 scale-100" class="absolute inset-0 flex items-center justify-center">
<x-ri-close-fill class="size-5 text-base/80 group-hover:text-foreground transition-colors duration-200" />
</span>
</button>
</div>
</div>
<!-- Mobile slide-over menu (<1024px) -->
<template x-teleport="body">
<div x-show="slideOverOpen" @keydown.window.escape="slideOverOpen=false" x-cloak class="fixed inset-0 z-[99]" aria-modal="true">
{{-- Backdrop with blur and fade --}}
<div x-show="slideOverOpen"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="slideOverOpen = false"
class="absolute inset-0 bg-black/40 backdrop-blur-sm"></div>
{{-- Slide-over panel --}}
<div x-show="slideOverOpen"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-full"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 translate-y-full"
@click.stop
class="absolute left-0 right-0 top-16 bg-background-secondary/95 backdrop-blur-xl border-t border-neutral/50 shadow-2xl overflow-y-auto flex flex-col rounded-t-2xl"
style="height:calc(100dvh - 4rem);">
<div class="flex flex-col h-full p-6">
{{-- Swipe handle indicator --}}
<div class="flex justify-center mb-4">
<div class="w-10 h-1 bg-neutral/40 rounded-full"></div>
</div>
<div class="flex-1 min-h-0 overflow-y-auto">
<x-navigation.sidebar-links />
</div>
</div>
</div>
</div>
</template>
</div>
</nav>