Skip to content

Commit 83baa15

Browse files
authored
Merge pull request #101 from dnum-mi/feat/dsfr-side-menu
Feat/dsfr side menu
2 parents 655a618 + 43af304 commit 83baa15

26 files changed

+843
-9
lines changed

ci/check-exports.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ if (currentFileContent !== correctString) {
3535
},
3636
]
3737

38-
const missingComponent = ''
3938
const currentFileContentAsList = currentFileContent.split('\n').slice(0, -1).sort()
4039
const onlyInCorrectList = correctComponentList.filter(line => !currentFileContentAsList.includes(line))
4140
if (onlyInCorrectList.length) {
@@ -59,6 +58,7 @@ if (currentFileContent !== correctString) {
5958
await inquirer.prompt(questions).then(async answers => {
6059
if (answers.fix.toLocaleLowerCase() === 'y') {
6160
await writeFile(path.resolve(__dirname, '../src/components/index.js'), correctString)
61+
await writeFile(path.resolve(__dirname, '../types/components/index.d.ts'), correctString)
6262
console.log(chalk.green.bold('Fichier corrigé !'))
6363
process.exit(0)
6464
}

src/components/DsfrNavigation/DsfrNavigationItem.stories.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
description: 'Permet de voir le composant dans les deux **thèmes** : **clair** (`false`, défaut) et **sombre** (`true`).\n\n*N.B. : Ne fait pas partie du composant.*',
1212
},
1313
id: {
14-
control: 'string',
14+
control: 'text',
1515
description: 'Valeur de l’attribut `id` de ce sous-menu. *N.B. : Il est recommandé de ne pas le donner, la bibliothèque lui en donnera un aléatoire*.',
1616
},
1717
active: {

src/components/DsfrNavigation/DsfrNavigationMenu.stories.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
description: 'Permet de voir le composant dans les deux **thèmes** : **clair** (`false`, défaut) et **sombre** (`true`).\n\n*N.B. : Ne fait pas partie du composant.*',
1212
},
1313
id: {
14-
control: 'string',
14+
control: 'text',
1515
description: 'Valeur de l’attribut `id` de ce sous-menu. *N.B. : Il est recommandé de ne pas le donner, la bibliothèque lui en donnera un aléatoire*.',
1616
},
1717
links: {

src/components/DsfrNavigation/DsfrNavigationMenuItem.stories.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ export default {
1717
description: 'Valeur de l’id du menu déroulé. *N.B. : Ne fait pas partie de ce composant.*',
1818
},
1919
menuId: {
20-
control: 'string',
20+
control: 'text',
2121
description: 'Valeur de l’attribut `id` de ce sous-menu. *N.B. : Il est recommandé de ne pas le donner, la bibliothèque lui en donnera un aléatoire*. *N.B. : Ne fait pas partie de ce composant.*',
2222
},
2323
id: {
24-
control: 'string',
24+
control: 'text',
2525
description: 'Valeur de l’attribut `id` de cet item de menu de navigation.',
2626
},
2727
active: {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { render } from '@testing-library/vue'
2+
3+
import DsfrSideMenu from './DsfrSideMenu.vue'
4+
5+
describe('DsfrSideMenu', () => {
6+
it('should render a DsfrSideMenu component', () => {
7+
const headingTitle = 'Titre de la rubrique'
8+
9+
const { getByRole, getByText } = render(DsfrSideMenu, {
10+
props: {
11+
headingTitle,
12+
},
13+
})
14+
// Il y a un élément 'nav' avec un rôle 'navigation'
15+
// dont l’attribut 'aria-label' est "Menu latéral"
16+
const navEl = getByRole('navigation')
17+
expect(navEl.tagName).toBe('NAV')
18+
expect(navEl).toHaveAttribute('aria-label', 'Menu latéral')
19+
// e premier enfant de navEl est un div avec la classe 'fr-sidemenu__inner'
20+
expect(navEl.firstElementChild).toHaveClass('fr-sidemenu__inner')
21+
// l’enfant suivant doit être un bouton qui doit avoir la classe 'fr-sidemenu__btn'
22+
const buttonEl = getByText('Dans cette rubrique')
23+
expect(buttonEl.tagName).toBe('BUTTON')
24+
expect(buttonEl).toHaveClass('fr-sidemenu__btn')
25+
const sideMenuWrapper = buttonEl.nextElementSibling
26+
expect(buttonEl).toHaveAttribute('aria-controls', sideMenuWrapper.id)
27+
expect(sideMenuWrapper).toHaveClass('fr-collapse')
28+
expect(sideMenuWrapper.firstElementChild).toHaveClass('fr-sidemenu__title')
29+
expect(sideMenuWrapper.firstElementChild).toContainHTML(headingTitle)
30+
})
31+
})
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import DsfrSideMenu from './DsfrSideMenu.vue'
2+
3+
function toggleExpandedForMenuWithId (menuItems, id) {
4+
menuItems.forEach(menuItem => {
5+
if (menuItem.id === id) {
6+
menuItem.expanded = !menuItem.expanded
7+
return
8+
}
9+
if (menuItem.menuItems) {
10+
toggleExpandedForMenuWithId(menuItem.menuItems, id)
11+
}
12+
})
13+
}
14+
15+
export default {
16+
component: DsfrSideMenu,
17+
title: 'Composants/Menu latéral/1 - Menu entier - DsfrSideMenu',
18+
argTypes: {
19+
dark: {
20+
control: 'boolean',
21+
description: 'Permet de voir le composant dans les deux **thèmes** : **clair** (`false`, défaut) et **sombre** (`true`).\n\n*N.B. : Ne fait pas partie du composant.*',
22+
},
23+
headingTitle: {
24+
control: 'text',
25+
description: 'Titre de la rubrique (c’est le titre du menu latéral)',
26+
},
27+
buttonLabel: {
28+
control: 'text',
29+
description: 'Label associé au bouton en état responsive dont le rôle est de déplier le side menu',
30+
},
31+
menuItems: {
32+
control: 'object',
33+
description: 'Tableau d’objets contenant les props attendus par DsfrSideMenuList',
34+
},
35+
onclick: {
36+
action: 'Clicked',
37+
},
38+
},
39+
}
40+
41+
export const MenuLateral = (args, { argTypes }) => ({
42+
components: {
43+
DsfrSideMenu,
44+
},
45+
46+
data () {
47+
return {
48+
...args,
49+
}
50+
},
51+
52+
mounted () {
53+
document.body.parentElement.setAttribute('data-fr-theme', this.dark ? 'dark' : 'light')
54+
},
55+
56+
methods: {
57+
toggleExpand (id) {
58+
toggleExpandedForMenuWithId(this.menuItems, id)
59+
},
60+
},
61+
62+
template: `
63+
<DsfrSideMenu
64+
:heading-title="headingTitle"
65+
:buttonLabel="buttonLabel"
66+
:menu-items="menuItems"
67+
@toggle-expand="toggleExpand"
68+
/>
69+
`,
70+
})
71+
MenuLateral.args = {
72+
dark: false,
73+
buttonLabel: 'Dans cette rubrique',
74+
headingTitle: 'Titre de la rubrique',
75+
menuItems: [
76+
{
77+
id: 11,
78+
to: '/rubrique-1',
79+
text: 'Premier titre de niveau 1',
80+
},
81+
{
82+
id: 12,
83+
to: '/rubrique-2',
84+
text: 'Deuxième titre de niveau 1',
85+
active: true,
86+
menuItems: [
87+
{
88+
id: 21,
89+
to: '/rubrique-2/sous-rubrique-1',
90+
text: 'Premier titre de niveau 2',
91+
},
92+
{
93+
id: 22,
94+
to: '/rubrique-2/sous-rubrique-2',
95+
text: 'Deuxième titre de niveau 2',
96+
active: true,
97+
menuItems: [
98+
{
99+
id: 31,
100+
to: '/rubrique-2/sous-rubrique-2/sous-sous-rubrique-1',
101+
text: 'Premier titre de niveau 3',
102+
},
103+
{
104+
id: 32,
105+
to: '/rubrique-2/sous-rubrique-2/sous-sous-rubrique-2',
106+
text: 'Deuxième titre de niveau 3',
107+
active: true,
108+
},
109+
],
110+
},
111+
],
112+
},
113+
],
114+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<script setup>
2+
import { getRandomId } from '../../utils/random-utils.js'
3+
4+
import '@gouvfr/dsfr/dist/component/sidemenu/sidemenu.module.js'
5+
6+
import DsfrSideMenuList from './DsfrSideMenuList.vue'
7+
8+
defineProps({
9+
buttonLabel: {
10+
type: String,
11+
default: 'Dans cette rubrique',
12+
},
13+
id: {
14+
type: String,
15+
default () {
16+
return getRandomId('sidemenu')
17+
},
18+
},
19+
menuItems: {
20+
type: Array,
21+
default: () => undefined,
22+
},
23+
headingTitle: {
24+
type: String,
25+
default: 'Titre de la rubrique',
26+
},
27+
})
28+
29+
defineEmits(['toggle-expand'])
30+
</script>
31+
32+
<template>
33+
<nav
34+
class="fr-sidemenu"
35+
aria-label="Menu latéral"
36+
>
37+
<div class="fr-sidemenu__inner">
38+
<button
39+
class="fr-sidemenu__btn"
40+
:aria-controls="id"
41+
aria-expanded="false"
42+
>
43+
{{ buttonLabel }}
44+
</button>
45+
<div
46+
:id="id"
47+
class="fr-collapse"
48+
>
49+
<div class="fr-sidemenu__title">
50+
{{ headingTitle }}
51+
</div>
52+
<!-- @slot Slot par défaut du contenu du menu latéral -->
53+
<slot>
54+
<DsfrSideMenuList
55+
:menu-items="menuItems"
56+
@toggle-expand="$emit('toggle-expand', $event)"
57+
/>
58+
</slot>
59+
</div>
60+
</div>
61+
</nav>
62+
</template>
63+
64+
<style src="@gouvfr/dsfr/dist/component/sidemenu/sidemenu.main.css" />
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import DsfrSideMenu from './DsfrSideMenu.vue'
2+
import DsfrSideMenuList from './DsfrSideMenuList.vue'
3+
import DsfrSideMenuListItem from './DsfrSideMenuListItem.vue'
4+
import DsfrSideMenuLink from './DsfrSideMenuLink.vue'
5+
import DsfrSideMenuButton from './DsfrSideMenuButton.vue'
6+
7+
export default {
8+
component: DsfrSideMenuButton,
9+
title: 'Composants/Menu latéral/5 - Bouton de menu dépliable - DsfrSideMenuButton',
10+
argTypes: {
11+
dark: {
12+
control: 'boolean',
13+
description: 'Permet de voir le composant dans les deux **thèmes** : **clair** (`false`, défaut) et **sombre** (`true`).\n\n*N.B. : Ne fait pas partie du composant.*',
14+
},
15+
active: {
16+
control: 'boolean',
17+
description: 'indique que l’item de menu correspond à la page courante',
18+
},
19+
expanded: {
20+
control: 'boolean',
21+
description: 'Permet de dire si le menu associé est plié (`false`) ou déplié (`true`)',
22+
},
23+
controlId: {
24+
control: 'text',
25+
description: 'Valeur de l’id du menu associé qui sera plié et déplié lors du clic sur ce bouton',
26+
},
27+
},
28+
}
29+
30+
export const BoutonDeMenuDepliable = (args, { argTypes }) => ({
31+
components: {
32+
DsfrSideMenu,
33+
DsfrSideMenuList,
34+
DsfrSideMenuListItem,
35+
DsfrSideMenuLink,
36+
DsfrSideMenuButton,
37+
},
38+
39+
data () {
40+
return {
41+
...args,
42+
isExpanded: args.expanded,
43+
}
44+
},
45+
46+
mounted () {
47+
document.body.parentElement.setAttribute('data-fr-theme', this.dark ? 'dark' : 'light')
48+
},
49+
50+
template: `
51+
<DsfrSideMenu
52+
heading-title="Menu latéral exemplaire"
53+
buttonLabel="Bouton exemplaire"
54+
>
55+
<DsfrSideMenuList>
56+
<DsfrSideMenuListItem>
57+
<DsfrSideMenuButton
58+
:active="active"
59+
:expanded="isExpanded"
60+
:controlId="controlId"
61+
@toggle-expand="isExpanded = !isExpanded"
62+
>
63+
Item de menu actifavec sous-menu
64+
</DsfrSideMenuButton>
65+
<DsfrSideMenuList
66+
:id="controlId"
67+
:expanded="expanded"
68+
:collapsable="true"
69+
>
70+
<DsfrSideMenuListItem>
71+
<DsfrSideMenuLink
72+
to="/one"
73+
>
74+
Lien 1
75+
</DsfrSideMenuLink>
76+
</DsfrSideMenuListItem>
77+
<DsfrSideMenuListItem>
78+
<DsfrSideMenuLink
79+
to="/two"
80+
>
81+
Lien 2
82+
</DsfrSideMenuLink>
83+
</DsfrSideMenuListItem>
84+
</DsfrSideMenuList>
85+
</DsfrSideMenuListItem>
86+
</DsfrSideMenuList>
87+
</DsfrSideMenu>
88+
`,
89+
})
90+
BoutonDeMenuDepliable.args = {
91+
dark: false,
92+
active: false,
93+
expanded: false,
94+
controlId: 'sidemenu-1',
95+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup>
2+
defineProps({
3+
active: Boolean,
4+
expanded: Boolean,
5+
controlId: {
6+
type: String,
7+
required: true,
8+
},
9+
})
10+
11+
defineEmits(['toggle-expand'])
12+
</script>
13+
14+
<template>
15+
<button
16+
class="fr-sidemenu__btn"
17+
:aria-current="!!active"
18+
:aria-expanded="!!expanded"
19+
:aria-controls="controlId"
20+
@click="$emit('toggle-expand', controlId)"
21+
>
22+
<!-- @slot Slot par défaut pour le texte du bouton -->
23+
<slot />
24+
</button>
25+
</template>

0 commit comments

Comments
 (0)