カスタムUIの構築
このガイドでは、ヘッドレスフックの上に独自のデートピッカーUIを構築するためのパターンを解説します。
パターン
すべてのヘッドレスフックは同じパターンに従います:
- オプション(
value、onChange、configuration)を指定して フックを呼び出します - 状態、計算値、ハンドラを取得するために 返り値を分割代入します
- 返された値を使用して 独自のマークアップをレンダリングします
- 提供されたハンドラを呼び出して インタラクションを接続します
フックは、カレンダー生成、日付計算、開閉状態、キーボードナビゲーション、範囲ロジックなど、すべての複雑さを管理し、あなたは純粋に表示に集中できます。
例:カスタムデートピッカー
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 関数はカレンダーレンダリングの中核です。各日付セルに対して、そのセルの状態を記述する18個の真偽値/数値フラグを持つ DayProps オブジェクトを返します:
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 オブジェクトから提供されます。部分的な locale オプションを渡すことで、任意の文字列をカスタマイズできます:
const picker = useDatePicker({ value, onChange: setValue, locale: { confirm: "OK", cancel: "戻る", placeholder: "日付を選択...", },});次のステップ
- Hook Reference — 各フックの完全なオプションと返り値
- Contexts — 複合コンポーネントパターンのためにプロバイダーを使用する
- Date Utilities — 日付操作のためのヘルパー関数