ActionMenu.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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 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(
  115. action.url,
  116. {
  117. open: { target: '_blank' }
  118. }
  119. )
  120. break
  121. default:
  122. throw new Error('Unrecognized action')
  123. }
  124. }
  125. </script>
  126. <style scoped lang="scss">
  127. .sticky-menu {
  128. z-index: 100;
  129. }
  130. // Menu format lateral (pour affichage écrans larges)
  131. .sticky-menu.lateral {
  132. position: fixed;
  133. right: 0;
  134. top: 60%;
  135. transform: translateY(-50%);
  136. float: right;
  137. display: flex;
  138. flex-direction: column;
  139. color: var(--on-primary-color);
  140. font-weight: 500;
  141. font-size: 0.7rem;
  142. line-height: 15px;
  143. text-align: center !important;
  144. letter-spacing: 0.2em;
  145. text-transform: uppercase;
  146. }
  147. // Menu format ruban (pour affichage petits écrans)
  148. .sticky-menu.band {
  149. position: fixed;
  150. height: 46px;
  151. bottom: 0;
  152. width: 100%;
  153. display: flex;
  154. justify-content: center;
  155. background-color: var(--neutral-color);
  156. max-width: 100vw;
  157. padding: 0 6px;
  158. .v-btn {
  159. margin: 6px 2%;
  160. }
  161. }
  162. .square {
  163. position: relative;
  164. width: 7rem;
  165. padding: 1rem;
  166. cursor: pointer;
  167. transition: transform 0.3s ease-in-out;
  168. box-shadow: -1px 2px 6px 1px var(--on-neutral-color-light);
  169. }
  170. .square:hover {
  171. transform: translateX(-10px);
  172. }
  173. .link {
  174. text-decoration: none;
  175. color: var(--on-primary-color);
  176. * {
  177. text-align: center;
  178. }
  179. }
  180. .back-to-the-top {
  181. position: fixed;
  182. right: 12px;
  183. bottom: 58px;
  184. z-index: 100;
  185. height: 48px;
  186. width: 48px;
  187. border-radius: 32px;
  188. }
  189. .primary {
  190. background: var(--action-menu-primary-color);
  191. color: var(--action-menu-on-primary-color);
  192. a {
  193. color: var(--action-menu-on-primary-color);
  194. }
  195. }
  196. .secondary {
  197. background: var(--action-menu-secondary-color);
  198. color: var(--action-menu-on-secondary-color);
  199. a {
  200. color: var(--action-menu-on-secondary-color);
  201. }
  202. }
  203. </style>