Création d’une interface utilisateur personnalisée
Ce guide présente le modèle pour construire votre propre interface utilisateur de sélecteur de dates par-dessus les hooks headless.
Le Modèle
Chaque hook headless suit le même modèle :
- Appelez le hook avec vos options (value, onChange, configuration)
- Déstructurez la valeur de retour pour obtenir l’état, les valeurs calculées et les gestionnaires
- Rendez votre propre balisage en utilisant les valeurs retournées
- Connectez les interactions en appelant les gestionnaires fournis
Le hook gère toute la complexité — génération du calendrier, calcul des dates, état ouvert/fermé, navigation au clavier, logique de plage — et vous vous concentrez uniquement sur la présentation.
Exemple : Sélecteur de dates personnalisé
import { useState } from "react";import { useDatePicker } from "react-date-range-picker-headless";
function CustomDatePicker() { const [value, setValue] = useState<Date | null>(null);
const { isOpen, calendar, getDayProps, displayValue, hasValue, canConfirm, locale, handleToggle, handleDateClick, handleConfirm, handleCancel, handleClear, handlePrevMonth, handleNextMonth, handleKeyDown, containerRef, popupRef, } = useDatePicker({ value, onChange: setValue });
return ( <div ref={containerRef} style={{ position: "relative", display: "inline-block" }}> {/* Trigger */} <button onClick={handleToggle} style={{ padding: "8px 16px", border: "1px solid #d1d5db", borderRadius: 6, background: "white", cursor: "pointer", minWidth: 180, textAlign: "left", }} > {displayValue || locale.placeholder} </button>
{/* Popup */} {isOpen && ( <div ref={popupRef} onKeyDown={handleKeyDown} style={{ position: "absolute", top: "100%", left: 0, marginTop: 4, padding: 16, background: "white", border: "1px solid #e5e7eb", borderRadius: 8, boxShadow: "0 4px 12px rgba(0,0,0,0.1)", zIndex: 50, }} > {/* Header */} <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12, }} > <button onClick={handlePrevMonth}>{locale.prevMonth}</button> <span style={{ fontWeight: 600 }}>{locale.formatMonthYear(calendar.month)}</span> <button onClick={handleNextMonth}>{locale.nextMonth}</button> </div>
{/* Weekday headers */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 36px)", gap: 2, textAlign: "center", }} > {locale.weekdays.map((wd) => ( <span key={wd} style={{ fontSize: 12, color: "#9ca3af", padding: 4 }}> {wd} </span> ))} </div>
{/* Calendar grid */} <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 36px)", gap: 2 }}> {calendar.weeks.flat().map((day, i) => { if (!day) return <span key={i} />; const dp = getDayProps(day); return ( <button key={i} onClick={() => handleDateClick(day)} disabled={dp.isDisabled} style={{ width: 36, height: 36, borderRadius: "50%", border: dp.isFocused ? "2px solid #0ea5e9" : "none", background: dp.isSelected ? "#0ea5e9" : dp.isToday ? "#f0f9ff" : "transparent", color: dp.isSelected ? "white" : dp.isDisabled ? "#d1d5db" : "inherit", cursor: dp.isDisabled ? "not-allowed" : "pointer", fontWeight: dp.isToday ? 600 : 400, }} > {dp.day} </button> ); })} </div>
{/* Footer */} <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 12 }}> {hasValue && ( <button onClick={handleClear} style={{ color: "#ef4444" }}> {locale.clear} </button> )} <button onClick={handleCancel}>{locale.cancel}</button> <button onClick={handleConfirm} disabled={!canConfirm} style={{ background: canConfirm ? "#0ea5e9" : "#e5e7eb", color: canConfirm ? "white" : "#9ca3af", padding: "4px 16px", borderRadius: 4, border: "none", }} > {locale.confirm} </button> </div> </div> )} </div> );}Concepts Clés
getDayProps
La fonction getDayProps est au cœur du rendu du calendrier. Pour chaque cellule de date, elle retourne un objet DayProps avec 18 indicateurs booléens/numériques qui décrivent l’état de la cellule :
const dp = getDayProps(day);
// Selection statedp.isSelected; // Currently selected datedp.isToday; // Today's date
// Range state (for range pickers)dp.isInRange; // Between start and end datesdp.isRangeStart; // First date of the rangedp.isRangeEnd; // Last date of the rangedp.isInHoverRange; // In the hover preview rangedp.isHoverTarget; // The hovered date itselfdp.isRangeSingle; // Both range start and range end (single-day range)
// Visual connection helpersdp.hasLeftConnection; // Connected to previous day (for range backgrounds)dp.hasRightConnection; // Connected to next daydp.isConsecutiveRange; // Part of a multi-day range
// Statedp.isDisabled; // Disabled by minDate/maxDate/isDateUnavailabledp.isFocused; // Has keyboard focusdp.isOutsideDay; // Belongs to adjacent month (when showOutsideDays is enabled)dp.isHighlighted; // In the highlightDates array
// Datadp.date; // The Date objectdp.day; // Day of month numberdp.dayOfWeek; // 0-6 (Sunday-Saturday)Refs pour le clic extérieur
Les hooks retournent containerRef et popupRef. Attachez-les à vos éléments d’enveloppe et de popup — le hook les utilise pour détecter les clics extérieurs et fermer automatiquement la popup.
Navigation au clavier
Le gestionnaire handleKeyDown prend en charge les touches fléchées, Entrée, Échap et Tab pour une navigation complète au clavier. Attachez-le au conteneur de la popup.
Locale
Toutes les chaînes de caractères visibles par l’utilisateur proviennent de l’objet locale. Vous pouvez personnaliser n’importe quelle chaîne en passant une option locale partielle :
const picker = useDatePicker({ value, onChange: setValue, locale: { confirm: "OK", cancel: "Back", placeholder: "Pick a date...", },});Prochaines Étapes
- Hook Reference — Options complètes et valeurs de retour pour chaque hook
- Contexts — Utiliser les fournisseurs pour les modèles de composants composés
- Date Utilities — Fonctions utilitaires pour la manipulation des dates