ActionMenu.vue 5.0 KB

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