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