Champ de saisie - DsfrInput 
🌟 Introduction 
Le composant DsfrInput, outil essentiel dans l'arsenal de tout développeur Vue ! Que ce soit pour saisir votre nom de fromage préféré ou la date de votre dernière visite à la Tour Eiffel, DsfrInput est là pour rendre la saisie de données aussi douce qu'un croissant frais le matin 🥐 (oui, on aime bien les croissants par ici).
🏅 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 | 
|---|---|---|---|---|
| id | Function | () => useRandomId(...) | Identifiant unique pour l'input. Si non spécifié, un ID aléatoire est généré. | |
| descriptionId | string | undefined | ID pour la description associée à l'input. Utile pour l'accessibilité. | |
| hint | string | '' | Texte d'indice pour guider l'utilisateur. | |
| label | string | '' | Le libellé de l'input. | |
| labelClass | string | '' | Classe personnalisée pour le style du libellé. | |
| modelValue | string | '' | La valeur liée au modèle de l'input. | |
| wrapperClass | string | '' | Classe personnalisée pour le style du conteneur de l'input. | 
Attributs implicitement déclarés 
Important
Toutes les props passées à <DsfrInput> dans une template et qui ne sont pas définies dans les props seront passées à la balise <input> native du composant (cf. Attributs implicitement déclarés (Fallthrough attributes) de la documentation officielle de Vue.js.). Comme par exemple readonly.
Voici une liste non-exhaustive:
- name
- readonly
- disabled
- autocomplete
- autofocus(déconseillé)
- size
- maxlength
- pattern
Exemple :
<script setup>
// (...)
</script>
<template>
  <DsfrInput
    v-model="username"
    label="Nom d’utilisateur"
    name="username"
    pattern="\w{3,20}"
  />
</template>📡Événements 
| Nom | Description | 
|---|---|
| update:modelValue | Événement émis lors de la mise à jour de la valeur de l'input. | 
🧩 Slots 
| Nom | Description | 
|---|---|
| label | Slot pour personnaliser le contenu de la balise <label>. | 
| required-tip | Slot pour indiquer si le champ est obligatoire. Par défaut, affiche une astérisque si requis. | 
📝 Exemples 
Exemple simple d'utilisation de DsfrInput :
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrInput from '../DsfrInput.vue'
const name = ref('')
</script>
<template>
  <div class="fr-container fr-my-2w">
    <h2>1. Simple</h2>
    <DsfrInput
      v-model="name"
      label="Nom"
      placeholder="Jean Dupont"
      label-visible
      required
      hint="Indiquez votre nom"
    />
    <p>{{ name }}</p>
    <h2>2. Avec utilisation du slot <code>#required-tip</code></h2>
    <DsfrInput
      v-model="name"
      label="Nom"
      label-visible
      hint="Entrez votre nom complet"
      required
    >
      <template #required-tip>
        <span class="custom-required"> (requis)</span>
      </template>
    </DsfrInput>
  </div>
</template>
<style scoped>
.custom-required {
  color: red;
  font-style: italic;
}
</style>⚙️ Code source du composant 
<script lang="ts" setup>
import type { DsfrInputProps } from './DsfrInput.types'
import type { Ref } from 'vue'
import { computed, ref, useAttrs } from 'vue'
import { useRandomId } from '../../utils/random-utils'
export type { DsfrInputProps }
defineOptions({
  inheritAttrs: false,
})
const props = withDefaults(defineProps<DsfrInputProps>(), {
  id: () => useRandomId('basic', 'input'),
  descriptionId: undefined,
  hint: '',
  label: '',
  labelClass: '',
  modelValue: '',
  wrapperClass: '',
})
defineEmits<{ (e: 'update:modelValue', payload: string): void }>()
const attrs = useAttrs()
const __input: Ref<HTMLElement | null> = ref(null)
const focus = () => __input.value?.focus()
const isComponent = computed(() => props.isTextarea ? 'textarea' : 'input')
const wrapper = computed(() => props.isWithWrapper || attrs.type === 'date' || !!props.wrapperClass)
const finalLabelClass = computed(() => [
  'fr-label',
  { invisible: !props.labelVisible },
  props.labelClass,
])
defineExpose({
  focus,
})
</script>
<template>
  <label
    :class="finalLabelClass"
    :for="id"
  >
    <!-- @slot Slot pour personnaliser tout le contenu de la balise <label> -->
    <slot name="label">
      {{ label }}
      <!-- @slot Slot pour indiquer que le champ est obligatoire. Par défaut, met une astérisque si `required` est à true (dans un `<span class="required">`) -->
      <slot name="required-tip">
        <span
          v-if="'required' in $attrs && $attrs.required !== false"
          class="required"
        >*</span>
      </slot>
    </slot>
    <span
      v-if="hint"
      class="fr-hint-text"
    >{{ hint }}</span>
  </label>
  <component
    :is="isComponent"
    v-if="!wrapper"
    :id="id"
    v-bind="$attrs"
    ref="__input"
    class="fr-input"
    :class="{
      'fr-input--error': isInvalid,
      'fr-input--valid': isValid,
    }"
    :value="modelValue"
    :aria-describedby="descriptionId || undefined"
    @input="$emit('update:modelValue', $event.target.value)"
  />
  <div
    v-else
    :class="[
      { 'fr-input-wrap': isWithWrapper || $attrs.type === 'date' },
      wrapperClass,
    ]"
  >
    <component
      :is="isComponent"
      :id="id"
      v-bind="$attrs"
      ref="__input"
      class="fr-input"
      :class="{
        'fr-input--error': isInvalid,
        'fr-input--valid': isValid,
      }"
      :value="modelValue"
      :aria-describedby="descriptionId || undefined"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>
<style scoped>
.invisible {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
</style>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
}Avec DsfrInput, la saisie de données devient aussi élégante que la promenade dans un vignoble en automne. 🍇 Bonne programmation !
