Groupe de champs de saisie - DsfrInputGroup 
🌟 Introduction 
Bienvenue dans la documentation du composant DsfrInputGroup, conçu pour envelopper vos champs de saisie avec une élégance à la française. Que vous composiez un message d'amour ou remplissiez un formulaire administratif, ce composant est là pour apporter de l'ordre et de la clarté à vos interfaces utilisateur.
Ce composant est très utile si vous souhaitez afficher un message d’erreur ou de succès pour un ou plusieurs champs de saisie DsfrInput.
🏅 La documentation sur le champ de saisie sur le DSFR
La story sur le champ de saisie sur le storybook de VueDsfr🛠️ Props 
| Nom | Type | Défaut | Obligatoire | Description | 
|---|---|---|---|---|
| descriptionId | Function | () => useRandomId(...) | ID unique pour la description du groupe, généré automatiquement si non spécifié. | |
| hint | string | '' | Texte d'indice pour guider l'utilisateur dans le groupe de champs. | |
| label | string | '' | Le libellé associé au groupe de champs. | |
| labelClass | string | '' | Classe CSS personnalisée pour le style du libellé. | |
| modelValue | string | '' | La valeur liée au modèle du composant DsfrInput. | |
| wrapperClass | string | '' | Classe CSS pour le style du conteneur du groupe. | |
| placeholder | string | undefined | Texte de l'espace réservé pour l'input. | |
| errorMessage | string | string[] | undefined | Message(s) d'erreur à afficher si une erreur est présente. | |
| validMessage | string | string[] | undefined | Message(s) de validation à afficher si l'input est valide. | 
Attributs implicitement déclarés 
Important
Toutes les props passées à <DsfrInputGroup> dans une template et qui ne sont pas définies dans les props seront passées à la balise <DsfrInput>. Si ces props ne sont pas définies non plus en tant que props dans DsfrInput, elles seront passés à la balise native <input>, comme par exemple readonly ou disabled.
Cf. la note important sur DsfrInput
📡Événements 
| Nom | Description | 
|---|---|
| update:modelValue | Événement émis lors de la mise à jour de la valeur de l'input. | 
🧩 Slots 
| Nom | Description | 
|---|---|
| before-input | Slot pour insérer du contenu avant le champ de saisie principal. | 
| default | Slot par défaut pour le contenu principal du groupe de champ. Utilisé pour insérer des éléments personnalisés. | 
📝 Exemples 
Voici comment vous pourriez utiliser DsfrInputGroup :
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrInput from '../DsfrInput.vue'
import DsfrInputGroup from '../DsfrInputGroup.vue'
const type = 'text'
const label = 'Label champ de saisie'
const placeholder = 'Yo'
const modelValue = ref('')
const modelValue2 = ref('')
const validMessage1 = 'Message de validation'
const errorMessage1 = 'Message d’erreur'
const errorMessage2 = 'Message d’erreur 2'
const hint = 'Texte d’indice du champ'
const readonly = ''
</script>
<template>
  <div class="fr-container fr-my-2w">
    <h2>1. Désactivé</h2>
    <DsfrInputGroup
      :placeholder="placeholder"
      :readonly="readonly !== ''"
      :model-value="modelValue"
      :label="label"
      :type="type"
      :hint="hint"
      label-visible
      disabled
    />
    <h2>2. Avec un message de succès</h2>
    <DsfrInputGroup
      :valid-message="validMessage1"
      :placeholder="placeholder"
      :readonly="readonly !== ''"
      :model-value="modelValue"
      :label="label"
      :type="type"
      :hint="hint"
      label-visible
    />
    <h2>3. Avec un message d’erreur</h2>
    <DsfrInputGroup
      :error-message="errorMessage1"
      :placeholder="placeholder"
      :readonly="readonly !== ''"
      :model-value="modelValue"
      :label="label"
      :type="type"
      :hint="hint"
      label-visible
    />
    <h2>4. Avec plusieurs messages</h2>
    <DsfrInputGroup
      :error-message="[errorMessage1, errorMessage2]"
      :valid-message="[validMessage1]"
      :placeholder="placeholder"
      :readonly="readonly !== ''"
      :model-value="modelValue"
      :label="label"
      :type="type"
      :hint="hint"
      label-visible
    />
    <h2>5. Avec plusieurs champs de saisie</h2>
    <DsfrInputGroup
      v-slot="slotProps"
      valid-message="Tout va bien pour ces deux champs"
    >
      <p>
        <DsfrInput
          v-bind="slotProps"
          :placeholder="placeholder"
          :readonly="readonly !== ''"
          :model-value="modelValue"
          :label="label"
          :type="type"
          :hint="hint"
          label-visible
        />
      </p>
      <p>
        <DsfrInput
          v-bind="slotProps"
          :placeholder="placeholder"
          :readonly="readonly !== ''"
          :model-value="modelValue2"
          :label="label"
          :type="type"
          :hint="hint"
          label-visible
        />
      </p>
    </DsfrInputGroup>
  </div>
