Erstellen einer benutzerdefinierten Benutzeroberfläche

Diese Anleitung führt Sie durch das Muster zum Erstellen Ihrer eigenen Datumsauswahl-Benutzeroberfläche auf Basis der Headless-Hooks.

Das Muster

Jeder Headless-Hook folgt demselben Muster:

  1. Rufen Sie den Hook auf mit Ihren Optionen (value, onChange, Konfiguration)
  2. Destrukturieren Sie den Rückgabewert, um Zustand, berechnete Werte und Handler zu erhalten
  3. Rendern Sie Ihr eigenes Markup unter Verwendung der zurückgegebenen Werte
  4. Verbinden Sie Interaktionen, indem Sie die bereitgestellten Handler aufrufen

Der Hook verwaltet die gesamte Komplexität – Kalendererstellung, Datumsberechnungen, Öffnen/Schließen-Zustand, Tastaturnavigation, Bereichslogik – und Sie konzentrieren sich ausschließlich auf die Präsentation.

Beispiel: Benutzerdefinierter Date Picker

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>
);
}

Wichtige Konzepte

getDayProps

Die getDayProps-Funktion ist der Kern des Kalender-Renderings. Für jede Datumszelle gibt sie ein DayProps-Objekt mit 18 booleschen/numerischen Flags zurück, die den Zustand der Zelle beschreiben:

const dp = getDayProps(day);
// Auswahlzustand
dp.isSelected; // Aktuell ausgewähltes Datum
dp.isToday; // Heutiges Datum
// Bereichszustand (für Bereichsauswahlen)
dp.isInRange; // Zwischen Start- und Enddatum
dp.isRangeStart; // Erstes Datum des Bereichs
dp.isRangeEnd; // Letztes Datum des Bereichs
dp.isInHoverRange; // Im Hover-Vorschaubereich
dp.isHoverTarget; // Das überfahrene Datum selbst
dp.isRangeSingle; // Sowohl Bereichsstart als auch -ende (eintägiger Bereich)
// Helfer für visuelle Verbindungen
dp.hasLeftConnection; // Verbunden mit dem Vortag (für Bereichshintergründe)
dp.hasRightConnection; // Verbunden mit dem nächsten Tag
dp.isConsecutiveRange; // Teil eines mehrtägigen Bereichs
// Zustand
dp.isDisabled; // Deaktiviert durch minDate/maxDate/isDateUnavailable
dp.isFocused; // Hat Tastaturfokus
dp.isOutsideDay; // Gehört zum benachbarten Monat (wenn showOutsideDays aktiviert ist)
dp.isHighlighted; // Im highlightDates-Array
// Daten
dp.date; // Das Date-Objekt
dp.day; // Tag des Monats (Nummer)
dp.dayOfWeek; // 0-6 (Sonntag-Samstag)

Refs für Klicks außerhalb

Die Hooks geben containerRef und popupRef zurück. Hängen Sie diese an Ihre Wrapper- und Popup-Elemente an – der Hook verwendet sie, um Klicks außerhalb zu erkennen und das Popup automatisch zu schließen.

Tastaturnavigation

Der handleKeyDown-Handler unterstützt Pfeiltasten, Enter, Escape und Tab für eine vollständige Tastaturnavigation. Hängen Sie ihn an den Popup-Container an.

Locale

Alle benutzerseitigen Zeichenfolgen stammen aus dem locale-Objekt. Sie können jede Zeichenfolge anpassen, indem Sie eine partielle locale-Option übergeben:

const picker = useDatePicker({
value,
onChange: setValue,
locale: {
confirm: "OK",
cancel: "Zurück",
placeholder: "Datum auswählen...",
},
});

Nächste Schritte

  • Hook Reference — Vollständige Optionen und Rückgabewerte für jeden Hook
  • Contexts — Verwenden Sie Provider für zusammengesetzte Komponentenmuster
  • Date Utilities — Hilfsfunktionen zur Datumsmanipulation