Context

headless パッケージは、フックをラップし、その状態を React Context 経由で公開する Context プロバイダーを提供します。これにより、コンパウンドコンポーネントパターンが可能になります。UI を小さく、焦点を絞ったサブコンポーネントに分割し、props を下に渡す代わりに context から状態を読み取ります。

なぜ Context を使うのか?

Context がなければ、すべての状態を props を通して渡すことになります:

// Contextなし -- prop drilling
function MyDatePicker() {
const picker = useDatePicker({ value, onChange });
return (
<MyTrigger displayValue={picker.displayValue} onToggle={picker.handleToggle} />
<MyCalendar calendar={picker.calendar} getDayProps={picker.getDayProps} ... />
<MyFooter onConfirm={picker.handleConfirm} onCancel={picker.handleCancel} ... />
);
}

Context を使えば、サブコンポーネントは必要なものを直接消費します:

// Contextあり -- コンパウンドコンポーネント
function MyDatePicker() {
return (
<DatePickerProvider value={value} onChange={onChange}>
<ExampleWrapper variant="headless" client:visible>
<MyTrigger client:visible />
</ExampleWrapper>
<ExampleWrapper variant="headless" client:visible>
<MyCalendar client:visible />
</ExampleWrapper>
<ExampleWrapper variant="headless" client:visible>
<MyFooter client:visible />
</ExampleWrapper>
</DatePickerProvider>
);
}
function MyTrigger() {
const { displayValue, handleToggle } = useDatePickerContext();
return <button onClick={handleToggle}>{displayValue}</button>;
}

利用可能なプロバイダー

プロバイダー内部で使用されるフックコンシューマーフック
DatePickerProvideruseDatePickeruseDatePickerContext()
DateRangePickerProvideruseDateRangePickeruseDateRangePickerContext()
DateTimePickerProvideruseDateTimePickeruseDateTimePickerContext()
DateRangeTimePickerProvideruseDateRangeTimePickeruseDateRangeTimePickerContext()

各プロバイダーは、型付けされた context(そのピッカータイプに固有)と統一された PickerContext(すべてのタイプで共有)の両方をレンダリングします。

import {
DatePickerProvider,
DateRangePickerProvider,
DateTimePickerProvider,
DateRangeTimePickerProvider,
} from "react-date-range-picker-headless";

DatePickerProvider

useDatePicker をラップします。DatePickerContext + PickerContext を提供します。

import type { DatePickerProviderProps } from "react-date-range-picker-headless";
Prop説明
childrenReactNode必須。 子コンポーネント。
valueDate | null必須。 現在の日付。
onChange(date: Date | null) => void必須。 変更ハンドラー。
inlinebooleanインラインでレンダリング(ポップアップなし)。
sizeDatePickerSizeサイズのバリアント。
placeholderstringトリガーのプレースホルダー。
namestringフォームフィールド名。
…restBaseDatePickerOptionsすべての基本オプション(minDate, maxDate, locale など)。

コンシューマーフック: useDatePickerContext()UseDatePickerReturn を返します。

DateRangePickerProvider

useDateRangePicker をラップします。DateRangePickerContext + PickerContext を提供します。

import type { DateRangePickerProviderProps } from "react-date-range-picker-headless";
Prop説明
childrenReactNode必須。 子コンポーネント。
value{ start: Date | null; end: Date | null }必須。 現在の範囲。
onChange(value: { start: Date | null; end: Date | null }) => void必須。 変更ハンドラー。
inlinebooleanインラインでレンダリング。
sizeDatePickerSizeサイズのバリアント。
placeholderstringトリガーのプレースホルダー。
namestringフォームフィールド名。
maxDaysnumber範囲内の最大日数。
minDaysnumber範囲内の最小日数。
presetsDateRangePreset[]範囲のプリセット。
allowSingleDateInRangeboolean単一日での範囲を許可。
…restBaseDatePickerOptionsすべての基本オプション。

コンシューマーフック: useDateRangePickerContext()UseDateRangePickerReturn を返します。

DateTimePickerProvider

useDateTimePicker をラップします。DateTimePickerContext + PickerContext を提供します。

import type { DateTimePickerProviderProps } from "react-date-range-picker-headless";
Prop説明
childrenReactNode必須。 子コンポーネント。
valueDate | null必須。 現在の日時。
onChange(dateTime: Date | null) => void必須。 変更ハンドラー。
timeTimeConfig時刻の設定。
inlinebooleanインラインでレンダリング。
sizeDatePickerSizeサイズのバリアント。
placeholderstringトリガーのプレースホルダー。
namestringフォームフィールド名。
…restBaseDatePickerOptionsすべての基本オプション。

コンシューマーフック: useDateTimePickerContext()UseDateTimePickerReturn を返します。

DateRangeTimePickerProvider

