建立自訂 UI
本指南將引導您了解在無頭 (headless) 掛鉤之上建立您自己的日期選擇器 UI 的模式。
模式
每個無頭 (headless) 掛鉤都遵循相同的模式:
- 呼叫掛鉤並帶入您的選項 (值、onChange、設定)
- 解構傳回值以獲取狀態、計算值和處理程式
- 使用傳回的值渲染您自己的標記
- 透過呼叫提供的處理程式連接互動
掛鉤管理所有複雜性——日曆生成、日期計算、開/關狀態、鍵盤導覽、範圍邏輯——而您只需專注於呈現。
範例:自訂日期選擇器
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> );}核心概念
getDayProps
getDayProps 函式是日曆渲染的核心。對於每個日期單元格,它會傳回一個 DayProps 物件,其中包含 18 個布林值/數字標記,用於描述該單元格的狀態:
const dp = getDayProps(day);
// 選擇狀態dp.isSelected; // 目前選擇的日期dp.isToday; // 今天的日期
// 範圍狀態 (適用於範圍選擇器)dp.isInRange; // 介於開始和結束日期之間dp.isRangeStart; // 範圍的第一天dp.isRangeEnd; // 範圍的最後一天dp.isInHoverRange; // 在懸停預覽範圍內dp.isHoverTarget; // 懸停的日期本身dp.isRangeSingle; // 同時是範圍的開始和結束 (單日範圍)
// 視覺連接輔助工具dp.hasLeftConnection; // 連接到前一天 (用於範圍背景)dp.hasRightConnection; // 連接到後一天dp.isConsecutiveRange; // 多日範圍的一部分
// 狀態dp.isDisabled; // 被 minDate/maxDate/isDateUnavailable 禁用dp.isFocused; // 具有鍵盤焦點dp.isOutsideDay; // 屬於相鄰月份 (當 showOutsideDays 啟用時)dp.isHighlighted; // 在 highlightDates 陣列中
// 資料dp.date; // Date 物件dp.day; // 月份中的日期數字dp.dayOfWeek; // 0-6 (星期日-星期六)用於外部點擊的 Refs
這些掛鉤會傳回 containerRef 和 popupRef。將它們附加到您的包裝器和彈出元素上——掛鉤會使用它們來偵測外部點擊並自動關閉彈出視窗。
鍵盤導覽
handleKeyDown 處理程式支援方向鍵、Enter、Escape 和 Tab 鍵以實現完整的鍵盤導覽。將它附加到彈出視窗的容器上。
語系
所有面向使用者的字串都來自 locale 物件。您可以通過傳入一個部分的 locale 選項來自訂任何字串:
const picker = useDatePicker({ value, onChange: setValue, locale: { confirm: "確定", cancel: "返回", placeholder: "選擇一個日期...", },});後續步驟
- 掛鉤參考 — 每個掛鉤的完整選項和傳回值
- 情境 (Contexts) — 使用提供者 (provider) 實現複合元件模式
- 日期工具 — 用於日期操作的輔助函式