frontend: improve navbar dropdown behaviour and navigation

This commit is contained in:
Matthieu
2025-09-25 16:14:41 +02:00
parent 8e3894bfe2
commit cab9b216e6

View File

@@ -37,111 +37,132 @@
</li>
<li>
<NuxtLink
to="/types"
to="/machine-skeleton"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/types') ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="isActive('/machine-skeleton') ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Schémas de machine
Squelettes de machine
</NuxtLink>
</li>
<li>
<details class="group">
<summary
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
:class="isActive('/models/pieces') ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Pièces
</summary>
<ul class="menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1">
<li>
<NuxtLink
to="/model-types"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/model-types') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catégorie de pièce
</NuxtLink>
</li>
<li>
<NuxtLink
to="/models/pieces"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/models/pieces') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catalogue de pièce
</NuxtLink>
</li>
</ul>
</details>
<li
class="dropdown dropdown-hover"
:class="{ 'dropdown-open': openDropdown === 'pieces-mobile' }"
@mouseenter="setDropdown('pieces-mobile')"
@mouseleave="scheduleDropdownClose('pieces-mobile')"
@focusin="setDropdown('pieces-mobile')"
@focusout="scheduleDropdownClose('pieces-mobile')"
>
<div
tabindex="0"
role="button"
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
:class="(isActive('/pieces-catalog') || isActive('/piece-category')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Pièces
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1">
<li>
<NuxtLink
to="/piece-category"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/piece-category') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catégorie de pièce
</NuxtLink>
</li>
<li>
<NuxtLink
to="/pieces-catalog"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/pieces-catalog') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catalogue de pièce
</NuxtLink>
</li>
</ul>
</li>
<li>
<details class="group">
<summary
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
:class="(isActive('/models/components') || isActive('/model-types')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Composant
</summary>
<ul class="menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1">
<li>
<NuxtLink
to="/model-types"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/model-types') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catégorie de composant
</NuxtLink>
</li>
<li>
<NuxtLink
to="/models/components"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/models/components') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catalogue de composant
</NuxtLink>
</li>
</ul>
</details>
<li
class="dropdown dropdown-hover"
:class="{ 'dropdown-open': openDropdown === 'component-mobile' }"
@mouseenter="setDropdown('component-mobile')"
@mouseleave="scheduleDropdownClose('component-mobile')"
@focusin="setDropdown('component-mobile')"
@focusout="scheduleDropdownClose('component-mobile')"
>
<div
tabindex="0"
role="button"
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
:class="(isActive('/component-catalog') || isActive('/component-category')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Composant
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1">
<li>
<NuxtLink
to="/component-category"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/component-category') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catégorie de composant
</NuxtLink>
</li>
<li>
<NuxtLink
to="/component-catalog"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/component-catalog') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catalogue de composant
</NuxtLink>
</li>
</ul>
</li>
<li>
<details class="group">
<summary
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
:class="(isActive('/sites') || isActive('/documents') || isActive('/constructeurs')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Ressources liées
</summary>
<ul class="menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1">
<li>
<NuxtLink
to="/sites"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/sites') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Sites
</NuxtLink>
</li>
<li>
<NuxtLink
to="/documents"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/documents') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Documents
</NuxtLink>
</li>
<li>
<NuxtLink
to="/constructeurs"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/constructeurs') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Constructeurs
</NuxtLink>
</li>
</ul>
</details>
<li
class="dropdown dropdown-hover"
:class="{ 'dropdown-open': openDropdown === 'resources-mobile' }"
@mouseenter="setDropdown('resources-mobile')"
@mouseleave="scheduleDropdownClose('resources-mobile')"
@focusin="setDropdown('resources-mobile')"
@focusout="scheduleDropdownClose('resources-mobile')"
>
<div
tabindex="0"
role="button"
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
:class="(isActive('/sites') || isActive('/documents') || isActive('/constructeurs')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Ressources liées
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu menu-sm bg-base-100 rounded-box p-2 shadow space-y-1">
<li>
<NuxtLink
to="/sites"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/sites') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Sites
</NuxtLink>
</li>
<li>
<NuxtLink
to="/documents"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/documents') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Documents
</NuxtLink>
</li>
<li>
<NuxtLink
to="/constructeurs"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/constructeurs') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Constructeurs
</NuxtLink>
</li>
</ul>
</li>
</ul>
</div>
@@ -176,74 +197,95 @@
</li>
<li>
<NuxtLink
to="/types"
to="/machine-skeleton"
class="transition-colors px-3 py-2 rounded-md"
:class="isActive('/types') ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="isActive('/machine-skeleton') ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Schémas de machine
Squelettes de machine
</NuxtLink>
</li>
<li class="dropdown dropdown-hover">
<li
class="dropdown dropdown-hover"
:class="{ 'dropdown-open': openDropdown === 'pieces-desktop' }"
@mouseenter="setDropdown('pieces-desktop')"
@mouseleave="scheduleDropdownClose('pieces-desktop')"
@focusin="setDropdown('pieces-desktop')"
@focusout="scheduleDropdownClose('pieces-desktop')"
>
<div
tabindex="0"
role="button"
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
:class="isActive('/models/pieces') ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="(isActive('/pieces-catalog') || isActive('/piece-category')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Pièces
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-60">
<li>
<NuxtLink
to="/model-types"
to="/piece-category"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/model-types') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="isActive('/piece-category') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catégorie de pièce
</NuxtLink>
</li>
<li>
<NuxtLink
to="/models/pieces"
to="/pieces-catalog"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/models/pieces') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="isActive('/pieces-catalog') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catalogue de pièce
</NuxtLink>
</li>
</ul>
</li>
<li class="dropdown dropdown-hover">
<li
class="dropdown dropdown-hover"
:class="{ 'dropdown-open': openDropdown === 'component-desktop' }"
@mouseenter="setDropdown('component-desktop')"
@mouseleave="scheduleDropdownClose('component-desktop')"
@focusin="setDropdown('component-desktop')"
@focusout="scheduleDropdownClose('component-desktop')"
>
<div
tabindex="0"
role="button"
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
:class="(isActive('/model-types') || isActive('/models/components')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="(isActive('/component-category') || isActive('/component-catalog')) ? 'bg-primary text-primary-content font-semibold shadow-sm' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Composant
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64">
<li>
<NuxtLink
to="/model-types"
to="/component-category"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/model-types') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="isActive('/component-category') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catégorie de composant
</NuxtLink>
</li>
<li>
<NuxtLink
to="/models/components"
to="/component-catalog"
class="rounded-md px-2 py-1 transition-colors"
:class="isActive('/models/components') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
:class="isActive('/component-catalog') ? 'bg-primary/10 text-primary font-semibold' : 'text-base-content hover:bg-primary/10 hover:text-primary'"
>
Catalogue de composant
</NuxtLink>
</li>
</ul>
</li>
<li class="dropdown dropdown-hover">
<li
class="dropdown dropdown-hover"
:class="{ 'dropdown-open': openDropdown === 'resources-desktop' }"
@mouseenter="setDropdown('resources-desktop')"
@mouseleave="scheduleDropdownClose('resources-desktop')"
@focusin="setDropdown('resources-desktop')"
@focusout="scheduleDropdownClose('resources-desktop')"
>
<div
tabindex="0"
role="button"
@@ -382,7 +424,7 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute, navigateTo } from '#imports'
import { useProfileSession } from '~/composables/useProfileSession'
import IconLucideMenu from '~icons/lucide/menu'
@@ -428,6 +470,31 @@ const handleLogout = async () => {
await navigateTo('/profiles')
}
const openDropdown = ref(null)
let dropdownCloseTimer = null
const setDropdown = (name) => {
if (dropdownCloseTimer) {
clearTimeout(dropdownCloseTimer)
dropdownCloseTimer = null
}
if (openDropdown.value !== name) {
openDropdown.value = name
}
}
const scheduleDropdownClose = (name) => {
if (dropdownCloseTimer) {
clearTimeout(dropdownCloseTimer)
}
dropdownCloseTimer = setTimeout(() => {
if (openDropdown.value === name) {
openDropdown.value = null
}
dropdownCloseTimer = null
}, 200)
}
const activeProfileLabel = computed(() => {
if (!activeProfile.value) return 'Profil inconnu'
return `${activeProfile.value.firstName} ${activeProfile.value.lastName}`
@@ -442,4 +509,11 @@ const activeProfileInitials = computed(() => {
onMounted(async () => {
await ensureSession()
})
onUnmounted(() => {
if (dropdownCloseTimer) {
clearTimeout(dropdownCloseTimer)
dropdownCloseTimer = null
}
})
</script>