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).
🛠️ Props
Nom | Type | Défaut | Obligatoire | Description |
---|---|---|---|---|
id | Function | () => getRandomId(...) | 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 { computed, ref, useAttrs } from 'vue'
import type { Ref } from 'vue'
import { getRandomId } from '../../utils/random-utils'
import type { DsfrInputProps } from './DsfrInput.types'
export type { DsfrInputProps }
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<DsfrInputProps>(), {
id: () => getRandomId('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 = {
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 !