MainMenu.vue 4.8 KB

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