</template>⚙️ Code source du composant 
<script lang="ts" setup>
import type { DsfrInputGroupProps } from './DsfrInput.types'
import { computed } from 'vue'
import { useRandomId } from '../../utils/random-utils'
import DsfrInput from './DsfrInput.vue'
export type { DsfrInputGroupProps }
defineOptions({
  inheritAttrs: false,
})
const props = withDefaults(defineProps<DsfrInputGroupProps>(), {
  inputGroupId: () => useRandomId('input-group', ''),
  descriptionId: () => useRandomId('input', 'group'),
  hint: '',
  label: '',
  labelClass: '',
  modelValue: '',
  wrapperClass: '',
  placeholder: undefined,
  errorMessage: undefined,
  validMessage: undefined,
})
defineEmits<{ (e: 'update:modelValue', payload: string): void }>()
function getDescriptionIdFromArray (messages: string[], baseId: string): string {
  return Array.from(Array.from({ length: messages.length })).map((_, idx) => `${baseId}-${idx + 1}`).join(' ')
}
const descId = computed(() => {
  if (!props.errorMessage && !props.validMessage) {
    return undefined
  }
  if (Array.isArray(props.errorMessage)) {
    return getDescriptionIdFromArray(props.errorMessage, props.descriptionId)
  }
  if (typeof props.errorMessage === 'string') {
    return props.descriptionId
  }
  if (typeof props.validMessage === 'string') {
    return props.descriptionId
  }
  if (Array.isArray(props.validMessage)) {
    return getDescriptionIdFromArray(props.validMessage, props.descriptionId)
  }
  return undefined
})
</script>
<template>
  <div
    class="fr-input-group"
    :class="[
      {
        'fr-input-group--disabled': 'disabled' in $attrs && $attrs.disabled !== false && $attrs.disabled !== undefined,
        'fr-input-group--error': errorMessage,
        'fr-input-group--valid': (validMessage && !errorMessage),
      },
      wrapperClass,
    ]"
    :data-testid="inputGroupId"
  >
    <slot name="before-input" />
    <!-- @slot Slot par défaut pour le contenu du groupe de champ -->
    <slot
      :is-valid="!!validMessage"
      :is-invalid="!!errorMessage"
      :description-id="descId"
    />
    <DsfrInput
      v-if="!$slots.default"
      v-bind="$attrs"
      :is-valid="!!validMessage"
      :is-invalid="!!errorMessage"
      :label="label"
      :hint="hint"
      :description-id="descId"
      :label-visible="labelVisible"
      :model-value="modelValue"
      :placeholder="placeholder"
      @update:model-value="$emit('update:modelValue', $event)"
    />
    <div
      class="fr-messages-group"
      role="alert"
      aria-live="polite"
    >
      <template
        v-if="Array.isArray(errorMessage)"
      >
        <p
          v-for="(message, idx) in errorMessage"
          :id="`${descriptionId}-${idx + 1}`"
          :key="message"
          :data-testid="descriptionId"
          class="fr-error-text"
        >
          {{ message }}
        </p>
      </template>
      <p
        v-else-if="errorMessage"
        :id="descriptionId"
        :key="errorMessage"
        :data-testid="descriptionId"
        class="fr-error-text"
      >
        {{ errorMessage }}
      </p>
      <template
        v-else-if="Array.isArray(validMessage)"
      >
        <p
          v-for="(message, idx) in validMessage"
          :id="`${descriptionId}-${idx + 1}`"
          :key="message"
          :data-testid="descriptionId"
          class="fr-valid-text"
        >
          {{ message }}
        </p>
      </template>
      <p
        v-else-if="validMessage"
        :id="descriptionId"
        :key="validMessage"
        :data-testid="descriptionId"
        class="fr-valid-text"
      >
        {{ validMessage }}
      </p>
    </div>
  </div>
</template>export type DsfrInputProps = {
  id?: string
  descriptionId?: string
  hint?: string
  isInvalid?: boolean
  isValid?: boolean
  isTextarea?: boolean
  isWithWrapper?: boolean
  labelVisible?: boolean
  label?: string
  labelClass?: string
  modelValue?: string | number | null
  wrapperClass?: string
}
export type DsfrInputGroupProps = {
  inputGroupId?: string
  descriptionId?: string
  hint?: string
  labelVisible?: boolean
  label?: string
  labelClass?: string
  modelValue?: string | number | null
  placeholder?: string
  errorMessage?: string | string[]
  validMessage?: string | string[]
  wrapperClass?: string
}