@cnamts/synapse 0.0.2-alpha → 0.0.4-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +1 -1
  2. package/dist/design-system-v3.d.ts +712 -27
  3. package/dist/design-system-v3.js +2745 -5384
  4. package/dist/design-system-v3.umd.cjs +10 -2
  5. package/dist/style.css +1 -1
  6. package/package.json +32 -29
  7. package/src/components/Alert/Alert.mdx +1 -1
  8. package/src/components/Alert/Alert.stories.ts +91 -1
  9. package/src/components/Alert/Alert.vue +8 -8
  10. package/src/components/BackBtn/BackBtn.mdx +1 -1
  11. package/src/components/BackBtn/BackBtn.stories.ts +84 -1
  12. package/src/components/BackToTopBtn/BackToTopBtn.mdx +3 -3
  13. package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +172 -11
  14. package/src/components/CollapsibleList/CollapsibleList.mdx +2 -2
  15. package/src/components/CollapsibleList/CollapsibleList.stories.ts +37 -1
  16. package/src/components/CopyBtn/CopyBtn.mdx +1 -1
  17. package/src/components/CopyBtn/CopyBtn.stories.ts +120 -1
  18. package/src/components/CopyBtn/CopyBtn.vue +1 -1
  19. package/src/components/Customs/CustomInputSelect/CustomInputSelect.mdx +6 -8
  20. package/src/components/Customs/CustomInputSelect/CustomInputSelect.stories.ts +270 -4
  21. package/src/components/Customs/CustomInputSelect/CustomInputSelect.vue +80 -53
  22. package/src/components/Customs/CustomInputSelect/config.ts +10 -0
  23. package/src/components/Customs/CustomSelect/CustomSelect.mdx +3 -3
  24. package/src/components/Customs/CustomSelect/CustomSelect.stories.ts +158 -2
  25. package/src/components/Customs/CustomSelect/CustomSelect.vue +25 -6
  26. package/src/components/Customs/CustomTextField/CustomTextField.mdx +44 -0
  27. package/src/components/Customs/CustomTextField/CustomTextField.stories.ts +403 -0
  28. package/src/components/Customs/CustomTextField/CustomTextField.vue +110 -0
  29. package/src/components/Customs/CustomTextField/tests/CustomTextField.spec.ts +93 -0
  30. package/src/components/Customs/CustomTextField/tests/__snapshots__/CustomTextField.spec.ts.snap +59 -0
  31. package/src/components/Customs/CustomTextField/types.d.ts +3 -0
  32. package/src/components/DataList/DataList.mdx +77 -0
  33. package/src/components/DataList/DataList.stories.ts +960 -0
  34. package/src/components/DataList/DataList.vue +140 -0
  35. package/src/components/DataList/DataListLoading/DataListLoading.vue +56 -0
  36. package/src/components/DataList/DataListLoading/tests/DataListLoading.spec.ts +23 -0
  37. package/src/components/DataList/locales.ts +3 -0
  38. package/src/components/DataList/tests/DataList.spec.ts +194 -0
  39. package/src/components/DataList/types.d.ts +23 -0
  40. package/src/components/DataListGroup/DataListGroup.mdx +77 -0
  41. package/src/components/DataListGroup/DataListGroup.stories.ts +987 -0
  42. package/src/components/DataListGroup/DataListGroup.vue +59 -0
  43. package/src/components/DataListGroup/tests/DataListGroup.spec.ts +54 -0
  44. package/src/components/DataListGroup/tests/data/dataListGroupItems.ts +41 -0
  45. package/src/components/DataListGroup/types.d.ts +15 -0
  46. package/src/components/DataListItem/DataListItem.vue +135 -0
  47. package/src/components/DataListItem/config.ts +17 -0
  48. package/src/components/DataListItem/locales.ts +3 -0
  49. package/src/components/DataListItem/tests/DataListItem.spec.ts +156 -0
  50. package/src/components/DataListItem/types.d.ts +23 -0
  51. package/src/components/DownloadBtn/Accessibilite.mdx +14 -0
  52. package/src/components/DownloadBtn/Accessibilite.stories.ts +166 -0
  53. package/src/components/DownloadBtn/AccessibiliteItems.ts +129 -0
  54. package/src/components/DownloadBtn/DownloadBtn.mdx +5 -6
  55. package/src/components/DownloadBtn/DownloadBtn.stories.ts +207 -2
  56. package/src/components/DownloadBtn/constants/ExpertiseLevelEnum.ts +4 -0
  57. package/src/components/FooterBar/FooterBar.mdx +2 -2
  58. package/src/components/FooterBar/FooterBar.stories.ts +1 -1
  59. package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +1 -1
  60. package/src/components/FranceConnectBtn/FranceConnectBtn.stories.ts +58 -1
  61. package/src/components/FranceConnectBtn/FranceConnectBtn.vue +2 -2
  62. package/src/components/HeaderBar/HeaderBar.mdx +256 -0
  63. package/src/components/HeaderBar/HeaderBar.stories.ts +703 -0
  64. package/src/components/HeaderBar/HeaderBar.vue +276 -0
  65. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.mdx +433 -0
  66. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.stories.ts +1089 -0
  67. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderBurgerMenu.vue +234 -0
  68. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.mdx +38 -0
  69. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.stories.ts +89 -0
  70. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +51 -0
  71. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/tests/HeaderMenuItem.spec.ts +16 -0
  72. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/tests/__snapshots__/HeaderMenuItem.spec.ts.snap +3 -0
  73. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/HeaderMenuSection.mdx +17 -0
  74. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/HeaderMenuSection.stories.ts +121 -0
  75. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/HeaderMenuSection.vue +51 -0
  76. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuSection/tests/HeaderMenuSection.spec.ts +31 -0
  77. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.mdx +43 -0
  78. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.stories.ts +261 -0
  79. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.vue +194 -0
  80. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/tests/HeaderSubMenu.spec.ts +63 -0
  81. package/src/components/HeaderBar/HeaderBurgerMenu/conts.ts +1 -0
  82. package/src/components/HeaderBar/HeaderBurgerMenu/locals.ts +4 -0
  83. package/src/components/HeaderBar/HeaderBurgerMenu/tests/HeaderBurgerMenu.spec.ts +180 -0
  84. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/HeaderBurgerMenu.spec.ts.snap +13 -0
  85. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/HeaderComplexMenu.spec.ts.snap +13 -0
  86. package/src/components/HeaderBar/HeaderBurgerMenu/tests/useHandleSubMenus.spec.ts +158 -0
  87. package/src/components/HeaderBar/HeaderBurgerMenu/useHandleSubMenus.ts +49 -0
  88. package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +143 -0
  89. package/src/components/HeaderBar/HeaderLogo/locales.ts +3 -0
  90. package/src/components/HeaderBar/HeaderLogo/logos/Logo-mobile.vue +117 -0
  91. package/src/components/HeaderBar/HeaderLogo/logos/Logo.vue +279 -0
  92. package/src/components/HeaderBar/HeaderLogo/tests/HeaderLogo.spec.ts +119 -0
  93. package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +84 -0
  94. package/src/components/HeaderBar/HeaderMenuBtn/locals.ts +4 -0
  95. package/src/components/HeaderBar/HeaderMenuBtn/tests/HeaderMenuBtn.spec.ts +70 -0
  96. package/src/components/HeaderBar/Usages.mdx +85 -0
  97. package/src/components/HeaderBar/consts.scss +6 -0
  98. package/src/components/HeaderBar/consts.ts +2 -0
  99. package/src/components/HeaderBar/locales.ts +3 -0
  100. package/src/components/HeaderBar/tests/HeaderBar.spec.ts +216 -0
  101. package/src/components/HeaderBar/tests/__snapshots__/HeaderBar.spec.ts.snap +45 -0
  102. package/src/components/HeaderBar/tests/useHeaderResponsiveMode.spec.ts +26 -0
  103. package/src/components/HeaderBar/tests/useScrollDirection.spec.ts +34 -0
  104. package/src/components/HeaderBar/useHeaderResponsiveMode.ts +25 -0
  105. package/src/components/HeaderBar/useScrollDirection.ts +26 -0
  106. package/src/components/HeaderLoading/HeaderLoading.mdx +28 -0
  107. package/src/components/HeaderLoading/HeaderLoading.stories.ts +62 -0
  108. package/src/components/HeaderLoading/HeaderLoading.vue +45 -0
  109. package/src/components/HeaderLoading/tests/HeaderLoading.spec.ts +22 -0
  110. package/src/components/HeaderNavigationBar/HeaderNavigationBar.mdx +128 -0
  111. package/src/components/HeaderNavigationBar/HeaderNavigationBar.stories.ts +784 -0
  112. package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +194 -0
  113. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +74 -0
  114. package/src/components/HeaderNavigationBar/HorizontalNavbar/config.ts +18 -0
  115. package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.spec.ts +127 -0
  116. package/src/components/HeaderNavigationBar/types.ts +7 -0
  117. package/src/components/HeaderToolbar/HeaderToolbar.mdx +31 -0
  118. package/src/components/HeaderToolbar/HeaderToolbar.stories.ts +343 -0
  119. package/src/components/HeaderToolbar/HeaderToolbar.vue +487 -0
  120. package/src/components/HeaderToolbar/tests/HeaderToolbar.spec.ts +196 -0
  121. package/src/components/HeaderToolbar/types.d.ts +20 -0
  122. package/src/components/LangBtn/LangBtn.mdx +1 -1
  123. package/src/components/LangBtn/LangBtn.stories.ts +125 -8
  124. package/src/components/Logo/Logo.mdx +2 -2
  125. package/src/components/Logo/Logo.stories.ts +147 -1
  126. package/src/components/LogoBrandSection/LogoBrandSection.mdx +14 -0
  127. package/src/components/LogoBrandSection/LogoBrandSection.stories.ts +158 -0
  128. package/src/components/LogoBrandSection/LogoBrandSection.vue +312 -0
  129. package/src/components/LogoBrandSection/assets/ameli-pro.svg +1 -0
  130. package/src/components/LogoBrandSection/assets/ameli.svg +1 -0
  131. package/src/components/LogoBrandSection/assets/cnam.svg +1 -0
  132. package/src/components/LogoBrandSection/assets/compte-ameli.svg +1 -0
  133. package/src/components/LogoBrandSection/dividerDimensionsMapping.ts +14 -0
  134. package/src/components/LogoBrandSection/locales.ts +14 -0
  135. package/src/components/LogoBrandSection/secondaryLogoMapping.ts +24 -0
  136. package/src/components/LogoBrandSection/tests/LogoBrandSection.spec.ts +365 -0
  137. package/src/components/LogoBrandSection/tests/__snapshots__/LogoBrandSection.spec.ts.snap +14 -0
  138. package/src/components/LogoBrandSection/types.ts +8 -0
  139. package/src/components/NotificationBar/NotificationBar.mdx +6 -6
  140. package/src/components/NotificationBar/NotificationBar.stories.ts +1 -1
  141. package/src/components/NotificationBar/NotificationBar.vue +7 -9
  142. package/src/components/NotificationBar/tests/NotificationBar.spec.ts +1 -1
  143. package/src/components/PageContainer/PageContainer.mdx +1 -1
  144. package/src/components/PageContainer/PageContainer.stories.ts +86 -1
  145. package/src/components/PageContainer/PageContainer.vue +0 -1
  146. package/src/components/PhoneField/PhoneField.mdx +49 -0
  147. package/src/components/PhoneField/PhoneField.stories.ts +869 -0
  148. package/src/components/PhoneField/PhoneField.vue +230 -0
  149. package/src/components/PhoneField/indicatifs.ts +104 -0
  150. package/src/components/PhoneField/locales.ts +4 -0
  151. package/src/components/PhoneField/tests/PhoneField.spec.ts +179 -0
  152. package/src/components/SkipLink/SkipLink.stories.ts +50 -1
  153. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +28 -1
  154. package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +37 -1
  155. package/src/components/SubHeader/SubHeader.mdx +31 -0
  156. package/src/components/SubHeader/SubHeader.stories.ts +1032 -0
  157. package/src/components/SubHeader/SubHeader.vue +185 -0
  158. package/src/components/SubHeader/config.ts +12 -0
  159. package/src/components/SubHeader/locales.ts +3 -0
  160. package/src/components/SubHeader/tests/SubHeader.spec.ts +144 -0
  161. package/src/components/index.ts +24 -7
  162. package/src/composables/widthable/index.ts +29 -0
  163. package/src/composables/widthable/tests/widthable.spec.ts +52 -0
  164. package/src/designTokens/tokens/cnam/cnamLightTheme.ts +2 -2
  165. package/src/main.ts +1 -0
  166. package/src/modules.d.ts +4 -0
  167. package/src/services/index.ts +1 -0
  168. package/src/stories/Demarrer/Accueil.mdx +10 -0
  169. package/src/stories/Demarrer/Accueil.stories.ts +76 -0
  170. package/src/stories/Demarrer/PolitiqueDeConfidentialite.mdx +9 -0
  171. package/src/stories/Demarrer/PolitiqueDeConfidentialite.stories.ts +20 -0
  172. package/src/stories/Fondamentaux/Accessibilite/Accessibilite.mdx +1 -2
  173. package/src/stories/Fondamentaux/Accessibilite/Accessibilite.stories.ts +1 -1
  174. package/src/stories/Fondamentaux/EcoConception/Econception.stories.ts +1 -1
  175. package/src/stories/GuideDuDev/moduleDeNotification.mdx +52 -48
  176. package/src/stories/GuideDuDev/vuetifyOptions.mdx +31 -28
  177. package/src/stories/Guidelines/CustomisationEtThemes.mdx +1 -1
  178. package/src/utils/functions/throttleDisplayFn/tests/throttleDisplayFn.spec.ts +47 -0
  179. package/src/utils/functions/throttleDisplayFn/throttleDisplayFn.ts +26 -0
  180. package/src/utils/rules/exactLength/index.ts +33 -0
  181. package/src/utils/rules/exactLength/locales.ts +6 -0
  182. package/src/utils/rules/required/index.ts +25 -0
  183. package/src/utils/rules/required/locales.ts +5 -0
  184. package/src/utils/rules/required/ruleMessageHelper.ts +14 -0
  185. package/src/utils/rules/required/tests/index.spec.ts +47 -0
  186. package/src/utils/rules/required/tests/rulesMessageHelper.spec.ts +22 -0
  187. package/src/utils/rules/types.d.ts +15 -0
  188. package/src/components/Beta/beta.mdx +0 -5
  189. package/src/components/Deprecated/deprecated.mdx +0 -5
  190. package/src/stories/Home/Accueil.mdx +0 -7
  191. package/src/stories/Home/PolitiqueDeConfidentialite.mdx +0 -4
  192. package/src/stories/Home/synapse.webp +0 -0
  193. /package/src/components/Logo/{types.d.ts → types.ts} +0 -0
