fix: rewrite AppHeader — replace UDrawer with USlideover, clean design
UDrawer (vaul-vue bottom-sheet) rendered content in DOM even when closed, causing mobile nav to show on desktop. Replaced with USlideover (proper sidebar panel). Also: backdrop-blur header, UButton for actions, Lucide icons, brand color active states.
This commit is contained in:
@@ -3,7 +3,7 @@ const { t, locale, setLocale } = useI18n()
|
|||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const drawerOpen = ref(false)
|
const mobileOpen = ref(false)
|
||||||
|
|
||||||
const navLinks = computed(() => [
|
const navLinks = computed(() => [
|
||||||
{ key: 'home', path: '/' },
|
{ key: 'home', path: '/' },
|
||||||
@@ -27,121 +27,135 @@ function isActive(path: string): boolean {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="sticky top-0 z-[1020] bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800">
|
<header class="sticky top-0 z-50 backdrop-blur-xl bg-white/80 dark:bg-gray-950/80 border-b border-gray-200/50 dark:border-gray-800/50">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<!-- Logo -->
|
<div class="flex items-center justify-between h-16">
|
||||||
<NuxtLink :to="localePath('/')" :aria-label="t('a11y.logoLabel')" class="flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded">
|
<!-- Logo -->
|
||||||
<NuxtImg src="/images/logo.webp" alt="Killian Dalcin" width="40" height="40" loading="eager" class="rounded" />
|
|
||||||
<span class="text-lg font-semibold text-gray-900 dark:text-white">Killian</span>
|
|
||||||
</NuxtLink>
|
|
||||||
|
|
||||||
<!-- Desktop nav -->
|
|
||||||
<nav class="hidden md:flex items-center gap-6" aria-label="Main navigation">
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-for="link in navLinks"
|
:to="localePath('/')"
|
||||||
:key="link.key"
|
:aria-label="t('a11y.logoLabel')"
|
||||||
:to="localePath(link.path)"
|
class="flex items-center gap-2.5 shrink-0"
|
||||||
:aria-current="isActive(link.path) ? 'page' : undefined"
|
|
||||||
class="text-base transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded px-1 py-2"
|
|
||||||
:class="[
|
|
||||||
isActive(link.path)
|
|
||||||
? 'border-b-2 border-primary-500 text-gray-900 dark:text-white'
|
|
||||||
: 'text-gray-700 dark:text-gray-300 hover:text-primary-500'
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
{{ t(`nav.${link.key}`) }}
|
<NuxtImg
|
||||||
|
src="/images/logo.webp"
|
||||||
|
alt="Killian Dalcin"
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
loading="eager"
|
||||||
|
class="rounded-lg"
|
||||||
|
/>
|
||||||
|
<span class="text-base font-semibold tracking-tight text-gray-900 dark:text-white">Killian</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Toggles -->
|
<!-- Desktop nav -->
|
||||||
<div class="flex items-center gap-1">
|
<nav class="hidden md:flex items-center gap-1" aria-label="Main navigation">
|
||||||
<!-- Language toggle -->
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:aria-label="t('a11y.langToggle')"
|
|
||||||
class="min-w-11 min-h-11 inline-flex items-center justify-center text-gray-700 dark:text-gray-300 font-medium hover:text-primary-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded transition-colors"
|
|
||||||
@click="toggleLocale"
|
|
||||||
>
|
|
||||||
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Theme toggle -->
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
|
||||||
class="min-w-11 min-h-11 inline-flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-primary-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded transition-colors duration-300"
|
|
||||||
@click="toggleTheme"
|
|
||||||
>
|
|
||||||
<UIcon :name="colorMode.value === 'dark' ? 'heroicons:sun' : 'heroicons:moon'" class="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Hamburger (mobile) -->
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:aria-label="t('a11y.openMenu')"
|
|
||||||
class="md:hidden min-w-11 min-h-11 inline-flex items-center justify-center text-gray-700 dark:text-gray-300 hover:text-primary-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded transition-colors"
|
|
||||||
@click="drawerOpen = true"
|
|
||||||
>
|
|
||||||
<UIcon name="heroicons:bars-3" class="w-6 h-6" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile Drawer -->
|
|
||||||
<UDrawer v-model:open="drawerOpen" side="left">
|
|
||||||
<div class="p-6 flex flex-col h-full">
|
|
||||||
<!-- Close button -->
|
|
||||||
<div class="flex justify-end mb-4">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:aria-label="t('a11y.closeDrawer')"
|
|
||||||
class="min-w-11 min-h-11 inline-flex items-center justify-center text-gray-700 dark:text-gray-300 hover:text-primary-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded transition-colors"
|
|
||||||
@click="drawerOpen = false"
|
|
||||||
>
|
|
||||||
<UIcon name="heroicons:x-mark" class="w-6 h-6" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Nav links -->
|
|
||||||
<nav class="flex flex-col gap-2 flex-1" aria-label="Mobile navigation">
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-for="link in navLinks"
|
v-for="link in navLinks"
|
||||||
:key="link.key"
|
:key="link.key"
|
||||||
:to="localePath(link.path)"
|
:to="localePath(link.path)"
|
||||||
:aria-current="isActive(link.path) ? 'page' : undefined"
|
:aria-current="isActive(link.path) ? 'page' : undefined"
|
||||||
class="min-h-11 flex items-center px-4 py-3 text-base rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
|
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors"
|
||||||
:class="[
|
:class="[
|
||||||
isActive(link.path)
|
isActive(link.path)
|
||||||
? 'text-primary-500 font-medium bg-primary-50 dark:bg-primary-900/20'
|
? 'text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40'
|
||||||
: 'text-gray-700 dark:text-gray-300 hover:text-primary-500 hover:bg-gray-50 dark:hover:bg-gray-800'
|
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800/60'
|
||||||
]"
|
]"
|
||||||
@click="drawerOpen = false"
|
|
||||||
>
|
>
|
||||||
{{ t(`nav.${link.key}`) }}
|
{{ t(`nav.${link.key}`) }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Toggles at bottom -->
|
<!-- Right actions -->
|
||||||
<div class="flex items-center gap-2 pt-4 border-t border-gray-200 dark:border-gray-700">
|
<div class="flex items-center gap-1">
|
||||||
<button
|
<!-- Language toggle -->
|
||||||
type="button"
|
<UButton
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
size="sm"
|
||||||
:aria-label="t('a11y.langToggle')"
|
:aria-label="t('a11y.langToggle')"
|
||||||
class="min-w-11 min-h-11 inline-flex items-center justify-center text-gray-700 dark:text-gray-300 font-medium hover:text-primary-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded transition-colors"
|
|
||||||
@click="toggleLocale"
|
@click="toggleLocale"
|
||||||
>
|
>
|
||||||
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
||||||
</button>
|
</UButton>
|
||||||
|
|
||||||
<button
|
<!-- Theme toggle -->
|
||||||
type="button"
|
<UButton
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
size="sm"
|
||||||
|
:icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
||||||
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
||||||
class="min-w-11 min-h-11 inline-flex items-center justify-center text-gray-600 dark:text-gray-400 hover:text-primary-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded transition-colors duration-300"
|
|
||||||
@click="toggleTheme"
|
@click="toggleTheme"
|
||||||
>
|
/>
|
||||||
<UIcon :name="colorMode.value === 'dark' ? 'heroicons:sun' : 'heroicons:moon'" class="w-5 h-5" />
|
|
||||||
</button>
|
<!-- Mobile hamburger -->
|
||||||
|
<UButton
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
size="sm"
|
||||||
|
icon="i-lucide-menu"
|
||||||
|
class="md:hidden"
|
||||||
|
:aria-label="t('a11y.openMenu')"
|
||||||
|
@click="mobileOpen = true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UDrawer>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile slideover -->
|
||||||
|
<USlideover v-model:open="mobileOpen" side="left" class="md:hidden">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2.5">
|
||||||
|
<NuxtImg
|
||||||
|
src="/images/logo.webp"
|
||||||
|
alt="Killian Dalcin"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
class="rounded-lg"
|
||||||
|
/>
|
||||||
|
<span class="text-base font-semibold text-gray-900 dark:text-white">Killian</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<nav class="flex flex-col gap-1" aria-label="Mobile navigation">
|
||||||
|
<NuxtLink
|
||||||
|
v-for="link in navLinks"
|
||||||
|
:key="link.key"
|
||||||
|
:to="localePath(link.path)"
|
||||||
|
:aria-current="isActive(link.path) ? 'page' : undefined"
|
||||||
|
class="px-4 py-3 text-base font-medium rounded-lg transition-colors"
|
||||||
|
:class="[
|
||||||
|
isActive(link.path)
|
||||||
|
? 'text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-950/40'
|
||||||
|
: 'text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800/60'
|
||||||
|
]"
|
||||||
|
@click="mobileOpen = false"
|
||||||
|
>
|
||||||
|
{{ t(`nav.${link.key}`) }}
|
||||||
|
</NuxtLink>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UButton
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
:aria-label="t('a11y.langToggle')"
|
||||||
|
@click="toggleLocale"
|
||||||
|
>
|
||||||
|
{{ locale === 'fr' ? 'EN' : 'FR' }}
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
:icon="colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon'"
|
||||||
|
:aria-label="colorMode.value === 'dark' ? t('a11y.themeDark') : t('a11y.themeLight')"
|
||||||
|
@click="toggleTheme"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</USlideover>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user