Refactor dropdown and navigation components: update styles for improved consistency and accessibility; enhance link styling for better user experience

This commit is contained in:
2026-03-16 20:00:05 -04:00
parent 56462aac19
commit 99d14d9aa9
3 changed files with 37 additions and 60 deletions

View File

@@ -12,22 +12,22 @@
})"> })">
<button <button
class="flex flex-row items-center px-2 py-1 text-sm font-medium whitespace-nowrap text-base/80 hover:text-base transition-colors duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-md" class="flex flex-row items-center px-2 py-1 text-sm font-semibold whitespace-nowrap text-base hover:text-primary transition-colors duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-md"
x-on:click="open = !open" x-on:click="open = !open"
:aria-expanded="open"> :aria-expanded="open">
{{ $trigger }} {{ $trigger }}
<x-ri-arrow-down-s-line x-bind:class="{ '-rotate-180' : open }" <x-ri-arrow-down-s-line x-bind:class="{ '-rotate-180' : open }"
class="md:block hidden size-4 text-base/50 ease-out duration-300" /> class="md:block hidden size-4 text-base ease-out duration-300" />
</button> </button>
<div x-ref="dropdown" <div x-ref="dropdown"
class="absolute mt-2 w-48 bg-background-secondary/95 backdrop-blur-xl rounded-xl shadow-xl shadow-black/[0.08] z-10 border border-neutral/40 overflow-hidden ring-1 ring-black/[0.03]" class="absolute mt-2 w-48 bg-background-secondary rounded-xl shadow-xl shadow-black/5 z-10 border border-neutral/50 overflow-hidden"
x-show="open" x-show="open"
x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0 scale-95 -translate-y-2" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0 scale-95 -translate-y-2"
x-transition:enter-end="opacity-100 scale-100 translate-y-0" x-transition:leave="transition ease-in duration-100" x-transition:enter-end="opacity-100 scale-100 translate-y-0" x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
x-on:click.outside="open = false" x-cloak> x-on:click.outside="open = false" x-cloak>
<div class="py-1.5 [&>a]:block [&>a]:px-4 [&>a]:py-2.5 [&>a]:text-sm [&>a]:text-color-base/90 [&>a]:transition-colors [&>a]:duration-150 [&>a:hover]:bg-primary/10 [&>a:hover]:text-primary [&>a:focus]:bg-primary/10 [&>a:focus]:text-primary [&>a:focus]:outline-none"> <div class="py-1 [&>a]:block [&>a]:px-4 [&>a]:py-2.5 [&>a]:text-sm [&>a]:text-color-base [&>a]:transition-colors [&>a]:duration-150 [&>a:hover]:bg-primary/10 [&>a:hover]:text-primary [&>a:focus]:bg-primary/10 [&>a:focus]:text-primary [&>a:focus]:outline-none">
{{ $content }} {{ $content }}
</div> </div>
</div> </div>

View File

