Curseur - DsfrRange
🌟 Introduction
Bienvenue dans la documentation du DsfrRange
, un composant Vue qui va slider dans votre coeur comme un croissant bien chaud glisse dans votre petit déjeuner. Ce composant est un véritable couteau suisse pour les curseurs, capable de tout faire, de l'affichage simple à la gestion de valeurs doubles. Mettez vos ceintures, on décolle !
Les curseurs sont des entrées numériques qui permettent de voir graphiquement une sélection par rapport a une valeur minimale et maximale. Ils servent à montrer en temps réel les options choisies et éclairer la prise de décision. ("Why so serious?" 🦇🃏)
🏅 La documentation sur le curseur importante sur le DSFR
La story sur le curseur importante sur le storybook de VueDsfr📐 Structure
- Le composant est encapsulé dans une
div
avec la classefr-range-group
, qui peut afficher un message d'erreur viamessage
. - Le
label
est affiché en haut, suivi par un texte d'indice (hint
) si fourni. - Le curseur (
input type="range"
) est stylisé avec des classes pour gérer la taille et l'état désactivé. - Les valeurs minimales et maximales sont affichées si
hideIndicators
estfalse
. - Un second curseur est présent si la prop
double
esttrue
. - Les messages d'erreur ou autres sont affichés dans une
div
spécifique.
🛠️ Props
Nom | Type | Défaut | Description |
---|---|---|---|
id | string | getRandomId('range') | Identifiant unique du curseur. Si non fourni, un id est généré aléatoirement. |
min | number | 0 | Valeur minimale du curseur. |
max | number | 100 | Valeur maximale du curseur. |
modelValue | number | 0 | Valeur actuelle du curseur. |
label | string | - | Texte de l'étiquette associée au curseur. |
hint | string | undefined | Texte d'indice optionnel. |
message | string | undefined | Message à afficher en cas d'erreur. |
prefix | string | undefined | Texte à afficher avant la valeur. |
suffix | string | undefined | Texte à afficher après la valeur. |
small | boolean | undefined | Si true , réduit la taille du curseur. |
hideIndicators | boolean | undefined | Cache les indicateurs de valeur min/max si true . |
step | number | undefined | Pas d'incrément du curseur. |
double | boolean | undefined | Active un second curseur si true . |
disabled | boolean | undefined | Désactive le curseur si true . |
📡 Évenements
update:modelValue
: Émis lors de la modification de la valeur du curseur. Renvoie la nouvelle valeur.
📝 Exemple
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrRange from '../DsfrRange.vue'
const value = ref<number>(100)
const value2 = ref<number>(100)
const lowerValue = ref<number>(0)
</script>
<template>
<div class="fr-container fr-py-4w">
<div>
<DsfrRange
v-model="value"
label="Label du curseur"
/>
</div>
<p>
{{ value }}
</p>
<div>
<DsfrRange
v-model="value2"
v-model:lower-value="lowerValue"
label="Label du curseur"
/>
</div>
<p>
{{ lowerValue }} - {{ value2 }}
</p>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue'
import { getRandomId } from '../../utils/random-utils'
import type { DsfrRangeProps } from './DsfrRange.types'
const props = withDefaults(defineProps<DsfrRangeProps>(), {
id: () => getRandomId('range'),
min: 0,
max: 100,
modelValue: 0,
lowerValue: undefined,
hint: undefined,
message: undefined,
prefix: undefined,
suffix: undefined,
step: undefined,
})
const emit = defineEmits<{
(e: 'update:modelValue', payload: string | number): void
(e: 'update:lowerValue', payload: string | number): void
}>()
const input = ref<HTMLInputElement>()
const output = ref<HTMLSpanElement>()
const inputWidth = ref()
const double = computed(() => props.lowerValue !== undefined)
const outputStyle = computed(() => {
if (props.lowerValue === undefined) {
const translateXValue = (props.modelValue - props.min) / (props.max - props.min) * inputWidth.value
return `transform: translateX(${translateXValue}px) translateX(-${props.modelValue}%);`
}
const translateXValue = (props.modelValue + props.lowerValue - props.min) / 2 / (props.max - props.min) * inputWidth.value
return `transform: translateX(${translateXValue}px) translateX(-${props.lowerValue + ((props.modelValue - props.lowerValue) / 2)}%);`
})
const rangeStyle = computed(() => {
const progressRight = (props.modelValue - props.min) / (props.max - props.min) * inputWidth.value - (double.value ? 12 : 0)
const progressLeft = ((props.lowerValue ?? 0) - props.min) / (props.max - props.min) * inputWidth.value
return {
'--progress-right': `${progressRight + 24}px`,
...(double.value ? { '--progress-left': `${progressLeft + 12}px` } : {}),
}
})
watch([() => props.modelValue, () => props.lowerValue], ([upper, lower]) => {
if (lower === undefined) {
return
}
if (double.value && upper < lower) {
emit('update:lowerValue', upper)
}
if (double.value && lower > upper) {
emit('update:modelValue', lower)
}
})
const outputValue = computed(() => {
return (props.prefix ?? '')
.concat(double.value ? `${props.lowerValue} - ` : '')
.concat(`${props.modelValue}`)
.concat(props.suffix ?? '')
})
onMounted(() => {
inputWidth.value = input.value?.offsetWidth
})
</script>
<template>
<div
:id="`${id}-group`"
class="fr-range-group"
:class="{ 'fr-range-group--error': message }"
>
<label
:id="`${id}-label`"
class="fr-label"
>
<slot name="label">
{{ label }}
</slot>
<span class="fr-hint-text">
<slot name="hint">
{{ hint }}
</slot>
</span>
</label>
<div
class="fr-range"
data-fr-js-range="true"
:class="{
'fr-range--sm': small,
'fr-range--double': double,
'fr-range-group--disabled': disabled,
}"
:data-fr-prefix="prefix ?? undefined"
:data-fr-suffix="suffix ?? undefined"
:style="rangeStyle"
>
<span
ref="output"
class="fr-range__output"
data-fr-js-range-output="true"
:style="outputStyle"
>{{ outputValue }}</span>
<input
v-if="double"
:id="`${id}-2`"
type="range"
:min="min"
:max="max"
:step="step"
:value="lowerValue"
:disabled="disabled"
:aria-disabled="disabled"
:aria-labelledby="`${id}-label`"
:aria-describedby="`${id}-messages`"
@input="emit('update:lowerValue', +($event.target as HTMLInputElement)?.value)"
>
<input
:id="id"
ref="input"
type="range"
:min="min"
:max="max"
:step="step"
:value="modelValue"
:disabled="disabled"
:aria-disabled="disabled"
:aria-labelledby="`${id}-label`"
:aria-describedby="`${id}-messages`"
@input="emit('update:modelValue', +($event.target as HTMLInputElement)?.value)"
>
<span
v-if="!hideIndicators"
class="fr-range__min"
aria-hidden="true"
data-fr-js-range-limit="true"
>{{ min }}</span>
<span
v-if="!hideIndicators"
class="fr-range__max"
aria-hidden="true"
data-fr-js-range-limit="true"
>{{ max }}</span>
</div>
<div
v-if="message || $slots.messages"
:id="`${id}-messages`"
class="fr-messages-group"
aria-live="polite"
role="alert"
>
<slot name="messages">
<p
v-if="message"
:id="`${id}-message-error`"
class="fr-message fr-message--error"
>
{{ message }}
</p>
</slot>
</div>
</div>
</template>
export type DsfrRangeProps = {
id?: string
min?: number
max?: number
modelValue?: number
lowerValue?: number
label: string
hint?: string
message?: string
prefix?: string
suffix?: string
small?: boolean
hideIndicators?: boolean
step?: number
disabled?: boolean
}
Et voilà ! Notre DsfrRange est prêt à être croqué dans vos interfaces comme une baguette bien croustillante. N'oubliez pas de l'assaisonner avec vos styles et logiques pour qu'il s'intègre parfaitement dans le festin visuel de votre application. Bon codage ! 🥖💻