ActionMenu.vue 4.7 KB

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