useDateRangeTimePicker をラップします。DateRangeTimePickerContext + PickerContext を提供します。

import type { DateRangeTimePickerProviderProps } from "react-date-range-picker-headless";
Prop説明
childrenReactNode必須。 子コンポーネント。
value{ start: Date | null; end: Date | null }必須。 現在の範囲。
onChange(value: { start: Date | null; end: Date | null }) => void必須。 変更ハンドラー。
timeTimeConfig時刻の設定。
inlinebooleanインラインでレンダリング。
sizeDatePickerSizeサイズのバリアント。
placeholderstringトリガーのプレースホルダー。
namestringフォームフィールド名。
maxDaysnumber範囲内の最大日数。
minDaysnumber範囲内の最小日数。
presetsDateRangePreset[]範囲のプリセット。
allowSingleDateInRangeboolean単一日での範囲を許可。
…restBaseDatePickerOptionsすべての基本オプション。

コンシューマーフック: useDateRangeTimePickerContext()UseDateRangeTimePickerReturn を返します。

PickerContext (統一)

すべてのプロバイダーは、統一された PickerContext も注入します。これは、すべてのピッカータイプのすべてのフィールドを含むスーパーセットの context であり、ピッカー固有の値のためのオプショナルなフィールドがあります。

import { usePickerContext } from "react-date-range-picker-headless";
function MyGenericHeader() {
const { locale, handlePrevMonth, handleNextMonth, calendars } = usePickerContext();
return (
<div>
<button onClick={handlePrevMonth}>{locale.prevMonth}</button>
<span>{locale.formatMonthYear(calendars[0].month)}</span>
<button onClick={handleNextMonth}>{locale.nextMonth}</button>
</div>
);
}

統一された context を使用すると、すべてのピッカータイプで機能する共有サブコンポーネントを構築できます。スタイル付きパッケージ(styledtailwind3tailwind4)は、このパターンを内部で使用しています。— それらの HeaderGridFooter などのコンポーネントはすべて PickerContext を消費します。

PickerContext のフィールド

import { PickerContext, usePickerContext } from "react-date-range-picker-headless";
import type { PickerContextValue } from "react-date-range-picker-headless";
function usePickerContext(): PickerContextValue;

ピッカープロバイダーの外部で使用するとエラーをスローします。

コアフィールド(常に存在)

FieldType
isOpenboolean
localeLocale
focusedDateDate | null
displayValuestring
hasValueboolean
canConfirmboolean
calendarsCalendarMonth[]
getDayProps(date: Date, referenceMonth?: Date) => DayProps
handleToggle() => void
handleOpen() => void
handleClose() => void
handleConfirm() => void
handleCancel() => void
handleClear() => void
handleGoToToday() => void
handlePrevMonth() => void
handleNextMonth() => void
handleKeyDown(e: KeyboardEvent<HTMLElement>) => void
handleDateClick(date: Date) => void
containerRefRefObject<HTMLDivElement | null>
popupRefRefObject<HTMLDivElement | null>
yearsnumber[]
monthsnumber[]
handleYearSelect(year: number, calendarIndex?: number) => void
handleMonthSelect(month: number, calendarIndex?: number) => void

UI パススルーフィールド

FieldType
valueunknown
endNamestring | undefined
requiredboolean | undefined
inlineboolean | undefined
showOutsideDaysboolean | undefined
sizeDatePickerSize | undefined
placeholderstring | undefined
namestring | undefined
captionLayoutCaptionLayout | undefined

範囲指定フィールド(オプショナル)

FieldType
handleDateHover((date: Date | null) => void) | undefined
presetsDateRangePreset[] | undefined
handlePresetClick((preset: DateRangePreset) => void) | undefined
activePresetIndexnumber | undefined

単一時刻フィールド(オプショナル, DateTimePicker)

FieldType
resolvedTimeConfigRequired<TimeConfig> | undefined
tempHournumber | undefined
tempMinutenumber | undefined
tempSecondnumber | undefined
tempPeriodTimePeriod | undefined
handleHourChange((hour: number) => void) | undefined
handleMinuteChange((minute: number) => void) | undefined
handleSecondChange((second: number) => void) | undefined
handlePeriodChange((period: TimePeriod) => void) | undefined
isTimePickerOpenboolean | undefined
handleTimePickerOpen(() => void) | undefined
handleTimePickerClose(() => void) | undefined
handleTimePickerConfirm(() => void) | undefined
handleTimePickerCancel(() => void) | undefined
timePickerRefRefObject<HTMLDivElement | null> | undefined
timeDisplayValuestring | undefined

範囲時刻フィールド(オプショナル, DateRangeTimePicker)

