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 sizevaleurssm,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 type { DsfrButtonGroupProps } from './DsfrButton.types'
import { computed, onMounted, ref } from 'vue'
import DsfrButton from './DsfrButton.vue'
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 VIcon from '../VIcon/VIcon.vue'
import type { ButtonHTMLAttributes } from '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 ! 🎨✨
