MainMenu.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <!--
  2. Menu principal de l'application
  3. Prend en paramètre une liste de ItemMenu et les met en forme
  4. -->
  5. <template>
  6. <v-navigation-drawer
  7. v-model="displayMenu"
  8. :rail="isRail"
  9. :disable-resize-watcher="true"
  10. class="theme-secondary main-menu"
  11. >
  12. <template #prepend>
  13. <slot name="title"></slot>
  14. </template>
  15. <v-list
  16. open-strategy="single"
  17. active-class="active"
  18. class="left-menu"
  19. >
  20. <!-- TODO: que se passe-t-il si le menu ne comprend qu'un seul MenuItem? -->
  21. <div v-for="(item, i) in items" :key="i">
  22. <!-- Cas 1 : l'item n'a pas d'enfants, c'est un lien -->
  23. <v-list-item
  24. v-if="!item.children"
  25. :title="$t(item.label)"
  26. :prepend-icon="item.icon.name"
  27. :href="!isInternalLink(item) ? item.to : undefined"
  28. :to="isInternalLink(item) ? item.to : undefined"
  29. exact
  30. height="48px"
  31. />
  32. <!-- Cas 2 : l'item a des enfants, c'est un groupe -->
  33. <v-list-group
  34. v-else
  35. expand-icon="fas fa-angle-down"
  36. collapse-icon="fas fa-angle-up"
  37. >
  38. <template #activator="{ props }">
  39. <v-list-item
  40. v-bind="props"
  41. :prepend-icon="item.icon.name"
  42. :title="$t(item.label)"
  43. class="theme-secondary"
  44. height="48px"
  45. />
  46. </template>
  47. <v-list-item
  48. v-for="child in item.children"
  49. :key="$t(child.label)"
  50. :title="$t(child.label)"
  51. :prepend-icon="child.icon.name"
  52. :href="!isInternalLink(child) ? child.to : undefined"
  53. :to="isInternalLink(child) ? child.to : undefined"
  54. exact
  55. height="38px"
  56. class="theme-secondary-alt"
  57. />
  58. </v-list-group>
  59. </div>
  60. </v-list>
  61. <template #append>
  62. <slot name="foot"></slot>
  63. </template>
  64. </v-navigation-drawer>
  65. </template>
  66. <script setup lang="ts">
  67. import {useMenu} from "~/composables/layout/useMenu";
  68. import {computed} from "@vue/reactivity";
  69. import { useDisplay } from 'vuetify'
  70. import { MenuGroup, MenuItem } from "~/types/layout";
  71. const { getMenu, hasMenu, isInternalLink, setMenuState, isMenuOpened } = useMenu()
  72. const { mdAndUp, lgAndUp } = useDisplay()
  73. const menu = getMenu('Main')
  74. const isOpened = computed(() => isMenuOpened('Main'))
  75. let items: Array<MenuGroup | MenuItem>
  76. if (menu === null) {
  77. items = []
  78. } else if (menu.hasOwnProperty('children')) {
  79. items = (menu as MenuGroup).children ?? []
  80. } else {
  81. items = [menu]
  82. }
  83. // En vue lg+, on affiche toujours le menu
  84. const displayMenu = computed(() => {
  85. return menu !== null && hasMenu('Main') && (lgAndUp.value || isOpened.value)
  86. })
  87. // En vue md+, fermer le menu le passe simplement en mode rail
  88. // Sinon, le fermer le masque complètement
  89. const isRail = computed(() => {
  90. return menu !== null && mdAndUp.value && !isOpened.value && !items.some((item) => item.expanded)
  91. })
  92. const unwatch = watch(lgAndUp, (newValue, oldValue) => {
  93. // Par défaut si l'écran est trop petit au chargement de la page, le menu doit rester fermé.
  94. if (process.client && menu !== null) {
  95. setMenuState('Main', lgAndUp.value)
  96. }
  97. })
  98. onUnmounted(() => {
  99. unwatch()
  100. })
  101. </script>
  102. <style scoped lang="scss">
  103. .v-list-item {
  104. min-height: 10px !important;
  105. }
  106. :deep(.v-list-item-title),
  107. :deep(.v-icon),
  108. {
  109. font-size: 14px;
  110. color: rgb(var(--v-theme-on-secondary));
  111. }
  112. .v-list-item__prepend {
  113. margin: 10px 0;
  114. margin-right: 10px !important;
  115. }
  116. .v-application--is-ltr .v-list-group--no-action > .v-list-group__header {
  117. margin-left: 0;
  118. padding-left: 0;
  119. }
  120. .v-application--is-ltr .v-list-group--no-action > .v-list-group__items > .v-list-item {
  121. padding-left: 30px;
  122. }
  123. .v-list-item__content {
  124. padding: 8px 0;
  125. }
  126. .v-list-group__items .v-list-item {
  127. padding-inline-start: 30px !important;
  128. }
  129. .v-list-group--no-action > .v-list-group__header,
  130. .v-list-item
  131. {
  132. border-left: 3px solid rgb(var(--v-theme-secondary));
  133. height: 48px;
  134. }
  135. .v-list-item:hover,
  136. .v-list-item.active,
  137. :deep(.v-list-group__items .v-list-item)
  138. {
  139. border-left: 3px solid rgb(var(--v-theme-primary));
  140. background-color: rgb(var(--v-theme-secondary-alt)) !important;
  141. color: rgb(var(--v-theme-on-secondary-alt)) !important;
  142. }
  143. :deep(.v-list-group__items .v-list-item-title) {
  144. color: rgb(var(--v-theme-on-secondary-alt));
  145. }
  146. :deep(.v-list-group__items .v-icon) {
  147. color: rgb(var(--v-theme-on-secondary-alt));
  148. }
  149. :deep(.v-list-item .v-icon) {
  150. margin-right: 10px;
  151. }
  152. </style>