@@ -23,11 +23,10 @@
.text-flow-effect { .text-flow-effect {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
hsl(220, 98%, 61%) 0%, hsl(var(--color-primary)) 0%,
hsl(260, 80%, 65%) 25%, hsl(var(--color-secondary)) 40%,
hsl(220, 98%, 61%) 50%, hsl(var(--color-primary)) 60%,
hsl(190, 90%, 50%) 75%, hsl(var(--color-secondary)) 100%
hsl(220, 98%, 61%) 100%
); );
background-size: 300% auto; background-size: 300% auto;
background-clip: text; background-clip: text;
@@ -43,57 +42,35 @@
} }
/* ── Nav link animated underline ── */ /* ── Nav link animated underline ── */
.nav-link-underline { .nav-link-modern {
position: relative; position: relative;
} }
.nav-link-underline::after { .nav-link-modern::after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -2px; bottom: 0;
left: 50%; left: 50%;
width: 0; width: 0;
height: 2px; height: 2px;
border-radius: 1px; border-radius: 1px;
background: linear-gradient(90deg, hsl(220,98%,61%), hsl(260,80%,65%)); background: hsl(var(--color-primary));
transition: width 0.25s ease, left 0.25s ease; transition: width 0.25s ease, left 0.25s ease;
} }
.nav-link-underline:hover::after, .nav-link-modern:hover::after {
.nav-link-underline.active::after {
width: 60%; width: 60%;
left: 20%; left: 20%;
} }
/* ── Register button subtle glow ── */ /* ── Avatar ring using theme colors ── */
.btn-glow {
position: relative;
overflow: hidden;
isolation: isolate;
}
.btn-glow::before {
content: '';
position: absolute;
inset: -1px;
border-radius: inherit;
background: linear-gradient(135deg, hsl(220,98%,61%), hsl(260,80%,65%));
opacity: 0;
transition: opacity 0.3s ease;
z-index: -1;
filter: blur(12px);
}
.btn-glow:hover::before {
opacity: 0.5;
}
/* ── Avatar ring gradient ── */
.avatar-ring { .avatar-ring {
padding: 2px; padding: 2px;
background: linear-gradient(135deg, hsl(220,98%,61%), hsl(260,80%,65%)); background: linear-gradient(135deg, hsl(var(--color-primary)), hsl(var(--color-secondary)));
border-radius: 9999px; border-radius: 9999px;
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
} }
.avatar-ring:hover { .avatar-ring:hover {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 0 12px hsl(220 98% 61% / 0.35); box-shadow: 0 0 10px hsl(var(--color-primary) / 0.3);
} }
.avatar-ring img { .avatar-ring img {
display: block; display: block;
@@ -108,29 +85,29 @@
$watch('slideOverOpen', value => { document.documentElement.style.overflow = value ? 'hidden' : '' }); $watch('slideOverOpen', value => { document.documentElement.style.overflow = value ? 'hidden' : '' });
" "
:class="{ :class="{
'bg-background-secondary/80 backdrop-blur-xl shadow-lg shadow-black/[0.06] border-neutral/60': scrolled, 'shadow-lg shadow-black/5 border-neutral/80': scrolled,
'bg-background-secondary/50 backdrop-blur-md border-transparent': !scrolled 'border-neutral': !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-500 ease-out"> class="w-full px-4 lg:px-8 bg-background-secondary 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="relative z-50 w-full h-auto">
<div class="flex flex-row items-center justify-between w-full h-16"> <div class="flex flex-row items-center justify-between w-full h-16">
<!-- Logo --> <!-- Logo -->
<div class="flex flex-row items-center gap-0"> <div class="flex flex-row items-center">
<a href="{{ route('home') }}" class="flex items-center h-10 gap-2.5 group" wire:navigate> <a href="{{ route('home') }}" class="flex items-center h-10 gap-2.5 group" wire:navigate>
<x-logo class="h-10 transition-transform duration-300 group-hover:scale-105" /> <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> <span class="text-xl font-extrabold tracking-tight leading-none flex items-center text-flow-effect">{{ config('app.name') }}</span>
</a> </a>
<!-- Desktop menu: lg and above --> <!-- Desktop menu: lg and above -->
<div class="hidden lg:flex flex-row items-center ml-8 gap-1"> <div class="hidden lg:flex flex-row items-center ml-8 gap-0.5">
@foreach (\App\Classes\Navigation::getLinks() as $nav) @foreach (\App\Classes\Navigation::getLinks() as $nav)
@if (isset($nav['children']) && count($nav['children']) > 0) @if (isset($nav['children']) && count($nav['children']) > 0)
<div class="relative"> <div class="relative">
<x-dropdown> <x-dropdown>
<x-slot:trigger> <x-slot:trigger>
<span class="nav-link-underline flex flex-row items-center px-3 py-2 text-sm font-medium whitespace-nowrap text-base/80 hover:text-base transition-colors duration-200">{{ $nav['name'] }}</span> <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:trigger>
<x-slot:content> <x-slot:content>
@foreach ($nav['children'] as $child) @foreach ($nav['children'] as $child)
@@ -145,7 +122,7 @@
@else @else
<x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)" <x-navigation.link :href="route($nav['route'], $nav['params'] ?? null)"
:spa="isset($nav['spa']) ? $nav['spa'] : true" :spa="isset($nav['spa']) ? $nav['spa'] : true"
class="nav-link-underline flex items-center px-3 py-2"> class="nav-link-modern flex items-center px-3 py-2">
{{ $nav['name'] }} {{ $nav['name'] }}
</x-navigation.link> </x-navigation.link>
@endif @endif
@@ -154,7 +131,7 @@
</div> </div>
<!-- Right-side controls --> <!-- Right-side controls -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-3">
<livewire:components.cart /> <livewire:components.cart />
@@ -163,11 +140,11 @@
<div class="hidden lg:block"> <div class="hidden lg:block">
<x-dropdown> <x-dropdown>
<x-slot:trigger> <x-slot:trigger>
<div class="flex items-center gap-2.5 cursor-pointer"> <div class="flex items-center gap-2.5 cursor-pointer group">
<div class="avatar-ring"> <div class="avatar-ring">
<img src="{{ auth()->user()->avatar }}" class="w-8 h-8" alt="avatar" /> <img src="{{ auth()->user()->avatar }}" class="w-8 h-8" alt="avatar" />
</div> </div>
<span class="text-sm font-semibold text-base/90">{{ Auth::user()->first_name }}</span> <span class="text-sm font-semibold text-base group-hover:text-primary transition-colors duration-200">{{ Auth::user()->first_name }}</span>
</div> </div>
</x-slot:trigger> </x-slot:trigger>
<x-slot:content> <x-slot:content>
@@ -185,13 +162,13 @@
@else @else
<div class="hidden lg:flex items-center gap-2.5"> <div class="hidden lg:flex items-center gap-2.5">
<a href="{{ route('login') }}" wire:navigate> <a href="{{ route('login') }}" wire:navigate>
<button class="px-4 py-2 text-sm font-medium text-base/80 hover:text-base rounded-lg transition-all duration-200 hover:bg-neutral/20"> <button class="px-4 py-2 text-sm font-medium text-base hover:text-primary rounded-lg transition-all duration-200">
{{ __('navigation.login') }} {{ __('navigation.login') }}
</button> </button>
</a> </a>
@if(!config('settings.registration_disabled', false)) @if(!config('settings.registration_disabled', false))
<a href="{{ route('register') }}" wire:navigate> <a href="{{ route('register') }}" wire:navigate>
<button class="btn-glow px-5 py-2 text-sm font-semibold text-white bg-gradient-to-r from-primary to-secondary rounded-lg transition-all duration-300 shadow-md shadow-primary/20 hover:shadow-lg hover:shadow-primary/30 hover:brightness-110 active:scale-[0.97]"> <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') }} {{ __('navigation.register') }}
</button> </button>
</a> </a>
@@ -201,13 +178,13 @@
<!-- Mobile menu toggle --> <!-- Mobile menu toggle -->
<button @click="slideOverOpen = !slideOverOpen" <button @click="slideOverOpen = !slideOverOpen"
class="relative w-10 h-10 flex lg:hidden items-center justify-center rounded-lg hover:bg-neutral/20 transition-all duration-200 group" 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"> 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"> <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/70 group-hover:text-base transition-colors duration-200" /> <x-ri-menu-fill class="size-5 text-base/80 group-hover:text-foreground transition-colors duration-200" />
</span> </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"> <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/70 group-hover:text-base transition-colors duration-200" /> <x-ri-close-fill class="size-5 text-base/80 group-hover:text-foreground transition-colors duration-200" />
</span> </span>
</button> </button>
@@ -226,7 +203,7 @@
x-transition:leave-start="opacity-100" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" x-transition:leave-end="opacity-0"
@click="slideOverOpen = false" @click="slideOverOpen = false"
class="absolute inset-0 bg-black/50 backdrop-blur-md"></div> class="absolute inset-0 bg-black/40 backdrop-blur-sm"></div>
{{-- Slide-over panel --}} {{-- Slide-over panel --}}
<div x-show="slideOverOpen" <div x-show="slideOverOpen"
@@ -237,11 +214,11 @@
x-transition:leave-start="opacity-100 translate-y-0" x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 translate-y-full" x-transition:leave-end="opacity-0 translate-y-full"
@click.stop @click.stop
class="absolute left-0 right-0 top-16 bg-background-secondary/90 backdrop-blur-2xl border-t border-neutral/30 shadow-2xl overflow-y-auto flex flex-col rounded-t-2xl" 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);"> style="height:calc(100dvh - 4rem);">
<div class="flex flex-col h-full p-6"> <div class="flex flex-col h-full p-6">
{{-- Swipe handle indicator --}} {{-- Swipe handle indicator --}}
<div class="flex justify-center mb-5"> <div class="flex justify-center mb-4">
<div class="w-10 h-1 bg-neutral/40 rounded-full"></div> <div class="w-10 h-1 bg-neutral/40 rounded-full"></div>
</div> </div>
<div class="flex-1 min-h-0 overflow-y-auto"> <div class="flex-1 min-h-0 overflow-y-auto">

View File

@@ -1,6 +1,6 @@
@props(['href', 'spa' => true]) @props(['href', 'spa' => true])
<a href="{{ $href }}" {{ $attributes->merge(['class' => 'flex flex-row items-center p-3 gap-2 text-sm font-medium <a href="{{ $href }}" {{ $attributes->merge(['class' => 'flex flex-row items-center p-3 gap-2 text-sm font-semibold
text-wrap transition-colors duration-200 ' . ($href === request()->url() ? 'text-primary' : 'text-base/80 hover:text-base')]) }} @if($spa) text-wrap transition-colors duration-200 ' . ($href === request()->url() ? 'text-primary' : 'text-base hover:text-primary')]) }} @if($spa)
wire:navigate @endif> wire:navigate @endif>
{{ $slot }} {{ $slot }}
</a> </a>