FieldType
startHournumber | undefined
startMinutenumber | undefined
startSecondnumber | undefined
startPeriodTimePeriod | undefined
endHournumber | undefined
endMinutenumber | undefined
endSecondnumber | undefined
endPeriodTimePeriod | undefined
handleStartHourChange((hour: number) => void) | undefined
handleStartMinuteChange((minute: number) => void) | undefined
handleStartSecondChange((second: number) => void) | undefined
handleStartPeriodChange((period: TimePeriod) => void) | undefined
handleEndHourChange((hour: number) => void) | undefined
handleEndMinuteChange((minute: number) => void) | undefined
handleEndSecondChange((second: number) => void) | undefined
handleEndPeriodChange((period: TimePeriod) => void) | undefined
isStartTimePickerOpenboolean | undefined
isEndTimePickerOpenboolean | undefined
handleStartTimePickerOpen(() => void) | undefined
handleStartTimePickerClose(() => void) | undefined
handleStartTimePickerConfirm(() => void) | undefined
handleStartTimePickerCancel(() => void) | undefined
handleEndTimePickerOpen(() => void) | undefined
handleEndTimePickerClose(() => void) | undefined
handleEndTimePickerConfirm(() => void) | undefined
handleEndTimePickerCancel(() => void) | undefined
startTimePickerRefRefObject<HTMLDivElement | null> | undefined
endTimePickerRefRefObject<HTMLDivElement | null> | undefined
startTimeDisplayValuestring | undefined
endTimeDisplayValuestring | undefined

型付き vs 統一 Context

ユースケースどの Context
すべてのピッカータイプで動作する共有コンポーネントを構築するusePickerContext()
完全な型安全性を備えた、1つのピッカーに特化したコンポーネントを構築するuseDatePickerContext(), useDateRangePickerContext(), etc.

型付けされた context は正確なフックの戻り値の型を返すため、完全な TypeScript の型推論が得られます。統一された context は、ピッカー固有の値に対してオプショナルなフィールドを使用します。

デュアル Context パターン

すべてのプロバイダーは、2つのネストされた context をレンダリングします:

<SpecificPickerContext.Provider value={hookReturn}>
<PickerContext.Provider value={unifiedValue}>{children}</PickerContext.Provider>
</SpecificPickerContext.Provider>
  • 共有サブコンポーネントには usePickerContext() を使用します(どのピッカーでも動作します)
  • ピッカー固有のロジックには、型付けされたコンシューマーフックを使用します(完全な型安全性)

例:コンパウンドデートピッカー

import { useState } from "react";
import {
DatePickerProvider,
usePickerContext,
type DayProps,
} from "react-date-range-picker-headless";
/** A child component that reads from PickerContext instead of receiving props. */
function CalendarDisplay() {
const ctx = usePickerContext();
const calendar = ctx.calendars[0];
return (
<div style={{ border: "1px solid #ccc", padding: 16, borderRadius: 8, background: "#fff" }}>
{/* Header */}
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}>
<button onClick={ctx.handlePrevMonth}>←</button>
<span>{ctx.locale.formatMonthYear(calendar.month)}</span>
<button onClick={ctx.handleNextMonth}>→</button>
</div>
{/* Weekdays */}
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", textAlign: "center" }}>
{ctx.locale.weekdays.map((wd) => (
<div key={wd} style={{ fontSize: 12, color: "#888", padding: 4 }}>
{wd}
</div>
))}
</div>
{/* Days */}
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", textAlign: "center" }}>
{calendar.days.map((date, i) => {
if (!date) return <div key={i} />;
const dp: DayProps = ctx.getDayProps(date);
return (
<button
key={i}
onClick={() => ctx.handleDateClick(date)}
disabled={dp.isDisabled}
style={{
padding: 8,
cursor: dp.isDisabled ? "not-allowed" : "pointer",
background: dp.isSelected ? "#3b82f6" : "transparent",
color: dp.isSelected
? "#fff"
: dp.isToday
? "#3b82f6"
: dp.isDisabled
? "#ccc"
: "#333",
fontWeight: dp.isToday ? "bold" : "normal",
border: "none",
borderRadius: 4,
}}
>
{dp.day}
</button>
);
})}
</div>
{/* Footer */}
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 8 }}>
<button onClick={ctx.handleCancel}>Cancel</button>
<button onClick={ctx.handleConfirm} disabled={!ctx.canConfirm}>
Confirm
</button>
</div>
</div>
);
}
/** Main example wrapping with DatePickerProvider so children can use usePickerContext. */
function ContextExample() {
const [value, setValue] = useState<Date | null>(null);
return (
<div style={{ fontFamily: "system-ui" }}>
<DatePickerProvider value={value} onChange={setValue} initialOpen>
<div style={{ marginBottom: 8, fontSize: 14, color: "#555" }}>
Context-driven calendar (always open via initialOpen):
</div>
<CalendarDisplay />
</DatePickerProvider>
</div>
);
}
Context-driven calendar (always open via initialOpen):
March 2026
Su
Mo
Tu
We
Th
Fr
Sa