Skip to content

Commit deba7ec

Browse files
committed
feat(DsfrButtonGroup): ✨ ajoute la prop equisized
1 parent 6b0d6ed commit deba7ec

File tree

6 files changed

+91
-214
lines changed

6 files changed

+91
-214
lines changed
Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
# DsfrButton
22

3-
🌟 **Introduction**
3+
## 🌟 Introduction
4+
5+
Le bouton est un élément d’interaction avec l’interface permettant à l’utilisateur d’effectuer une action.
46

57
Le `DsfrButton` est un composant Vue élégant et réutilisable, conçu pour simplifier la création de boutons personnalisés. Il intègre des tailles ajustables, des icônes optionnelles et un gestionnaire de clics, tout en respectant le style de `DSFR`. Son utilisation est simple, avec une grande flexibilité pour s'adapter à différents contextes.
68

9+
🏅 La documentation sur l’alerte sur le [DSFR](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/bouton)
10+
11+
<VIcon name="vi-file-type-storybook" /> La story sur l’alerte sur le storybook de [VueDsfr](https://vue-dsfr.netlify.app/?path=/docs/composants-dsfrbutton--docs)
12+
13+
## 📐 Structure
14+
15+
Les boutons sont composés de :
16+
17+
- Un label - obligatoire, soit en utilisant la prop `label` soit en utilisant le slot par défaut ;
18+
- Une icône, pouvant être modifiée (voir les icônes disponibles) - optionnelle.
19+
720
## 🛠️ Les props
821

922
| Nom | Type | Défaut | Obligatoire | Description |
1023
|-------------|----------------------------|------------|:-----------:|-------------------------------------------------------|
1124
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | | Taille du bouton. Peut être 'sm', 'md', ou 'lg'. |
1225
| `icon` | `string \| object` | `undefined`| | Icône à afficher dans le bouton. Peut être un nom ou une configuration d'icône. |
13-
| `label` | `string` | `undefined`| | Étiquette textuelle du bouton. |
26+
| `label` | `string` | `undefined`| | Étiquette textuelle du bouton. Si le label est laissé à undefined, le slot par défaut doit contenir du texte ! |
1427
| `onClick` | `Function` | `() => {}` | | Fonction appelée lors du clic sur le bouton. |
1528

1629
## 📡 Les événements
@@ -21,29 +34,33 @@ Le `DsfrButton` est un composant Vue élégant et réutilisable, conçu pour sim
2134

2235
- `default` : Emplacement pour le contenu personnalisé du bouton. Inséré dans `<button class="fr-btn"><span">`.
2336

37+
## ✨ Les groupes de boutons
38+
39+
Cf. [documentation dédiée](/composants/DsfrButtonGroup)
40+
2441
## 📝 Des exemples
2542

26-
```vue
27-
<DsfrButton
28-
size="lg"
29-
icon="fr-icon-home-4-fill"
30-
label="Accueil"
31-
@click="handleClick()"
32-
/>
43+
Un bouton large avec une icône 'maison' à gauche et le texte 'Accueil' :
3344

34-
Un bouton large avec une icône 'maison' et le texte 'Accueil'. L'événement de clic est géré par la méthode `handleClick()`.
45+
::: code-group
3546

36-
```vue
37-
<DsfrButton
38-
size="sm"
39-
label="Petit Bouton"
40-
@click="handleClick()"
41-
>
42-
Contenu supplémentaire
43-
</DsfrButton>
44-
```
47+
<Story data-title="Démo">
48+
<DsfrButtonExample1 />
49+
</Story>
4550

46-
Un petit bouton avec le texte 'Petit Bouton' et du contenu supplémentaire dans le slot par défaut. L'événement de clic est géré par la méthode `handleClick()`.
51+
<<< docs-demo/DsfrButtonExample1.vue [Code de la démo]
52+
:::
53+
54+
Un petit bouton avec le texte 'Aller plus loin', du contenu supplémentaire dans le slot par défaut, et une icône à droite :
55+
56+
::: code-group
57+
58+
<Story data-title="Démo">
59+
<DsfrButtonExample2 />
60+
</Story>
61+
62+
<<< docs-demo/DsfrButtonExample2.vue [Code de la démo]
63+
:::
4764

4865
## 📝 (Presque) toutes les variantes 🌈 de boutons
4966

@@ -53,13 +70,17 @@ Un petit bouton avec le texte 'Petit Bouton' et du contenu supplémentaire dans
5370
<DsfrButtonDemo />
5471
</Story>
5572

56-
<<< docs-demo/DsfrButtonExample.vue [Code de la démo]
73+
<<< docs-demo/DsfrButtonDemo.vue [Code de la démo]
5774

5875
<<< DsfrButton.vue
76+
77+
<<< DsfrButton.types.ts
5978
:::
6079

6180
Et voilà ! Notre DsfrButton est prêt à illuminer votre interface avec style et fonctionnalité. N'oubliez pas d'appuyer sur ces boutons avec panache ! 🚀
6281

6382
<script setup lang="ts">
64-
import DsfrButtonDemo from './docs-demo/DsfrButtonExample.vue'
83+
import DsfrButtonDemo from './docs-demo/DsfrButtonDemo.vue'
84+
import DsfrButtonExample1 from './docs-demo/DsfrButtonExample1.vue'
85+
import DsfrButtonExample2 from './docs-demo/DsfrButtonExample2.vue'
6586
</script>

src/components/DsfrButton/DsfrButton.types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ export type DsfrButtonProps = {
1717
export type DsfrButtonGroupProps = {
1818
buttons?: (DsfrButtonProps & ButtonHTMLAttributes)[]
1919
reverse?: boolean
20+
equisized?: boolean
2021
iconRight?: boolean
2122
align?: 'right' | 'center' | '' | undefined
22-
inlineLayoutWhen?: 'always' | 'never' | 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | undefined | boolean
23-
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | undefined
23+
inlineLayoutWhen?: 'always' | 'never' | 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | true | undefined
24+
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | undefined
2425
}

src/components/DsfrButton/DsfrButton.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const iconProps = computed(() => typeof props.icon === 'string' ? { name: props.
4949
:title="iconOnly ? label : undefined"
5050
:disabled="disabled"
5151
:aria-disabled="disabled"
52-
:style="(!dsfr && iconOnly) ? { 'padding-inline': '0.5rem' } : {}"
52+
:style="(!dsfrIcon && iconOnly) ? { 'padding-inline': '0.5rem' } : {}"
5353
@click="onClick ? onClick($event) : () => {}"
5454
>
5555
<VIcon

src/components/DsfrButton/DsfrButtonGroup.stories.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export default {
2727
options: ['never', 'always', 'small', 'medium', 'large'],
2828
description: 'Indique si le groupe de boutons doit apparaître en empilement horizontal (toujours, ou seulement sur les tailles de vue spécifiées)',
2929
},
30+
equisized: {
31+
control: 'boolean',
32+
description: 'Indique si la taille des boutons doit être la même pour tous les boutons (prend la taille du plus large) si est à `true`, ou si chaque bouton doit avoir sa propre taille si est à `false`',
33+
},
3034
reverse: {
3135
control: 'boolean',
3236
description: 'Indique si l’ordre des boutons doit être inversé par rapport au DOM.\n\n *N.B. : Ne fonctionne que si `align` est à `right`*',
@@ -43,7 +47,7 @@ export default {
4347
align: {
4448
control: 'radio',
4549
options: ['default', 'center', 'right'],
46-
description: 'Indique l\'alignement du groupe de boutons',
50+
description: 'Indique lalignement du groupe de boutons',
4751
},
4852
onClick: { action: 'clicked' },
4953
},

src/components/DsfrButton/DsfrButtonGroup.vue

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts" setup>
2-
import { computed } from 'vue'
2+
import { computed, onMounted, ref } from 'vue'
33
44
import type { DsfrButtonGroupProps } from './DsfrButton.types'
55
import DsfrButton from './DsfrButton.vue'
@@ -9,33 +9,64 @@ export type { DsfrButtonGroupProps }
99
const props = withDefaults(defineProps<DsfrButtonGroupProps>(), {
1010
buttons: () => [],
1111
inlineLayoutWhen: 'never',
12-
size: '',
12+
size: 'md',
1313
align: undefined,
1414
})
1515
16+
const buttonsEl = ref<HTMLUListElement | null>(null)
17+
1618
const sm = computed(() => ['sm', 'small'].includes(props.size))
1719
const md = computed(() => ['md', 'medium'].includes(props.size))
1820
const lg = computed(() => ['lg', 'large'].includes(props.size))
1921
20-
const inlineAlways = computed(() => ['always', true].includes(props.inlineLayoutWhen))
22+
const inlineAlways = computed(() => ['always', '', true].includes(props.inlineLayoutWhen))
2123
const inlineSm = computed(() => ['sm', 'small'].includes(props.inlineLayoutWhen as string))
2224
const inlineMd = computed(() => ['md', 'medium'].includes(props.inlineLayoutWhen as string))
2325
const inlineLg = computed(() => ['lg', 'large'].includes(props.inlineLayoutWhen as string))
2426
const center = computed(() => props.align === 'center')
2527
const right = computed(() => props.align === 'right')
28+
29+
const equisizedWidth = ref('auto')
30+
const groupStyle = computed(() => `--equisized-width: ${equisizedWidth.value};`)
31+
32+
const computeEquisizedWidth = async () => {
33+
let maxWidth = 0
34+
await new Promise((resolve) => setTimeout(resolve, 100))
35+
buttonsEl.value?.querySelectorAll('.fr-btn').forEach((button: Element) => {
36+
const width = button.offsetWidth
37+
const buttonStyle = window.getComputedStyle(button)
38+
const marginLeft = +buttonStyle.marginLeft.replace('px', '')
39+
const marginRight = +buttonStyle.marginRight.replace('px', '')
40+
button.style.width = 'var(--equisized-width)'
41+
const newWidth = width + marginLeft + marginRight
42+
if (newWidth > maxWidth) {
43+
maxWidth = newWidth
44+
}
45+
})
46+
equisizedWidth.value = maxWidth + 'px'
47+
}
48+
49+
onMounted(async () => {
50+
if (!buttonsEl.value || !props.equisized) {
51+
return
52+
}
53+
await computeEquisizedWidth()
54+
})
2655
</script>
2756

2857
<template>
2958
<ul
59+
ref="buttonsEl"
60+
:style="groupStyle"
3061
class="fr-btns-group"
3162
:class="{
32-
'fr-btns-group--inline': inlineAlways,
63+
'fr-btns-group--equisized': equisized,
3364
'fr-btns-group--sm': sm,
3465
'fr-btns-group--md': md,
3566
'fr-btns-group--lg': lg,
36-
'fr-btns-group--inline-sm': inlineSm,
37-
'fr-btns-group--inline-md': inlineMd,
38-
'fr-btns-group--inline-lg': inlineLg,
67+
'fr-btns-group--inline-sm': inlineAlways || inlineSm,
68+
'fr-btns-group--inline-md': inlineAlways || inlineMd,
69+
'fr-btns-group--inline-lg': inlineAlways || inlineLg,
3970
'fr-btns-group--center': center,
4071
'fr-btns-group--right': right,
4172
'fr-btns-group--icon-right': iconRight,
@@ -52,5 +83,6 @@ const right = computed(() => props.align === 'right')
5283
@click="onClick"
5384
/>
5485
</li>
86+
<slot />
5587
</ul>
5688
</template>

0 commit comments

Comments
 (0)