MainMenu.vue 5.3 KB

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