Groupe de boutons - DsfrButtonGroup
🌟 Introduction
Les boutons dans le contexte d'un groupe suivent les même règles que le composant bouton :
- Il prend en charge les 2 types de boutons (primaire, secondaire) ;
- Il gère les 3 tailles (prop
size
valeurssm
,md
,lg
) et les variantes ( Icônes / texte seul, avec icônes à gauche / droite).
📐 Structure
Ce composant est une simple balise ul
qui peut recevoir un tableau de DsfrButtonProps & ButtonHTMLAttributes
pour mettre chaque bouton dans un li
.
Le slot par défaut peut être utilisé pour mettre vos boutons si la prop buttons
est absente (ou un tableau vide).
🛠️ Props
Aucune prop n’est obligatoire
Nom | Type | Défaut | Description |
---|---|---|---|
align | 'right' / 'center' / String | undefined | Définit l'alignement des boutons dans le groupe. Peut être 'right' ou 'center'. |
buttons | (DsfrButtonProps & ButtonHTMLAttributes)[] | () => [] | Liste des boutons à afficher. Chaque bouton est un objet qui peut inclure toutes les pros d’un DsfrButton, y compris un gestionnaire onClick . |
equisized | boolean | false | Si true , tous les boutons du groupe auront la même largeur. |
inlineLayoutWhen | string | boolean | 'never' | Détermine quand les boutons doivent être affichés sur une seule linge. Peut être 'always' , 'never' , ou correspondre à une taille spécifique ('sm' , 'md' , 'lg' ). |
iconRight | boolean | false | Si true , place les icônes à droite du texte dans tous les boutons. |
size | 'sm' | 'md' | 'lg' | 'md' | Détermine la taille des boutons. Peut être 'sm' (petit), 'md ' (moyen, défaut), 'lg' (grand). |
🧩 Slots
Le slot par défaut peut être utilisé pour mettre des boutons personnalisés.
Important
Si vous utilisez le slot, il faut bien envelopper chaque bouton dans une balise <li>
Cf. les exemples
📝 Exemples
vue
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrButton from '../DsfrButton.vue'
import DsfrButtonGroup from '../DsfrButtonGroup.vue'
const nb1 = ref(0)
const nb2 = ref(0)
const buttons = [
{
label: 'Bouton du premier groupe',
onclick: () => {
nb1.value++
},
},
{
label: 'Bouton secondaire du premier groupe',
secondary: true,
onclick: () => {
nb2.value++
},
},
]
</script>
<template>
<div class="fr-container fr-my-2w">
<div>
<p class="fr-text--lg">
Premier groupe, 2 petits boutons avec utilisation de la prop `buttons`
</p>
<DsfrButtonGroup
size="sm"
:buttons="buttons"
/>
<p class="fr-text--sm">
Bouton primaire cliqué {{ nb1 }} fois
</p>
<p class="fr-text--sm">
Bouton secondaire cliqué {{ nb2 }} fois
</p>
</div>
<div>
<p class="fr-text--lg">
Deuxième groupe, avec utilisation du slot
</p>
<DsfrButtonGroup
equisized
inline-layout-when="always"
>
<li>
<DsfrButton
label="1re"
primary
/>
</li>
<li>
<DsfrButton
label="2re"
secondary
/>
</li>
<li>
<DsfrButton
label="3re"
tertiary
/>
</li>
<li>
<DsfrButton
label="3re ss bord"
tertiary
no-outline
/>
</li>
</DsfrButtonGroup>
</div>
</div>
</template>
vue
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import DsfrButton from './DsfrButton.vue'
import type { DsfrButtonGroupProps } from './DsfrButton.types'
export type { DsfrButtonGroupProps }
const props = withDefaults(defineProps<DsfrButtonGroupProps>(), {
buttons: () => [],
inlineLayoutWhen: 'never',
size: 'md',
align: undefined,
})
const buttonsEl = ref<HTMLUListElement | null>(null)
const sm = computed(() => ['sm', 'small'].includes(props.size))
const md = computed(() => ['md', 'medium'].includes(props.size))
const lg = computed(() => ['lg', 'large'].includes(props.size))
const inlineAlways = computed(() => ['always', '', true].includes(props.inlineLayoutWhen))
const inlineSm = computed(() => ['sm', 'small'].includes(props.inlineLayoutWhen as string))
const inlineMd = computed(() => ['md', 'medium'].includes(props.inlineLayoutWhen as string))
const inlineLg = computed(() => ['lg', 'large'].includes(props.inlineLayoutWhen as string))
const center = computed(() => props.align === 'center')
const right = computed(() => props.align === 'right')
const equisizedWidth = ref('auto')
const groupStyle = computed(() => `--equisized-width: ${equisizedWidth.value};`)
const computeEquisizedWidth = async () => {
let maxWidth = 0
await new Promise((resolve) => setTimeout(resolve, 100))
buttonsEl.value?.querySelectorAll('.fr-btn').forEach((btn: Element) => {
const button = btn as HTMLButtonElement
const width = button.offsetWidth
const buttonStyle = window.getComputedStyle(button)
const marginLeft = +buttonStyle.marginLeft.replace('px', '')
const marginRight = +buttonStyle.marginRight.replace('px', '')
button.style.width = 'var(--equisized-width)'
const newWidth = width + marginLeft + marginRight
if (newWidth > maxWidth) {
maxWidth = newWidth
}
})
equisizedWidth.value = `${maxWidth}px`
}
onMounted(async () => {
if (!buttonsEl.value || !props.equisized) {
return
}
await computeEquisizedWidth()
})
</script>
<template>
<ul
ref="buttonsEl"
:style="groupStyle"
class="fr-btns-group"
:class="{
'fr-btns-group--equisized': equisized,
'fr-btns-group--sm': sm,
'fr-btns-group--md': md,
'fr-btns-group--lg': lg,
'fr-btns-group--inline-sm': inlineAlways || inlineSm,
'fr-btns-group--inline-md': inlineAlways || inlineMd,
'fr-btns-group--inline-lg': inlineAlways || inlineLg,
'fr-btns-group--center': center,
'fr-btns-group--right': right,
'fr-btns-group--icon-right': iconRight,
'fr-btns-group--inline-reverse': reverse,
}"
data-testid="fr-btns"
>
<li
v-for="({ onClick, ...button }, i) in buttons"
:key="i"
>
<DsfrButton
v-bind="button"
@click="onClick"
/>
</li>
<!-- @slot Slot par défaut pour le contenu de la liste de boutons. Sera dans `<ul class="fr-btns-group">` -->
<slot />
</ul>
</template>
ts
import type { ButtonHTMLAttributes } from 'vue'
import type VIcon from '../VIcon/VIcon.vue'
export type DsfrButtonProps = {
disabled?: boolean
label?: string
secondary?: boolean
tertiary?: boolean
iconRight?: boolean
iconOnly?: boolean
noOutline?: boolean
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | undefined
icon?: string | InstanceType<typeof VIcon>['$props']
onClick?: ($event: MouseEvent) => void
}
export type DsfrButtonGroupProps = {
buttons?: (DsfrButtonProps & ButtonHTMLAttributes)[]
reverse?: boolean
equisized?: boolean
iconRight?: boolean
align?: 'right' | 'center' | '' | undefined
inlineLayoutWhen?: 'always' | 'never' | 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | '' | true | undefined
size?: 'sm' | 'small' | 'lg' | 'large' | 'md' | 'medium' | undefined
}
Et voilà ! Vous êtes prêt à ajouter une touche de sophistication à votre interface avec DsfrButtonGroup. Bonne création ! 🎨✨