@@ -0,0 +1,234 @@
1
+ <script setup lang="ts">
2
+ import throttleDisplayFn from '@/utils/functions/throttleDisplayFn/throttleDisplayFn'
3
+ import { computed, inject, nextTick, onMounted, onUnmounted, readonly, ref, watch, type CSSProperties, type Ref } from 'vue'
4
+ import HeaderMenuBtn from '../HeaderMenuBtn/HeaderMenuBtn.vue'
5
+ import { registerHeaderMenuKey } from '../consts'
6
+ import useHeaderResponsiveMode from '../useHeaderResponsiveMode'
7
+ import locals from './locals'
8
+ import useHandleSubMenus from './useHandleSubMenus'
9
+
10
+ const headerMenuWrapper = ref<HTMLElement | null>(null)
11
+ const menuBtnWrapper = ref<HTMLDivElement | null>(null)
12
+ const outerBtn = ref<HTMLElement | null>(null)
13
+ const innerBtn = ref<HTMLElement | null>(null)
14
+ const menuLeft = ref(0)
15
+ const menuTop = ref(0)
16
+ const menuHeight = ref('70vh')
17
+
18
+ function positionMenu() {
19
+ const rect = menuBtnWrapper.value!.getBoundingClientRect()
20
+ menuLeft.value = rect.left
21
+ menuTop.value = rect.top
22
+ menuHeight.value = `calc(100vh - ${rect.top}px - 48px)`
23
+ }
24
+ const throttledPositionMenu = throttleDisplayFn(positionMenu, 16)
25
+ const optimizedPositionMenu = () => {
26
+ if (menuOpen.value) {
27
+ throttledPositionMenu()
28
+ }
29
+ }
30
+
31
+ onMounted(() => {
32
+ positionMenu()
33
+ togglePageScroll()
34
+ window.addEventListener('scroll', optimizedPositionMenu)
35
+ window.addEventListener('resize', optimizedPositionMenu)
36
+ window.addEventListener('click', handleClickOutside, { capture: true })
37
+ })
38
+
39
+ onUnmounted(() => {
40
+ window.removeEventListener('scroll', optimizedPositionMenu)
41
+ window.removeEventListener('resize', optimizedPositionMenu)
42
+ window.removeEventListener('click', handleClickOutside, { capture: true })
43
+
44
+ document.documentElement.style.overflow = 'auto'
45
+ document.body.style.overflow = 'auto'
46
+ })
47
+
48
+ const menuOpen = defineModel<boolean>({
49
+ default: false,
50
+ })
51
+
52
+ watch(menuOpen, async (newVal) => {
53
+ togglePageScroll()
54
+
55
+ if (newVal) {
56
+ positionMenu() // the menu position can have changed since the component was mounted
57
+
58
+ await nextTick()
59
+ innerBtn.value?.focus()
60
+ }
61
+ else {
62
+ outerBtn.value?.focus()
63
+ }
64
+ }, { immediate: true })
65
+
66
+ const { isDesktop } = useHeaderResponsiveMode()
67
+ const menuStyle = computed<CSSProperties>(() => ({
68
+ left: `${menuLeft.value}px`,
69
+ top: `${menuTop.value}px`,
70
+ height: isDesktop.value ? menuHeight.value : undefined,
71
+ }))
72
+
73
+ function handleClickOutside(event: MouseEvent | KeyboardEvent) {
74
+ if (!menuOpen.value) return
75
+
76
+ // do not close menu if click is inside the menu
77
+ let walkElement = event.target as HTMLElement | null
78
+ while (walkElement && walkElement !== document.body) {
79
+ if (walkElement === headerMenuWrapper.value) return
80
+ walkElement = walkElement.parentElement
81
+ }
82
+
83
+ event.stopPropagation()
84
+ menuOpen.value = false
85
+ }
86
+
87
+ function togglePageScroll() {
88
+ if (typeof window !== 'undefined') {
89
+ document.documentElement.style.overflow = menuOpen.value ? 'hidden' : 'auto'
90
+ document.body.style.overflow = menuOpen.value ? 'hidden' : 'auto'
91
+ }
92
+ }
93
+
94
+ const { haveOpenSubMenu } = useHandleSubMenus(readonly(menuOpen))
95
+
96
+ const registerHeaderMenu = inject<(menuOpen: Ref<boolean>) => void>(registerHeaderMenuKey)
97
+ if (registerHeaderMenu) registerHeaderMenu(readonly(menuOpen))
98
+ </script>
99
+ <template>
100
+ <div
101
+ role="dialog"
102
+ aria-modal="true"
103
+ :aria-label="locals.mainMenu"
104
+ >
105
+ <div ref="menuBtnWrapper">
106
+ <HeaderMenuBtn
107
+ ref="outerBtn"
108
+ v-model="menuOpen"
109
+ />
110
+ </div>
111
+ <Teleport to="body">
112
+ <Transition name="menu">
113
+ <div
114
+ v-if="menuOpen"
115
+ class="overlay"
116
+ >
117
+ <div
118
+ role="menu"
119
+ class="menu-wrapper"
120
+ :style="menuStyle"
121
+ >
122
+ <HeaderMenuBtn
123
+ ref="innerBtn"
124
+ v-model="menuOpen"
125
+ />
126
+ <nav
127
+ id="header-menu-wrapper"
128
+ ref="headerMenuWrapper"
129
+ class="header-menu-wrapper"
130
+ :class="{
131
+ 'header-menu-wrapper--submenu-open': haveOpenSubMenu,
132
+ }"
133
+ role="navigation"
134
+ :aria-label="locals.publicMenu"
135
+ >
136
+ <div class="header-menu">
137
+ <slot />
138
+ </div>
139
+ </nav>
140
+ </div>
141
+ </div>
142
+ </Transition>
143
+ </Teleport>
144
+ </div>
145
+ </template>
146
+
147
+ <style lang="scss" scoped>
148
+ @use '@/assets/tokens.scss' as *;
149
+ @use '../consts' as *;
150
+
151
+ .overlay {
152
+ inset: 0;
153
+ position: fixed;
154
+ z-index: 1000;
155
+ background-color: rgba(3, 16, 37, .5);
156
+ backdrop-filter: blur(2px);
157
+ }
158
+
159
+ .menu-wrapper {
160
+ height: 100dvh;
161
+ background-color: $neutral-white;
162
+ display: flex;
163
+ flex-direction: column;
164
+ }
165
+
166
+ .header-menu-wrapper {
167
+ height: calc(100% - $header-height);
168
+ display: grid;
169
+ position: relative;
170
+ overflow: auto;
171
+ }
172
+
173
+ .header-menu-wrapper--submenu-open {
174
+ overflow: clip;
175
+ }
176
+
177
+ @media screen and (min-width: $header-breakpoint) {
178
+ .menu-wrapper {
179
+ position: absolute;
180
+ background-color: transparent;
181
+ }
182
+
183
+ .header-menu-wrapper {
184
+ width: $menu-width;
185
+ overflow: visible;
186
+ }
187
+
188
+ .header-menu {
189
+ background-color: $neutral-white;
190
+ overflow-y : auto;
191
+ overflow-x: hidden;
192
+ height: 100%;
193
+ }
194
+ }
195
+
196
+ .menu-enter-active {
197
+ transition: opacity 0.15s ease-in;
198
+
199
+ .header-menu-wrapper {
200
+ transition: transform 0.1s ease-in;
201
+ }
202
+ }
203
+
204
+ .menu-leave-active {
205
+ transition: opacity 0.15s ease-out;
206
+
207
+ .header-menu-wrapper {
208
+ transition: transform 0.1s ease-out;
209
+ }
210
+ }
211
+
212
+ .menu-enter-from, .menu-leave-to {
213
+ opacity: 0;
214
+ }
215
+
216
+ @media screen and (min-width: $header-breakpoint) {
217
+ .menu-enter-from, .menu-leave-to {
218
+ .header-menu-wrapper {
219
+ transform: translateY(10px);
220
+ }
221
+ }
222
+ }
223
+
224
+ @media (prefers-reduced-motion: reduce) {
225
+ .menu-enter-active, .menu-leave-active {
226
+ transition: opacity 0s;
227
+ }
228
+ .menu-enter-from, .menu-leave-to {
229
+ .header-menu-wrapper {
230
+ transform: none;
231
+ }
232
+ }
233
+ }
234
+ </style>
@@ -0,0 +1,38 @@
1
+ import { Controls, Canvas, Meta, Source } from "@storybook/blocks";
2
+ import * as HeaderMenuItemStories from "./HeaderMenuItem.stories";
3
+
4
+ <Meta of={HeaderMenuItemStories} />
5
+
6
+ # HeaderMenuItem
7
+
8
+ Le composant `HeaderMenuItem` est un composant de menu qui permet de wrapper un lien de navigation. Cela peut être un lien `a`, `router-link` ou `nuxt-link`.
9
+ Ce composant doit être utilisé dans un composant `HeaderMenuSection` afin de respecter la structure du menu et de garantir la bonne accessibilité de celui ci.
10
+
11
+ <Source
12
+ dark
13
+ code={`
14
+ <HeaderMenuSection>
15
+ <HeaderMenuItem>
16
+ <a href="/">Home</a>
17
+ </HeaderMenuItem>
18
+ <HeaderMenuItem>
19
+ <a href="/about">About</a>
20
+ </HeaderMenuItem>
21
+ <HeaderMenuItem>
22
+ <RouterLink to="/services">Services</RouterLink>
23
+ </HeaderMenuItem>
24
+ <HeaderMenuItem>
25
+ <NuxtLink to="/contact">Contact</NuxtLink>
26
+ </HeaderMenuItem>
27
+ </HeaderMenuSection>
28
+ `}
29
+ />
30
+
31
+ Pour plus de détails sur le contexte d'utilisation de ce composant, veuillez consulter le composant `HeaderBurgerMenu`.
32
+
33
+
34
+ <Canvas of={HeaderMenuItemStories.Default} />
35
+
36
+ ## API
37
+
38
+ <Controls of={HeaderMenuItemStories.Default} />
@@ -0,0 +1,89 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import HeaderMenuItem from './HeaderMenuItem.vue'
3
+ import HeaderBurgerMenu from '../HeaderBurgerMenu.vue'
4
+ import HeaderBar from '../../HeaderBar.vue'
5
+ import HeaderMenuSection from '../HeaderMenuSection/HeaderMenuSection.vue'
6
+
7
+ const meta = {
8
+ title: 'Composants/Structure/HeaderBar/HeaderBurgerMenu/HeaderMenuItem',
9
+ component: HeaderMenuItem,
10
+ argTypes: {
11
+ default: {
12
+ control: { type: 'text' },
13
+ description: 'Le lien (`a`; `router-link`, `nuxt-link`) et son contenu',
14
+ table: {
15
+ type: { summary: '{}' },
16
+ },
17
+ },
18
+ },
19
+ parameters: {
20
+ layout: 'fullscreen',
21
+ },
22
+ } satisfies Meta<typeof HeaderMenuItem>
23
+
24
+ export default meta
25
+
26
+ type Story = StoryObj<typeof meta>
27
+
28
+ export const Default: Story = {
29
+ args: {
30
+ default: 'lorem ipsum',
31
+ },
32
+ render: (args) => {
33
+ return {
34
+ components: { HeaderMenuItem, HeaderBurgerMenu, HeaderBar, HeaderMenuSection },
35
+ setup() {
36
+ return { args }
37
+ },
38
+ template: `
39
+ <HeaderBar>
40
+ <template #menu>
41
+ <HeaderBurgerMenu>
42
+ <HeaderMenuSection>
43
+ <HeaderMenuItem>
44
+ <a href="">{{ args.default }}</a>
45
+ </HeaderMenuItem>
46
+ </HeaderMenuSection>
47
+ </HeaderBurgerMenu>
48
+ </template>
49
+ </HeaderBar>
50
+ `,
51
+ }
52
+ },
53
+ play: async ({ canvasElement }) => {
54
+ const menuBtn = canvasElement.querySelector('button')
55
+ setTimeout(() => {
56
+ menuBtn!.click()
57
+ }, 1000)
58
+ },
59
+ parameters: {
60
+ sourceCode: [
61
+ {
62
+ name: 'Template',
63
+ code: `
64
+ <Template>
65
+ <HeaderBar>
66
+ <template #menu>
67
+ <HeaderBurgerMenu>
68
+ <HeaderMenuSection>
69
+ <HeaderMenuItem>
70
+ <a href="">lorem ipsum</a>
71
+ </HeaderMenuItem>
72
+ </HeaderMenuSection>
73
+ </HeaderBurgerMenu>
74
+ </template>
75
+ </HeaderBar>
76
+ </Template>
77
+ `,
78
+ },
79
+ {
80
+ name: 'Script',
81
+ code: `
82
+ <script setup>
83
+ import { HeaderBurgerMenu, HeaderBar, HeaderMenuSection, HeaderMenuItem } from '@cnamts/synapse'
84
+ </script>
85
+ `,
86
+ },
87
+ ],
88
+ },
89
+ }
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <li class="header-menu-item">
7
+ <slot />
8
+ </li>
9
+ </template>
10
+
11
+ <style lang="scss" scoped>
12
+ @use "@/assets/tokens.scss" as *;
13
+
14
+ .header-menu-item {
15
+ color: $primary-base;
16
+ list-style-type: none;
17
+ margin: 0;
18
+ padding: 0;
19
+ min-height: 44px; // accessibility requirement
20
+ font-weight: 700;
21
+
22
+ > :deep(a) {
23
+ display: flex;
24
+ flex-direction: column;
25
+ padding: 16px 50px 16px 20px;
26
+ text-decoration: none;
27
+ color: currentColor;
28
+
29
+ &:hover {
30
+ text-decoration: underline;
31
+ }
32
+
33
+ &:visited {
34
+ color: currentColor;
35
+ }
36
+
37
+ &::first-letter {
38
+ text-transform: uppercase;
39
+ }
40
+ }
41
+ }
42
+
43
+ .header-menu-item:hover {
44
+ background-color: $primary-base;
45
+ color: $neutral-white;
46
+
47
+ > :deep(a > *) {
48
+ color: $neutral-white !important;
49
+ }
50
+ }
51
+ </style>
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import HeaderMenuItem from '../HeaderMenuItem.vue'
4
+
5
+ describe('HeaderMenuItem', () => {
6
+ it('should render the component', async () => {
7
+ const wrapper = mount(HeaderMenuItem, {
8
+ slots: {
9
+ default: '<a>Test</a>',
10
+ },
11
+ })
12
+
13
+ expect(wrapper.html()).toMatchSnapshot()
14
+ expect(wrapper.find('a').text()).toBe('Test')
15
+ })
16
+ })
@@ -0,0 +1,3 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`HeaderMenuItem > should render the component 1`] = `"<li data-v-bc776fc3="" class="header-menu-item"><a>Test</a></li>"`;
@@ -0,0 +1,17 @@
1
+ import { Controls, Canvas, Meta, Source } from "@storybook/blocks";
2
+ import * as HeaderMenuSectionStories from "./HeaderMenuSection.stories";
3
+
4
+ <Meta of={HeaderMenuSectionStories} />
5
+
6
+ # HeaderMenuSection
7
+
8
+ Le composant `HeaderMenuSection` permet de regrouper des éléments de menu dans une section. Il s'utilise dans le composant `HeaderComplexMenu` ou `HeaderSubMenu` et doit contenir des éléments de menu (`HeaderMenuItem`).
9
+
10
+
11
+ <Canvas of={HeaderMenuSectionStories.Default} />
12
+
13
+ ## API
14
+
15
+ <Controls of={HeaderMenuSectionStories.Default} />
16
+
17
+
@@ -0,0 +1,121 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import HeaderMenuSection from './HeaderMenuSection.vue'
3
+ import HeaderMenuItem from '../HeaderMenuItem/HeaderMenuItem.vue'
4
+ import HeaderBurgerMenu from '../HeaderBurgerMenu.vue'
5
+ import HeaderBar from '../../HeaderBar.vue'
6
+
7
+ const meta = {
8
+ title: 'Composants/Structure/HeaderBar/HeaderBurgerMenu/HeaderMenuSection',
9
+ component: HeaderMenuSection,
10
+ argTypes: {
11
+ title: {
12
+ description: 'Titre de la section',
13
+ control: { type: 'text' },
14
+ table: {
15
+ category: 'slots',
16
+ },
17
+ },
18
+ default: {
19
+ control: { type: 'text' },
20
+ description: 'Contenu de la section, construit avec des composants `HeaderMenuItem`',
21
+ table: {
22
+ type: { summary: '{}' },
23
+ },
24
+ },
25
+ },
26
+ parameters: {
27
+ layout: 'fullscreen',
28
+ },
29
+ } satisfies Meta<typeof HeaderMenuSection>
30
+
31
+ export default meta
32
+
33
+ type Story = StoryObj<typeof meta>
34
+
35
+ export const Default: Story = {
36
+ args: {
37
+ title: 'section 1',
38
+ },
39
+ render: (args) => {
40
+ return {
41
+ components: { HeaderMenuItem, HeaderBurgerMenu, HeaderBar, HeaderMenuSection },
42
+ setup() {
43
+ return { args }
44
+ },
45
+ template: `
46
+ <HeaderBar>
47
+ <template #menu>
48
+ <HeaderBurgerMenu>
49
+ <HeaderMenuSection>
50
+ <template #title>
51
+ {{ args.title }}
52
+ </template>
53
+ <HeaderMenuItem>
54
+ <a>lorem ipsum</a>
55
+ </HeaderMenuItem>
56
+ </HeaderMenuSection>
57
+
58
+ <HeaderMenuSection>
59
+ <template #title>
60
+ section 2
61
+ </template>
62
+
63
+ <HeaderMenuItem>
64
+ <a>lorem ipsum</a>
65
+ </HeaderMenuItem>
66
+ </HeaderMenuSection>
67
+ </HeaderBurgerMenu>
68
+ </template>
69
+ </HeaderBar>
70
+ `,
71
+ }
72
+ },
73
+ play: async ({ canvasElement }) => {
74
+ const menuBtn = canvasElement.querySelector('button')
75
+ setTimeout(() => {
76
+ menuBtn!.click()
77
+ }, 1000)
78
+ },
79
+ parameters: {
80
+ sourceCode: [
81
+ {
82
+ name: 'Template',
83
+ code: `
84
+ <Template>
85
+ <HeaderBar>
86
+ <template #menu>
87
+ <HeaderBurgerMenu>
88
+ <HeaderMenuSection>
89
+ <template #title>
90
+ {{ args.title }}
91
+ </template>
92
+ <HeaderMenuItem>
93
+ <a>lorem ipsum</a>
94
+ </HeaderMenuItem>
95
+ </HeaderMenuSection>
96
+
97
+ <HeaderMenuSection>
98
+ <template #title>
99
+ section 2
100
+ </template>
101
+ <HeaderMenuItem>
102
+ <a>lorem ipsum</a>
103
+ </HeaderMenuItem>
104
+ </HeaderMenuSection>
105
+ </HeaderBurgerMenu>
106
+ </template>
107
+ </HeaderBar>
108
+ </Template>
109
+ `,
110
+ },
111
+ {
112
+ name: 'Script',
113
+ code: `
114
+ <script setup>
115
+ import { HeaderBurgerMenu, HeaderBar, HeaderMenuSection, HeaderMenuItem } from '@cnamts/synapse'
116
+ </script>
117
+ `,
118
+ },
119
+ ],
120
+ },
121
+ }
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { useId } from 'vue'
3
+
4
+ defineProps<{
5
+ title?: string
6
+ }>()
7
+
8
+ const id = useId()
9
+ const groupId = `${id}-group`
10
+ const titleId = `${id}-group-title`
11
+ </script>
12
+
13
+ <template>
14
+ <div class="header-menu-section">
15
+ <div
16
+ v-if="$slots.title"
17
+ :id="titleId"
18
+ class="header-menu-section-title"
19
+ >
20
+ <slot name="title" />
21
+ </div>
22
+ <ul
23
+ :id="groupId"
24
+ role="group"
25
+ class="header-menu-section-list"
26
+ >
27
+ <slot />
28
+ </ul>
29
+ </div>
30
+ </template>
31
+
32
+ <style lang="scss" scoped>
33
+ @use "@/assets/tokens.scss" as *;
34
+ @use "../../consts.scss" as menu;
35
+
36
+ .header-menu-section {
37
+ list-style-type: none;
38
+ padding: 0;
39
+ margin: 0;
40
+ }
41
+
42
+ .header-menu-section-title {
43
+ padding: 40px 16px 8px 20px;
44
+ border-bottom: 1px solid menu.$menu-border-color;
45
+ font-size: 1.1rem;
46
+ margin-bottom: 8px;
47
+ color: #212529;
48
+ text-transform: capitalize;
49
+ font-weight: 700;
50
+ }
51
+ </style>
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import HeaderMenuSection from '../HeaderMenuSection.vue'
4
+
5
+ describe('HeaderMenuSection', () => {
6
+ it('should render the component', async () => {
7
+ const wrapper = mount(HeaderMenuSection, {
8
+ slots: {
9
+ title: 'Section title',
10
+ default: [
11
+ '<li><a>Test 1</a></li>',
12
+ '<li><a>Test 2</a></li>',
13
+ ],
14
+ },
15
+ })
16
+
17
+ expect(wrapper.find('.header-menu-section-title').text()).toBe('Section title')
18
+ expect(wrapper.find('.header-menu-section-list').element.children.length).toBe(2)
19
+ })
20
+
21
+ it('should render the component with no title', async () => {
22
+ const wrapper = mount(HeaderMenuSection, {
23
+ slots: {
24
+ default: '<li><a>Test 1</a></li>',
25
+ },
26
+ })
27
+
28
+ expect(wrapper.find('.header-menu-section-title').exists()).toBe(false)
29
+ expect(wrapper.find('.header-menu-section-list').element.children.length).toBe(1)
30
+ })
31
+ })
@@ -0,0 +1,43 @@
1
+ import { Controls, Canvas, Meta, Source } from "@storybook/blocks";
2
+ import * as HeaderSubMenuStories from "./HeaderSubMenu.stories";
3
+
4
+ <Meta of={HeaderSubMenuStories} />
5
+
6
+ # HeaderSubMenu
7
+
8
+ Le composant `HeaderSubMenu` est un sous-composant du composant `HeaderBurgerMenu` qui permet de créer un sous-menu dans le menu principal.
9
+ En mode mobile, les sous-menus s'affichent par dessus du menu parent, en mode desktop, les sous-menus s'affichent à droite du parent.
10
+ Les sous-menus peuvent être imbriqués pour créer des sous-sous-menus.
11
+
12
+ Le composant `HeaderSubMenu` doit être utilisé dans un composant `HeaderMenuItem` afin de respecter la structure du menu et de garantir la bonne accessibilité du menu.
13
+ On aura donc une structure de composant comme suit :
14
+
15
+ <Source
16
+ dark
17
+ code={`
18
+ <HeaderMenuBurger>
19
+ <HeaderMenuSection>
20
+ <HeaderMenuItem>
21
+ <HeaderSubMenu>
22
+ <template #title>
23
+ title
24
+ </template>
25
+ <HeaderMenuSection>
26
+ <HeaderMenuItem>
27
+ <a>lien de deuxième niveau</a>
28
+ </HeaderMenuItem>
29
+ </HeaderMenuSection>
30
+ </HeaderSubMenu>
31
+ </HeaderMenuItem>
32
+ </HeaderMenuSection>
33
+ </HeaderMenuBurger>
34
+ `}
35
+ />
36
+
37
+
38
+
39
+ <Canvas of={HeaderSubMenuStories.Default} />
40
+
41
+ ## API
42
+
43
+ <Controls of={HeaderSubMenuStories.Default} />