|
|
@@ -0,0 +1,177 @@
|
|
|
+<!--
|
|
|
+Menu principal de l'application
|
|
|
+Prend en paramètre une liste de ItemMenu et les met en forme
|
|
|
+-->
|
|
|
+
|
|
|
+<template>
|
|
|
+ <v-navigation-drawer
|
|
|
+ :mini-variant.sync="miniVariant"
|
|
|
+ v-model="open"
|
|
|
+ class="bg-ot-dark-grey text-ot-menu-color main-menu"
|
|
|
+ >
|
|
|
+ <template #prepend>
|
|
|
+ <slot name="title"></slot>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <v-list
|
|
|
+ open-strategy="single"
|
|
|
+ active-class="active"
|
|
|
+ class="left-menu"
|
|
|
+ >
|
|
|
+ <div v-for="(item, i) in menu.children" :key="i">
|
|
|
+
|
|
|
+ <!-- Cas 1 : l'item n'a pas d'enfants, c'est un lien -->
|
|
|
+ <v-list-item
|
|
|
+ v-if="!item.children"
|
|
|
+ :title="$t(item.label)"
|
|
|
+ :prepend-icon="item.icon.name"
|
|
|
+ :href="!isInternalLink(item) ? item.to : undefined"
|
|
|
+ :to="isInternalLink(item) ? item.to : undefined"
|
|
|
+ exact
|
|
|
+ class="text-ot-menu-color"
|
|
|
+ height="48px"
|
|
|
+ ></v-list-item>
|
|
|
+
|
|
|
+ <!-- Cas 2 : l'item a des enfants, c'est un groupe -->
|
|
|
+ <v-list-group
|
|
|
+ v-else
|
|
|
+ expand-icon="fas fa-angle-right"
|
|
|
+ collapse-icon="fas fa-angle-down"
|
|
|
+ class="text-ot-menu-color"
|
|
|
+ v-model="item.expanded"
|
|
|
+ >
|
|
|
+ <template #activator="{ props }">
|
|
|
+ <v-list-item
|
|
|
+ v-bind="props"
|
|
|
+ :prepend-icon="item.icon.name"
|
|
|
+ :title="$t(item.label)"
|
|
|
+ class="text-ot-menu-color"
|
|
|
+ height="48px"
|
|
|
+ ></v-list-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <v-list-item
|
|
|
+ v-for="child in item.children"
|
|
|
+ :key="$t(child.label)"
|
|
|
+ :title="$t(child.label)"
|
|
|
+ :prepend-icon="child.icon.name"
|
|
|
+ :href="!isInternalLink(child) ? child.to : undefined"
|
|
|
+ :to="isInternalLink(child) ? child.to : undefined"
|
|
|
+ exact
|
|
|
+ height="48px"
|
|
|
+ class="text-ot-white"
|
|
|
+ ></v-list-item>
|
|
|
+ </v-list-group>
|
|
|
+ </div>
|
|
|
+ </v-list>
|
|
|
+
|
|
|
+ <template #append>
|
|
|
+ <slot name="foot"></slot>
|
|
|
+ </template>
|
|
|
+ </v-navigation-drawer>
|
|
|
+
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import {onUnmounted, watch, WatchStopHandle} from "@vue/runtime-core";
|
|
|
+import {ref, toRefs} from "@vue/reactivity";
|
|
|
+import {MenuGroup, MenuItem} from "~/types/layout";
|
|
|
+import {MENU_LINK_TYPE} from "~/types/enum/layout";
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ menu: {
|
|
|
+ type: Object as () => MenuGroup,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ miniVariant: {
|
|
|
+ type: Boolean,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ openMenu: {
|
|
|
+ type: Boolean,
|
|
|
+ required: true
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const { openMenu } = toRefs(props)
|
|
|
+const open = ref(true)
|
|
|
+
|
|
|
+
|
|
|
+// Par défaut si l'écran est trop petit au chargement de la page, le menu doit être fermé.
|
|
|
+if(process.client)
|
|
|
+ open.value = window.innerWidth >= 1264
|
|
|
+
|
|
|
+// TODO : ajouter une petite explication de ce qu'on fait dans ce watch
|
|
|
+const unwatch: WatchStopHandle = watch(openMenu, (newValue, oldValue) => {
|
|
|
+ if (newValue !== oldValue) {
|
|
|
+ open.value = true
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const isInternalLink = (menuItem: MenuItem | MenuGroup) => {
|
|
|
+ return 'type' in menuItem && menuItem.type === MENU_LINK_TYPE.INTERNAL
|
|
|
+}
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ unwatch()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+
|
|
|
+ .v-list-item {
|
|
|
+ min-height: 10px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.v-list-item-title),
|
|
|
+ :deep(.v-icon),
|
|
|
+ {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgb(var(--v-theme-ot-menu-color));
|
|
|
+ }
|
|
|
+
|
|
|
+ .v-list-item__prepend {
|
|
|
+ margin: 10px 0;
|
|
|
+ margin-right: 10px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .v-application--is-ltr .v-list-group--no-action > .v-list-group__header {
|
|
|
+ margin-left: 0;
|
|
|
+ padding-left: 0;
|
|
|
+ }
|
|
|
+ .v-application--is-ltr .v-list-group--no-action > .v-list-group__items > .v-list-item {
|
|
|
+ padding-left: 30px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .v-list-item__content {
|
|
|
+ padding: 8px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .v-list-group__items .v-list-item {
|
|
|
+ padding-inline-start: 30px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .v-list-group--no-action > .v-list-group__header,
|
|
|
+ .v-list-item
|
|
|
+ {
|
|
|
+ border-left:3px solid rgb(var(--v-theme-ot-dark-grey));
|
|
|
+ height: 48px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .v-list-item:hover,
|
|
|
+ .v-list-item.active,
|
|
|
+ :deep(.v-list-group__items .v-list-item)
|
|
|
+ {
|
|
|
+ border-left: 3px solid rgb(var(--v-theme-ot-green));
|
|
|
+ background: rgb(var(--v-theme-ot-dark-grey-hover));
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.v-list-group__items .v-list-item-title) {
|
|
|
+ color: rgb(var(--v-theme-ot-white));
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.v-list-item .v-icon) {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+</style>
|