MainMenu.vue 5.6 KB

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