ActionMenu.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <!--
  2. Menu d'actions rapides (appel, contact, ...), qui reste accroché au bord droit
  3. de l'écran (ou au bas de l'écran sur les petits écrans)
  4. -->
  5. <template>
  6. <!-- Écrans larges : menu lateral, accroché au bord droit de l'écran -->
  7. <div v-if="lgAndUp && isVisible" class="sticky-menu lateral">
  8. <v-row
  9. v-for="(action, index) in actionsOrDefault"
  10. :key="index"
  11. :class="['square', action.color]"
  12. @click="() => onActionClick(action)"
  13. >
  14. <NuxtLink :to="action.url" class="link">
  15. <div>
  16. <v-icon :class="action.icon" />
  17. <p class="text-square mt-2">
  18. {{ action.text }}
  19. </p>
  20. </div>
  21. </NuxtLink>
  22. </v-row>
  23. </div>
  24. <!-- Petits écrans : menu sous forme de bandeau en pied de page (sauf si le footer du site est visible) -->
  25. <div v-else-if="isVisible" class="sticky-menu band">
  26. <v-btn
  27. :to="{ path: '', hash: '#top' }"
  28. class="primary"
  29. :width="24"
  30. >
  31. <v-icon>fas fa-arrow-up</v-icon>
  32. </v-btn>
  33. <v-btn
  34. v-for="(action, index) in actionsOrDefault"
  35. :key="index"
  36. :class="[action.color]"
  37. @click="() => onActionClick(action)"
  38. >
  39. {{ action.text }}
  40. </v-btn>
  41. </div>
  42. </template>
  43. <script setup lang="ts">
  44. import { useRouter } from 'vue-router'
  45. import { useDisplay } from 'vuetify'
  46. import type { ComputedRef } from 'vue'
  47. import { useLayoutStore } from '~/stores/layoutStore'
  48. import { ActionMenuItemType } from '~/types/enum/layout'
  49. import type { ActionMenuItem } from '~/types/interface'
  50. const { lgAndUp } = useDisplay()
  51. const router = useRouter()
  52. const layoutStore = useLayoutStore()
  53. const { isMobileDevice } = useClientDevice()
  54. const telephoneNumber = '09 72 12 60 17'
  55. const isVisible: ComputedRef<boolean> = computed(
  56. () =>
  57. !layoutStore.isHeaderVisible &&
  58. !layoutStore.isBannerVisible &&
  59. !layoutStore.isFooterVisible
  60. )
  61. // Actions par défaut du menu, peut-être surchargé via la propriété `actions`
  62. const defaultActions: Array<ActionMenuItem> = [
  63. {
  64. type: ActionMenuItemType.FOLLOW_LINK,
  65. color: 'secondary',
  66. icon: 'far fa-comments',
  67. text: 'Nous contacter',
  68. url: { path: 'nous-contacter', hash: '#form' },
  69. },
  70. {
  71. type: ActionMenuItemType.CALL_US,
  72. color: 'primary',
  73. icon: 'fas fa-phone',
  74. text: 'Nous Appeler',
  75. },
  76. ]
  77. const props = defineProps({
  78. /**
  79. * Actions accessibles via le menu (par défaut : "Nous contacter", "Nous appeler")
  80. */
  81. actions: {
  82. type: Array<ActionMenuItem>,
  83. required: false,
  84. default: [],
  85. },
  86. })
  87. const actionsOrDefault: ComputedRef<Array<ActionMenuItem>> = computed(() => {
  88. return props.actions.length > 0 ? props.actions : defaultActions
  89. })
  90. const callUs = () => {
  91. if (isMobileDevice()) {
  92. window.location.href = `tel:${telephoneNumber}`
  93. } else {
  94. navigateTo({ path: 'nous-contacter', hash: '#details' })
  95. }
  96. }
  97. /**
  98. * On a cliqué sur une des actions du menu
  99. * @param action
  100. */
  101. const onActionClick = (action: ActionMenuItem) => {
  102. switch (action.type) {
  103. case ActionMenuItemType.ASK_FOR_A_DEMO:
  104. router.push({ path: action.url as string, query: { request: 'demo' } })
  105. break
  106. case ActionMenuItemType.CALL_US:
  107. callUs()
  108. break
  109. case ActionMenuItemType.FOLLOW_LINK:
  110. if (!action.url) {
  111. throw new Error('Missing prop : url')
  112. }
  113. navigateTo(action.url)
  114. break
  115. default:
  116. throw new Error('Unrecognized action')
  117. }
  118. }
  119. </script>
  120. <style scoped lang="scss">
  121. .sticky-menu {
  122. z-index: 100;
  123. }
  124. // Menu format lateral (pour affichage écrans larges)
  125. .sticky-menu.lateral {
  126. position: fixed;
  127. right: 0;
  128. top: 60%;
  129. transform: translateY(-50%);
  130. float: right;
  131. display: flex;
  132. flex-direction: column;
  133. color: var(--on-primary-color);
  134. font-weight: 500;
  135. font-size: 0.7rem;
  136. line-height: 15px;
  137. text-align: center !important;
  138. letter-spacing: 0.2em;
  139. text-transform: uppercase;
  140. }
  141. // Menu format ruban (pour affichage petits écrans)
  142. .sticky-menu.band {
  143. position: fixed;
  144. height: 46px;
  145. bottom: 0;
  146. width: 100%;
  147. display: flex;
  148. justify-content: center;
  149. background-color: var(--neutral-color);
  150. max-width: 100vw;
  151. padding: 0 6px;
  152. .v-btn {
  153. margin: 6px 2%;
  154. }
  155. }
  156. .square {
  157. position: relative;
  158. width: 7rem;
  159. padding: 1rem;
  160. cursor: pointer;
  161. transition: transform 0.3s ease-in-out;
  162. box-shadow: -1px 2px 6px 1px var(--on-neutral-color-light);
  163. }
  164. .square:hover {
  165. transform: translateX(-10px);
  166. }
  167. .link {
  168. text-decoration: none;
  169. color: var(--on-primary-color);
  170. * {
  171. text-align: center;
  172. }
  173. }
  174. .primary {
  175. background: var(--action-menu-primary-color);
  176. color: var(--action-menu-on-primary-color);
  177. a {
  178. color: var(--action-menu-on-primary-color);
  179. }
  180. }
  181. .secondary {
  182. background: var(--action-menu-secondary-color);
  183. color: var(--action-menu-on-secondary-color);
  184. a {
  185. color: var(--action-menu-on-secondary-color);
  186. }
  187. }
  188. </style>