@cnamts/synapse 0.0.0-alpha.0 → 0.0.3-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.
- package/README.md +72 -2
- package/dist/design-system-v3.d.ts +234 -2
- package/dist/design-system-v3.js +1413 -4404
- package/dist/design-system-v3.umd.cjs +1 -2
- package/dist/style.css +1 -1
- package/package.json +37 -36
- package/src/components/Alert/Alert.vue +8 -8
- package/src/components/CollapsibleList/CollapsibleList.mdx +47 -0
- package/src/components/CollapsibleList/CollapsibleList.stories.ts +52 -0
- package/src/components/CollapsibleList/CollapsibleList.vue +157 -0
- package/src/components/CollapsibleList/tests/CollapsibleList.spec.ts +60 -0
- package/src/components/CollapsibleList/types.d.ts +5 -0
- package/src/components/Customs/CustomInputSelect/CustomInputSelect.mdx +42 -0
- package/src/components/Customs/CustomInputSelect/CustomInputSelect.stories.ts +154 -0
- package/src/components/Customs/CustomInputSelect/CustomInputSelect.vue +185 -0
- package/src/components/Customs/CustomInputSelect/tests/CustomInputSelect.spec.ts +216 -0
- package/src/components/Customs/CustomSelect/CustomSelect.mdx +47 -0
- package/src/components/Customs/CustomSelect/CustomSelect.stories.ts +182 -0
- package/src/components/Customs/CustomSelect/CustomSelect.vue +188 -0
- package/src/components/Customs/CustomSelect/tests/CustomSelect.spec.ts +236 -0
- package/src/components/FooterBar/A11yCompliance.ts +9 -0
- package/src/components/FooterBar/FooterBar.mdx +115 -0
- package/src/components/FooterBar/FooterBar.stories.ts +632 -0
- package/src/components/FooterBar/FooterBar.vue +330 -0
- package/src/components/FooterBar/config.ts +20 -0
- package/src/components/FooterBar/defaultSocialMediaLinks.ts +21 -0
- package/src/components/FooterBar/locales.ts +16 -0
- package/src/components/FooterBar/tests/FooterBar.spec.ts +167 -0
- package/src/components/FooterBar/tests/FooterBarConfig.spec.ts +36 -0
- package/src/components/FooterBar/tests/__snapshots__/FooterBar.spec.ts.snap +27 -0
- package/src/components/FooterBar/types.d.ts +10 -0
- package/src/components/FranceConnectBtn/FranceConnectBtn.vue +2 -2
- package/src/components/HeaderBar/HeaderBar.mdx +137 -0
- package/src/components/HeaderBar/HeaderBar.stories.ts +159 -0
- package/src/components/HeaderBar/HeaderBar.vue +238 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderComplexMenu.stories.ts +272 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderComplexMenu.vue +205 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/HeaderMenuItem.stories.ts +49 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/HeaderMenuItem.vue +51 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/tests/HeaderMenuItem.spec.ts +16 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/tests/__snapshots__/HeaderMenuItem.spec.ts.snap +3 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuSection/HeaderMenuSection.stories.ts +56 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuSection/HeaderMenuSection.vue +51 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuSection/tests/HeaderMenuSection.spec.ts +33 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderSubMenu/HeaderSubMenu.stories.ts +137 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderSubMenu/HeaderSubMenu.vue +180 -0
- package/src/components/HeaderBar/HeaderComplexMenu/HeaderSubMenu/tests/HeaderSubMenu.spec.ts +63 -0
- package/src/components/HeaderBar/HeaderComplexMenu/conts.ts +1 -0
- package/src/components/HeaderBar/HeaderComplexMenu/locals.ts +4 -0
- package/src/components/HeaderBar/HeaderComplexMenu/tests/HeaderComplexMenu.spec.ts +129 -0
- package/src/components/HeaderBar/HeaderComplexMenu/tests/__snapshots__/HeaderComplexMenu.spec.ts.snap +18 -0
- package/src/components/HeaderBar/HeaderComplexMenu/tests/useHandleSubMenus.spec.ts +158 -0
- package/src/components/HeaderBar/HeaderComplexMenu/useHandleSubMenus.ts +49 -0
- package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +106 -0
- package/src/components/HeaderBar/HeaderLogo/locales.ts +3 -0
- package/src/components/HeaderBar/HeaderLogo/logos/Logo-mobile.vue +117 -0
- package/src/components/HeaderBar/HeaderLogo/logos/Logo.vue +279 -0
- package/src/components/HeaderBar/HeaderLogo/tests/HeaderLogo.spec.ts +71 -0
- package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +88 -0
- package/src/components/HeaderBar/HeaderMenuBtn/locals.ts +4 -0
- package/src/components/HeaderBar/consts.scss +7 -0
- package/src/components/HeaderBar/consts.ts +2 -0
- package/src/components/HeaderBar/locales.ts +3 -0
- package/src/components/HeaderBar/tests/HeaderBar.spec.ts +210 -0
- package/src/components/HeaderBar/tests/__snapshots__/HeaderBar.spec.ts.snap +50 -0
- package/src/components/HeaderBar/tests/useHeaderResponsiveMode.spec.ts +26 -0
- package/src/components/HeaderBar/tests/useScrollDirection.spec.ts +34 -0
- package/src/components/HeaderBar/useHeaderResponsiveMode.ts +25 -0
- package/src/components/HeaderBar/useScrollDirection.ts +26 -0
- package/src/components/LangBtn/LangBtn.mdx +2 -1
- package/src/components/LangBtn/LangBtn.vue +3 -3
- package/src/components/Logo/Logo.mdx +26 -0
- package/src/components/Logo/Logo.stories.ts +217 -0
- package/src/components/Logo/Logo.vue +397 -0
- package/src/components/Logo/LogoSize.ts +7 -0
- package/src/components/Logo/locales.ts +6 -0
- package/src/components/Logo/logoDimensionsMapping.ts +16 -0
- package/src/components/Logo/tests/Logo.spec.ts +75 -0
- package/src/components/Logo/types.d.ts +8 -0
- package/src/components/NotificationBar/NotificationBar.vue +5 -7
- package/src/components/PageContainer/PageContainer.vue +0 -1
- package/src/components/SocialMediaLinks/DefaultSocialMediaLinks.ts +21 -0
- package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +15 -0
- package/src/components/SocialMediaLinks/SocialMediaLinks.stories.ts +72 -0
- package/src/components/SocialMediaLinks/SocialMediaLinks.vue +92 -0
- package/src/components/SocialMediaLinks/locales.ts +3 -0
- package/src/components/SocialMediaLinks/tests/DefaultSocialMediaLinks.spec.ts +21 -0
- package/src/components/SocialMediaLinks/tests/SocialMediaLinks.spec.ts +89 -0
- package/src/components/SocialMediaLinks/tests/__snapshots__/SocialMediaLinks.spec.ts.snap +24 -0
- package/src/components/SocialMediaLinks/types.d.ts +5 -0
- package/src/components/index.ts +6 -0
- package/src/directives/clickOutside.ts +24 -0
- package/src/temp/TestDTComponent.vue +6 -10
- package/src/temp/gridsTests.vue +0 -4
- package/src/utils/propValidator/index.ts +20 -0
- package/src/utils/propValidator/tests/propValidator.spec.ts +40 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import HeaderMenu from './HeaderComplexMenu.vue'
|
|
2
|
+
import HeaderBar from '../HeaderBar.vue'
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
4
|
+
import HeaderMenuSection from './HeaderMenuSection/HeaderMenuSection.vue'
|
|
5
|
+
import HeaderSubMenu from './HeaderSubMenu/HeaderSubMenu.vue'
|
|
6
|
+
import HeaderMenuItem from './HeaderMenuItem/HeaderMenuItem.vue'
|
|
7
|
+
import { VBtn } from 'vuetify/components'
|
|
8
|
+
import { mdiMapMarker } from '@mdi/js'
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: 'Components/HeaderBar/HeaderComplexMenu',
|
|
12
|
+
component: HeaderMenu,
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'fullscreen',
|
|
15
|
+
},
|
|
16
|
+
} satisfies Meta<typeof HeaderMenu>
|
|
17
|
+
|
|
18
|
+
export default meta
|
|
19
|
+
|
|
20
|
+
type Story = StoryObj<typeof meta>
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
args: {},
|
|
24
|
+
render: (args) => {
|
|
25
|
+
return {
|
|
26
|
+
components: { HeaderMenu, HeaderBar },
|
|
27
|
+
setup() {
|
|
28
|
+
return { args }
|
|
29
|
+
},
|
|
30
|
+
template: `
|
|
31
|
+
<HeaderBar>
|
|
32
|
+
<template #menu>
|
|
33
|
+
<HeaderMenu>
|
|
34
|
+
<p>lorem ipsum</p>
|
|
35
|
+
</HeaderMenu>
|
|
36
|
+
</template>
|
|
37
|
+
</HeaderBar>
|
|
38
|
+
`,
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const Populated: Story = {
|
|
44
|
+
args: {},
|
|
45
|
+
render: (args) => {
|
|
46
|
+
return {
|
|
47
|
+
components: { HeaderMenuItem, HeaderMenu, HeaderBar, HeaderSubMenu, HeaderMenuSection, VBtn },
|
|
48
|
+
setup() {
|
|
49
|
+
return { args }
|
|
50
|
+
},
|
|
51
|
+
template: `
|
|
52
|
+
<HeaderBar>
|
|
53
|
+
<template #menu>
|
|
54
|
+
<HeaderMenu>
|
|
55
|
+
<HeaderMenuSection title="section 1">
|
|
56
|
+
<HeaderMenuItem>
|
|
57
|
+
<a>Item 1</a>
|
|
58
|
+
</HeaderMenuItem>
|
|
59
|
+
<HeaderMenuItem>
|
|
60
|
+
<a>Item 2</a>
|
|
61
|
+
</HeaderMenuItem>
|
|
62
|
+
<headerMenuItem>
|
|
63
|
+
<HeaderSubMenu>
|
|
64
|
+
<template #title>
|
|
65
|
+
Menu de premier niveau 1
|
|
66
|
+
</template>
|
|
67
|
+
<HeaderMenuSection title="Section">
|
|
68
|
+
<HeaderMenuItem>
|
|
69
|
+
<a>Item</a>
|
|
70
|
+
</HeaderMenuItem>
|
|
71
|
+
<HeaderSubMenu>
|
|
72
|
+
<template #title>
|
|
73
|
+
Menu de deuxième niveau 1
|
|
74
|
+
</template>
|
|
75
|
+
<HeaderMenuItem>
|
|
76
|
+
<a>Item</a>
|
|
77
|
+
</HeaderMenuItem>
|
|
78
|
+
</HeaderSubMenu>
|
|
79
|
+
</HeaderMenuSection>
|
|
80
|
+
</HeaderSubMenu>
|
|
81
|
+
</headerMenuItem>
|
|
82
|
+
</HeaderMenuSection>
|
|
83
|
+
<HeaderMenuSection title="section 2">
|
|
84
|
+
<headerMenuItem>
|
|
85
|
+
<HeaderSubMenu>
|
|
86
|
+
<template #title>
|
|
87
|
+
Menu de premier niveau 2
|
|
88
|
+
</template>
|
|
89
|
+
<HeaderMenuItem>
|
|
90
|
+
<a>Item 1</a>
|
|
91
|
+
</HeaderMenuItem>
|
|
92
|
+
<HeaderMenuItem>
|
|
93
|
+
<HeaderSubMenu>
|
|
94
|
+
<template #title>
|
|
95
|
+
Menu de deuxième niveau 2
|
|
96
|
+
</template>
|
|
97
|
+
<HeaderMenuItem>
|
|
98
|
+
<a>Item 1</a>
|
|
99
|
+
</HeaderMenuItem>
|
|
100
|
+
</HeaderSubMenu>
|
|
101
|
+
</HeaderMenuItem>
|
|
102
|
+
<HeaderMenuItem>
|
|
103
|
+
<HeaderSubMenu>
|
|
104
|
+
<template #title>
|
|
105
|
+
Menu de deuxième niveau 3
|
|
106
|
+
</template>
|
|
107
|
+
<HeaderMenuSection title="section 1">
|
|
108
|
+
<HeaderMenuItem>
|
|
109
|
+
<a>Item 1</a>
|
|
110
|
+
</HeaderMenuItem>
|
|
111
|
+
</HeaderMenuSection>
|
|
112
|
+
</HeaderSubMenu>
|
|
113
|
+
</HeaderMenuItem>
|
|
114
|
+
</HeaderSubMenu>
|
|
115
|
+
</headerMenuItem>
|
|
116
|
+
<HeaderMenuItem>
|
|
117
|
+
<a>Item 3</a>
|
|
118
|
+
</HeaderMenuItem>
|
|
119
|
+
</HeaderMenuSection>
|
|
120
|
+
<div class="pa-4">
|
|
121
|
+
<p class="font-weight-bold">Veillez vous connecter</p>
|
|
122
|
+
<VBtn variant="tonal" class="mt-4 font-weight-medium" color="primary">Je me connecte</VBtn>
|
|
123
|
+
</div>
|
|
124
|
+
</HeaderMenu>
|
|
125
|
+
</template>
|
|
126
|
+
</HeaderBar>
|
|
127
|
+
`,
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const Generated: Story = {
|
|
133
|
+
args: {
|
|
134
|
+
},
|
|
135
|
+
render: (args) => {
|
|
136
|
+
return {
|
|
137
|
+
components: { HeaderMenu, HeaderBar, HeaderMenuSection, HeaderSubMenu, HeaderMenuItem },
|
|
138
|
+
setup() {
|
|
139
|
+
const menu = [
|
|
140
|
+
{
|
|
141
|
+
title: 'Vous informer',
|
|
142
|
+
items: [
|
|
143
|
+
{ title: 'Actualités', href: 'https://www.ameli.fr/assure/actualites' },
|
|
144
|
+
{
|
|
145
|
+
subMenuTitle: 'Droits et démarches',
|
|
146
|
+
subMenuSubtitle: 'selon votre situation',
|
|
147
|
+
sections: [
|
|
148
|
+
{
|
|
149
|
+
title: undefined,
|
|
150
|
+
items: [
|
|
151
|
+
{ title: 'Les essentiels de l’assuré', href: 'https://www.ameli.fr/assure/droits-demarches/principes' },
|
|
152
|
+
{ title: 'Parentalité, couple', href: 'https://www.ameli.fr/assure/droits-demarches/famille' },
|
|
153
|
+
{ title: 'Fin de vie, deuil', href: 'https://www.ameli.fr/assure/droits-demarches/fin-de-vie-deuil' },
|
|
154
|
+
{ title: 'Etudes et stages', href: 'https://www.ameli.fr/assure/droits-demarches/etudes-stages' },
|
|
155
|
+
{ title: 'Vie professionnelle, retraite', href: 'https://www.ameli.fr/assure/droits-demarches/vie-professionnelle-retraite' },
|
|
156
|
+
{ title: 'Difficultés d\'accès aux droits et aux soins', href: 'https://www.ameli.fr/assure/droits-demarches/difficultes-acces-droits-soins' },
|
|
157
|
+
{ title: 'Maladie, accident, hospitalisation', href: 'https://www.ameli.fr/assure/droits-demarches/maladie-accident-hospitalisation' },
|
|
158
|
+
{ title: 'invalidité, handicap', href: 'https://www.ameli.fr/assure/droits-demarches/invalidite-handicap' },
|
|
159
|
+
{ title: 'situations particumlières', href: 'https://www.ameli.fr/assure/droits-demarches/situations-particulieres' },
|
|
160
|
+
{ title: 'réclamation, médiation, recours', href: 'https://www.ameli.fr/assure/droits-demarches/reclamation-mediation-voies-de-recours' },
|
|
161
|
+
{ title: 'Europe, international', href: 'https://www.ameli.fr/assure/droits-demarches/europe-international' },
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
subMenuTitle: 'Remboursements',
|
|
168
|
+
subMenuSubtitle: 'prestations et aides',
|
|
169
|
+
sections: [
|
|
170
|
+
{
|
|
171
|
+
title: undefined,
|
|
172
|
+
items: [
|
|
173
|
+
{ title: 'Ce qui est remboursé', href: 'https://www.ameli.fr/assure/remboursements/rembourse' },
|
|
174
|
+
{ title: 'ce qui reste à votre charge', href: 'https://www.ameli.fr/assure/remboursements/reste-charge' },
|
|
175
|
+
{ title: 'Être bien remboursé', href: 'https://www.ameli.fr/assure/remboursements/etre-bien-rembourse' },
|
|
176
|
+
{ title: 'Indemnités journalières maladie, maternité, paternité', href: 'https://www.ameli.fr/assure/remboursements/indemnites-journalieres-maladie-maternite-paternite' },
|
|
177
|
+
{ title: 'Accident du travail : prise en charge et indemnités journalières', href: 'https://www.ameli.fr/assure/remboursements/accident-travail' },
|
|
178
|
+
{ title: 'Maladie professionnelle : prise en charge et indemnités journalières', href: 'https://www.ameli.fr/assure/remboursements/maladie-professionnelle' },
|
|
179
|
+
{ title: 'Pensions, allocations et rentes', href: 'https://www.ameli.fr/assure/remboursements/pensions-allocations-rentes' },
|
|
180
|
+
{ title: 'Incapacité permanente', href: 'https://www.ameli.fr/assure/remboursements/incapacite-permanente' },
|
|
181
|
+
{ title: 'Complémentaire santé solidaire : vous n\'avez rien à payer dans la plupart des cas ', href: 'https://www.ameli.fr/assure/remboursements/cmu-aides-financieres/complementaire-sante-solidaire' },
|
|
182
|
+
{ title: 'Aide médicale de l\'État et soins urgents', href: 'https://www.ameli.fr/assure/remboursements/aide-medicale-etat-soins-urgents' },
|
|
183
|
+
{ title: 'Compte ameli, mode d\'emploi', href: 'https://www.ameli.fr/assure/remboursements/suivre-remboursements' },
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
subMenuTitle: 'Maladies et prévention',
|
|
190
|
+
sections: [
|
|
191
|
+
{
|
|
192
|
+
title: undefined,
|
|
193
|
+
items: [
|
|
194
|
+
{ title: 'Tous les thèmes de santé', href: 'https://www.ameli.fr/assure/sante/themes' },
|
|
195
|
+
{ title: 'L\'Assurance Maladie vous accompagne', href: 'https://www.ameli.fr/assure/remboursements/reste-charge' },
|
|
196
|
+
{ title: 'Mon espace santé', href: 'https://www.ameli.fr/assure/sante/mon-espace-sante' },
|
|
197
|
+
{ title: 'Mon bilan prévention', href: 'https://www.ameli.fr/assure/sante/mon-bilan-prevention' },
|
|
198
|
+
{ title: 'Réagir en cas \'urgence ', href: 'https://www.ameli.fr/assure/sante/urgence' },
|
|
199
|
+
{ title: 'Accomplir les bons gestes ', href: 'https://www.ameli.fr/assure/sante/bons-gestes' },
|
|
200
|
+
{ title: 'Médicaments et vaccins', href: 'https://www.ameli.fr/assure/sante/medicaments' },
|
|
201
|
+
{ title: 'Déroulement d\'un examen', href: 'https://www.ameli.fr/assure/sante/examen' },
|
|
202
|
+
{ title: 'Certificat médical : dans quels cas et pour qui est-il obligatoire ?', href: 'https://www.ameli.fr/assure/sante/certificat-medical-quand-et-pour-qui' },
|
|
203
|
+
{ title: 'Devenir parent', href: 'https://www.ameli.fr/assure/sante/devenir-parent' },
|
|
204
|
+
{ title: 'Enfants', href: 'https://www.ameli.fr/assure/sante/enfants' },
|
|
205
|
+
{ title: 'Jeunes 16-25 ans', href: 'https://www.ameli.fr/assure/sante/jeunes-16-25-ans' },
|
|
206
|
+
{ title: 'Seniors', href: 'https://www.ameli.fr/assure/sante/seniors' },
|
|
207
|
+
{ title: 'Télésanté, la santé à distance', href: 'https://www.ameli.fr/assure/sante/telesante' },
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
title: 'Besoin d\'aide',
|
|
216
|
+
items: [
|
|
217
|
+
{
|
|
218
|
+
title: 'Contacter l\'Assurance Maladie',
|
|
219
|
+
subtitle: 'obtenir une attestation, envoyer une feuille de soins, contacter sa caisse, etc.',
|
|
220
|
+
href: 'https://www.ameli.fr/assure/adresses-contacts',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
title: 'Trouver un professionnel de santé',
|
|
224
|
+
subtitle: 'médecins, infirmiers...',
|
|
225
|
+
href: 'https://www.ameli.fr/assure/adresses-contacts' },
|
|
226
|
+
{ title: 'Télécharger un formulaire (ex: cerfa)', href: 'https://www.ameli.fr/assure/droits-demarches/formulaires' },
|
|
227
|
+
{ title: 'Consulter le forum', href: 'https://forum-assures.ameli.fr/' },
|
|
228
|
+
{ title: 'Sourds et malentendants', href: 'https://elioz.fr/elioz-connect/annuaire/assurance-maladie-annuaire/' },
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
return { args, menu, marker: mdiMapMarker }
|
|
233
|
+
},
|
|
234
|
+
template: `
|
|
235
|
+
<HeaderBar>
|
|
236
|
+
<template #menu>
|
|
237
|
+
<HeaderMenu>
|
|
238
|
+
<HeaderMenuSection v-for="section in menu" :key="section.title" :title="section.title">
|
|
239
|
+
<HeaderMenuItem v-for="item in section.items" :key="item.title">
|
|
240
|
+
<HeaderSubMenu v-if="item.subMenuTitle">
|
|
241
|
+
<template #title>
|
|
242
|
+
{{ item.subMenuTitle }}
|
|
243
|
+
<em v-if="item.subMenuSubtitle" style="font-style: normal; color: #757777;">{{ item.subMenuSubtitle }}</em>
|
|
244
|
+
</template>
|
|
245
|
+
<HeaderMenuSection v-for="subSection in item.sections" :key="subSection.title" :title="subSection.title">
|
|
246
|
+
<HeaderMenuItem v-for="subItem in subSection.items" :key="subItem.title">
|
|
247
|
+
<a :href="subItem.href">{{ subItem.title }}</a>
|
|
248
|
+
</HeaderMenuItem>
|
|
249
|
+
</HeaderMenuSection>
|
|
250
|
+
</HeaderSubMenu>
|
|
251
|
+
<a v-else :href="item.href">
|
|
252
|
+
{{ item.title }}
|
|
253
|
+
<em v-if="item.subtitle" style="font-style: normal; color: #757777;">{{ item.subtitle }}</em>
|
|
254
|
+
</a>
|
|
255
|
+
</HeaderMenuItem>
|
|
256
|
+
</HeaderMenuSection>
|
|
257
|
+
<h2 class="border-b-sm mb-2" style="font-size: 1.1rem; padding: 40px 16px 8px 20px;">Votre caisse</h2>
|
|
258
|
+
<div style="padding: 16px 50px 16px 20px;">
|
|
259
|
+
<div class="d-flex align-center ga-2 font-weight-bold">
|
|
260
|
+
<VIcon aria-label="Localisation" role="img" aria-hidden="false" color="primary">{{marker}}</VIcon>
|
|
261
|
+
<p>Vous n'avez pas sélectionné votre caisse</p>
|
|
262
|
+
</div>
|
|
263
|
+
<VBtn class="mt-3 mb-4 font-weight-bold text-capitalize" base-color="primary" density="comfortable" flat height="37">Sélectionner</VBtn>
|
|
264
|
+
<p class="mb-8">Les pages d'ameli seront alors enrichies des informations locales de votre caisse (contacts, événements régionaux, etc.) </p>
|
|
265
|
+
</div>
|
|
266
|
+
</HeaderMenu>
|
|
267
|
+
</template>
|
|
268
|
+
</HeaderBar>
|
|
269
|
+
`,
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, inject, nextTick, onMounted, onUnmounted, readonly, ref, watch, type CSSProperties, type Ref } from 'vue'
|
|
3
|
+
import HeaderMenuBtn from '../HeaderMenuBtn/HeaderMenuBtn.vue'
|
|
4
|
+
import { registerHeaderMenuKey } from '../consts'
|
|
5
|
+
import locals from './locals'
|
|
6
|
+
import useHandleSubMenus from './useHandleSubMenus'
|
|
7
|
+
|
|
8
|
+
const menuWrapper = ref<HTMLElement | null>(null)
|
|
9
|
+
const menuBtnWrapper = ref<HTMLDivElement | null>(null)
|
|
10
|
+
const outerBtn = ref<HTMLElement | null>(null)
|
|
11
|
+
const innerBtn = ref<HTMLElement | null>(null)
|
|
12
|
+
const menuLeft = ref(0)
|
|
13
|
+
const menuTop = ref(0)
|
|
14
|
+
|
|
15
|
+
function positionMenu() {
|
|
16
|
+
// todo debounce
|
|
17
|
+
menuLeft.value = menuBtnWrapper.value!.getBoundingClientRect().left
|
|
18
|
+
menuTop.value = menuBtnWrapper.value!.getBoundingClientRect().top
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
positionMenu()
|
|
23
|
+
window.addEventListener('scroll', positionMenu)
|
|
24
|
+
window.addEventListener('resize', positionMenu)
|
|
25
|
+
window.addEventListener('click', handleClickOutside, { capture: true })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
onUnmounted(() => {
|
|
29
|
+
window.removeEventListener('scroll', positionMenu)
|
|
30
|
+
window.removeEventListener('resize', positionMenu)
|
|
31
|
+
window.removeEventListener('click', handleClickOutside, { capture: true })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const menuOpen = ref(false)
|
|
35
|
+
|
|
36
|
+
watch(menuOpen, async (newVal) => {
|
|
37
|
+
document.documentElement.style.overflow = newVal ? 'hidden' : 'auto'
|
|
38
|
+
document.body.style.overflow = newVal ? 'hidden' : 'auto'
|
|
39
|
+
|
|
40
|
+
if (newVal) {
|
|
41
|
+
await nextTick()
|
|
42
|
+
innerBtn.value!.focus()
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
outerBtn.value!.focus()
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const menuStyle = computed<CSSProperties>(() => ({
|
|
50
|
+
left: `${menuLeft.value}px`,
|
|
51
|
+
top: `${menuTop.value}px`,
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
function handleClickOutside(event: MouseEvent | KeyboardEvent) {
|
|
55
|
+
if (!menuOpen.value) return
|
|
56
|
+
|
|
57
|
+
// do not close menu if click is inside the menu
|
|
58
|
+
let walkElement = event.target as HTMLElement | null
|
|
59
|
+
while (walkElement && walkElement !== document.body) {
|
|
60
|
+
if (walkElement === menuWrapper.value) return
|
|
61
|
+
walkElement = walkElement.parentElement
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
menuOpen.value = false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { haveOpenSubMenu } = useHandleSubMenus(readonly(menuOpen))
|
|
68
|
+
|
|
69
|
+
const registerHeaderMenu = inject<(menuOpen: Ref<boolean>) => void>(registerHeaderMenuKey)
|
|
70
|
+
if (registerHeaderMenu) registerHeaderMenu(menuOpen)
|
|
71
|
+
</script>
|
|
72
|
+
<template>
|
|
73
|
+
<div
|
|
74
|
+
role="dialog"
|
|
75
|
+
aria-modal="true"
|
|
76
|
+
:aria-label="locals.mainMenu"
|
|
77
|
+
>
|
|
78
|
+
<div ref="menuBtnWrapper">
|
|
79
|
+
<HeaderMenuBtn
|
|
80
|
+
ref="outerBtn"
|
|
81
|
+
v-model="menuOpen"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
<Transition name="menu">
|
|
85
|
+
<div
|
|
86
|
+
v-show="menuOpen"
|
|
87
|
+
class="overlay"
|
|
88
|
+
>
|
|
89
|
+
<div
|
|
90
|
+
ref="menuWrapper"
|
|
91
|
+
role="menu"
|
|
92
|
+
class="menu-wrapper"
|
|
93
|
+
:style="menuStyle"
|
|
94
|
+
>
|
|
95
|
+
<HeaderMenuBtn
|
|
96
|
+
ref="innerBtn"
|
|
97
|
+
v-model="menuOpen"
|
|
98
|
+
/>
|
|
99
|
+
<nav
|
|
100
|
+
id="header-menu-wrapper"
|
|
101
|
+
class="header-menu-wrapper"
|
|
102
|
+
:class="{
|
|
103
|
+
'header-menu-wrapper--submenu-open': haveOpenSubMenu,
|
|
104
|
+
}"
|
|
105
|
+
role="navigation"
|
|
106
|
+
:aria-label="locals.publicMenu"
|
|
107
|
+
>
|
|
108
|
+
<div class="header-menu">
|
|
109
|
+
<slot />
|
|
110
|
+
</div>
|
|
111
|
+
</nav>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</Transition>
|
|
115
|
+
</div>
|
|
116
|
+
</template>
|
|
117
|
+
|
|
118
|
+
<style lang="scss" scoped>
|
|
119
|
+
@use '@/assets/tokens.scss' as *;
|
|
120
|
+
@use '../consts' as *;
|
|
121
|
+
|
|
122
|
+
.overlay {
|
|
123
|
+
inset: 0;
|
|
124
|
+
position: fixed;
|
|
125
|
+
z-index: 1000;
|
|
126
|
+
background-color: rgba(3, 16, 37, .5);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.menu-wrapper {
|
|
130
|
+
height: 100dvh;
|
|
131
|
+
background-color: $neutral-white;
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.header-menu-wrapper {
|
|
137
|
+
height: calc(100% - $header-height);
|
|
138
|
+
display: grid;
|
|
139
|
+
position: relative;
|
|
140
|
+
overflow: auto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.header-menu-wrapper--submenu-open {
|
|
144
|
+
overflow: clip;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@media screen and (min-width: $header-breakpoint) {
|
|
148
|
+
.menu-wrapper {
|
|
149
|
+
position: absolute;
|
|
150
|
+
background-color: transparent;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.header-menu-wrapper {
|
|
154
|
+
height: $menu-height;
|
|
155
|
+
width: $menu-width;
|
|
156
|
+
overflow: visible;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.header-menu {
|
|
160
|
+
background-color: $neutral-white;
|
|
161
|
+
overflow-y : auto;
|
|
162
|
+
overflow-x: hidden;
|
|
163
|
+
height: $menu-height;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.menu-enter-active {
|
|
168
|
+
transition: opacity 0.15s ease-in;
|
|
169
|
+
|
|
170
|
+
.header-menu-wrapper {
|
|
171
|
+
transition: transform 0.1s ease-in;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.menu-leave-active {
|
|
176
|
+
transition: opacity 0.15s ease-out;
|
|
177
|
+
|
|
178
|
+
.header-menu-wrapper {
|
|
179
|
+
transition: transform 0.1s ease-out;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.menu-enter-from, .menu-leave-to {
|
|
184
|
+
opacity: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@media screen and (min-width: $header-breakpoint) {
|
|
188
|
+
.menu-enter-from, .menu-leave-to {
|
|
189
|
+
.header-menu-wrapper {
|
|
190
|
+
transform: translateY(10px);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@media (prefers-reduced-motion: reduce) {
|
|
196
|
+
.menu-enter-active, .menu-leave-active {
|
|
197
|
+
transition: opacity 0s;
|
|
198
|
+
}
|
|
199
|
+
.menu-enter-from, .menu-leave-to {
|
|
200
|
+
.header-menu-wrapper {
|
|
201
|
+
transform: none;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import HeaderMenuItem from './HeaderMenuItem.vue'
|
|
3
|
+
import HeaderMenu from '../HeaderComplexMenu.vue'
|
|
4
|
+
import HeaderBar from '../../HeaderBar.vue'
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/HeaderBar/HeaderComplexMenu/HeaderMenuItem',
|
|
8
|
+
component: HeaderMenuItem,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'fullscreen',
|
|
11
|
+
},
|
|
12
|
+
} satisfies Meta<typeof HeaderMenuItem>
|
|
13
|
+
|
|
14
|
+
export default meta
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof meta>
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
default: 'lorem ipsum',
|
|
21
|
+
},
|
|
22
|
+
render: (args) => {
|
|
23
|
+
return {
|
|
24
|
+
components: { HeaderMenuItem, HeaderMenu, HeaderBar },
|
|
25
|
+
setup() {
|
|
26
|
+
return { args }
|
|
27
|
+
},
|
|
28
|
+
template: `
|
|
29
|
+
<HeaderBar>
|
|
30
|
+
<template #menu>
|
|
31
|
+
<HeaderMenu>
|
|
32
|
+
<HeaderMenuSection>
|
|
33
|
+
<HeaderMenuItem>
|
|
34
|
+
<a>{{ args.default }}</a>
|
|
35
|
+
</HeaderMenuItem>
|
|
36
|
+
</HeaderMenuSection>
|
|
37
|
+
</HeaderMenu>
|
|
38
|
+
</template>
|
|
39
|
+
</HeaderBar>
|
|
40
|
+
`,
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
play: async ({ canvasElement }) => {
|
|
44
|
+
const menuBtn = canvasElement.querySelector('button')
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
menuBtn!.click()
|
|
47
|
+
}, 1000)
|
|
48
|
+
},
|
|
49
|
+
}
|
|
@@ -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>
|
package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuItem/tests/HeaderMenuItem.spec.ts
ADDED
|
@@ -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
|
+
})
|
package/src/components/HeaderBar/HeaderComplexMenu/HeaderMenuSection/HeaderMenuSection.stories.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import HeaderMenuSection from './HeaderMenuSection.vue'
|
|
3
|
+
import HeaderMenuItem from '../HeaderMenuItem/HeaderMenuItem.vue'
|
|
4
|
+
import HeaderMenu from '../HeaderComplexMenu.vue'
|
|
5
|
+
import HeaderBar from '../../HeaderBar.vue'
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Components/HeaderBar/HeaderComplexMenu/HeaderMenuSection',
|
|
9
|
+
component: HeaderMenuSection,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'fullscreen',
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta<typeof HeaderMenuSection>
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof meta>
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
title: 'section 1',
|
|
22
|
+
},
|
|
23
|
+
render: (args) => {
|
|
24
|
+
return {
|
|
25
|
+
components: { HeaderMenuItem, HeaderMenu, HeaderBar, HeaderMenuSection },
|
|
26
|
+
setup() {
|
|
27
|
+
return { args }
|
|
28
|
+
},
|
|
29
|
+
template: `
|
|
30
|
+
<HeaderBar>
|
|
31
|
+
<template #menu>
|
|
32
|
+
<HeaderMenu>
|
|
33
|
+
<HeaderMenuSection :title="args.title">
|
|
34
|
+
<HeaderMenuItem>
|
|
35
|
+
<a>lorem ipsum</a>
|
|
36
|
+
</HeaderMenuItem>
|
|
37
|
+
</HeaderMenuSection>
|
|
38
|
+
|
|
39
|
+
<HeaderMenuSection title="section 2">
|
|
40
|
+
<HeaderMenuItem>
|
|
41
|
+
<a>lorem ipsum</a>
|
|
42
|
+
</HeaderMenuItem>
|
|
43
|
+
</HeaderMenuSection>
|
|
44
|
+
</HeaderMenu>
|
|
45
|
+
</template>
|
|
46
|
+
</HeaderBar>
|
|
47
|
+
`,
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
play: async ({ canvasElement }) => {
|
|
51
|
+
const menuBtn = canvasElement.querySelector('button')
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
menuBtn!.click()
|
|
54
|
+
}, 1000)
|
|
55
|
+
},
|
|
56
|
+
}
|