Contexts
Gói headless cung cấp các Context provider bao bọc các hook và hiển thị trạng thái của chúng thông qua React Context. Điều này cho phép sử dụng mẫu thành phần phức hợp (compound component pattern) — chia nhỏ giao diện người dùng của bạn thành các thành phần con nhỏ, tập trung đọc trạng thái từ context thay vì truyền props xuống.
Tại sao dùng Contexts?
Nếu không có context, bạn sẽ phải luồn mọi phần của trạng thái qua props:
// Không có context -- prop drillingfunction 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} ... /> );}Với context, các thành phần con sử dụng những gì chúng cần một cách trực tiếp:
// Với context -- các thành phần phức hợpfunction 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>;}Các Provider có sẵn
| Provider | Hook được dùng nội bộ | Hook người dùng |
|---|---|---|
DatePickerProvider | useDatePicker | useDatePickerContext() |
DateRangePickerProvider | useDateRangePicker | useDateRangePickerContext() |
DateTimePickerProvider | useDateTimePicker | useDateTimePickerContext() |
DateRangeTimePickerProvider | useDateRangeTimePicker | useDateRangeTimePickerContext() |
Mỗi provider hiển thị cả một context đã được định kiểu (dành riêng cho loại picker đó) và PickerContext thống nhất (được chia sẻ trên tất cả các loại).
import { DatePickerProvider, DateRangePickerProvider, DateTimePickerProvider, DateRangeTimePickerProvider,} from "react-date-range-picker-headless";DatePickerProvider
Bao bọc useDatePicker. Cung cấp DatePickerContext + PickerContext.
import type { DatePickerProviderProps } from "react-date-range-picker-headless";| Prop | Kiểu | Mô tả |
|---|---|---|
children | ReactNode | Bắt buộc. Các thành phần con. |
value | Date | null | Bắt buộc. Ngày hiện tại. |
onChange | (date: Date | null) => void | Bắt buộc. Hàm xử lý thay đổi. |
inline | boolean | Hiển thị nội tuyến (không có popup). |
size | DatePickerSize | Biến thể kích thước. |
placeholder | string | Placeholder cho trigger. |
name | string | Tên trường của form. |
| …rest | BaseDatePickerOptions | Tất cả các tùy chọn cơ bản (minDate, maxDate, locale, v.v.). |
Hook người dùng: useDatePickerContext() trả về UseDatePickerReturn.
DateRangePickerProvider
Bao bọc useDateRangePicker. Cung cấp DateRangePickerContext + PickerContext.
import type { DateRangePickerProviderProps } from "react-date-range-picker-headless";| Prop | Kiểu | Mô tả |
|---|---|---|
children | ReactNode | Bắt buộc. Các thành phần con. |
value | { start: Date | null; end: Date | null } | Bắt buộc. Khoảng ngày hiện tại. |
onChange | (value: { start: Date | null; end: Date | null }) => void | Bắt buộc. Hàm xử lý thay đổi. |
inline | boolean | Hiển thị nội tuyến. |
size | DatePickerSize | Biến thể kích thước. |
placeholder | string | Placeholder cho trigger. |
name | string | Tên trường của form. |
maxDays | number | Số ngày tối đa trong khoảng. |
minDays | number | Số ngày tối thiểu trong khoảng. |
presets | DateRangePreset[] | Các thiết lập sẵn cho khoảng ngày. |
allowSingleDateInRange | boolean | Cho phép khoảng một ngày. |
| …rest | BaseDatePickerOptions | Tất cả các tùy chọn cơ bản. |
Hook người dùng: useDateRangePickerContext() trả về UseDateRangePickerReturn.
DateTimePickerProvider
Bao bọc useDateTimePicker. Cung cấp DateTimePickerContext + PickerContext.
import type { DateTimePickerProviderProps } from "react-date-range-picker-headless";| Prop | Kiểu | Mô tả |
|---|---|---|
children | ReactNode | Bắt buộc. Các thành phần con. |
value | Date | null | Bắt buộc. Ngày-giờ hiện tại. |
onChange | (dateTime: Date | null) => void | Bắt buộc. Hàm xử lý thay đổi. |
time | TimeConfig | Cấu hình thời gian. |
inline | boolean | Hiển thị nội tuyến. |
size | DatePickerSize | Biến thể kích thước. |
placeholder | string | Placeholder cho trigger. |
name | string | Tên trường của form. |
| …rest | BaseDatePickerOptions | Tất cả các tùy chọn cơ bản. |
Hook người dùng: useDateTimePickerContext() trả về UseDateTimePickerReturn.
DateRangeTimePickerProvider
Bao bọc useDateRangeTimePicker. Cung cấp DateRangeTimePickerContext + PickerContext.
import type { DateRangeTimePickerProviderProps } from "react-date-range-picker-headless";| Prop | Kiểu | Mô tả |
|---|---|---|
children | ReactNode | Bắt buộc. Các thành phần con. |
value | { start: Date | null; end: Date | null } | Bắt buộc. Khoảng ngày hiện tại. |
onChange | (value: { start: Date | null; end: Date | null }) => void | Bắt buộc. Hàm xử lý thay đổi. |
time | TimeConfig | Cấu hình thời gian. |
inline | boolean | Hiển thị nội tuyến. |
size | DatePickerSize | Biến thể kích thước. |
placeholder | string | Placeholder cho trigger. |
name | string | Tên trường của form. |
maxDays | number | Số ngày tối đa trong khoảng. |
minDays | number | Số ngày tối thiểu trong khoảng. |
presets | DateRangePreset[] | Các thiết lập sẵn cho khoảng ngày. |
allowSingleDateInRange | boolean | Cho phép khoảng một ngày. |
| …rest | BaseDatePickerOptions | Tất cả các tùy chọn cơ bản. |
Hook người dùng: useDateRangeTimePickerContext() trả về UseDateRangeTimePickerReturn.
PickerContext (Thống nhất)
Mỗi provider cũng đưa vào một PickerContext thống nhất. Đây là một context cha chứa tất cả các trường từ mọi loại picker, với các trường tùy chọn cho các giá trị dành riêng cho picker.
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 thống nhất cho phép bạn xây dựng các thành phần con dùng chung hoạt động trên tất cả các loại picker. Các gói đã được tạo kiểu (styled, tailwind3, tailwind4) sử dụng mẫu này trong nội bộ — các thành phần Header, Grid, Footer, v.v. của chúng đều sử dụng PickerContext.
Các trường của PickerContext
import { PickerContext, usePickerContext } from "react-date-range-picker-headless";import type { PickerContextValue } from "react-date-range-picker-headless";function usePickerContext(): PickerContextValue;Sẽ báo lỗi nếu được sử dụng bên ngoài một provider của picker.
Các trường cốt lõi (luôn có mặt)
| Trường | Kiểu |
|---|---|
isOpen | boolean |
locale | Locale |
focusedDate | Date | null |
displayValue | string |
hasValue | boolean |
canConfirm | boolean |
calendars | CalendarMonth[] |
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 |
containerRef | RefObject<HTMLDivElement | null> |
popupRef | RefObject<HTMLDivElement | null> |
years | number[] |
months | number[] |
handleYearSelect | (year: number, calendarIndex?: number) => void |
handleMonthSelect | (month: number, calendarIndex?: number) => void |
Các trường chuyển qua cho UI
| Trường | Kiểu |
|---|---|
value | unknown |
endName | string | undefined |
required | boolean | undefined |
inline | boolean | undefined |
showOutsideDays | boolean | undefined |
size | DatePickerSize | undefined |
placeholder | string | undefined |
name | string | undefined |
captionLayout | CaptionLayout | undefined |
Các trường dành riêng cho khoảng ngày (tùy chọn)
| Trường | Kiểu |
|---|---|
handleDateHover | ((date: Date | null) => void) | undefined |
presets | DateRangePreset[] | undefined |
handlePresetClick | ((preset: DateRangePreset) => void) | undefined |
activePresetIndex | number | undefined |
Các trường thời gian đơn (tùy chọn, DateTimePicker)
| Trường | Kiểu |
|---|---|
resolvedTimeConfig | Required<TimeConfig> | undefined |
tempHour | number | undefined |
tempMinute | number | undefined |
tempSecond | number | undefined |
tempPeriod | TimePeriod | undefined |
handleHourChange | ((hour: number) => void) | undefined |
handleMinuteChange | ((minute: number) => void) | undefined |
handleSecondChange | ((second: number) => void) | undefined |
handlePeriodChange | ((period: TimePeriod) => void) | undefined |
isTimePickerOpen | boolean | undefined |
handleTimePickerOpen | (() => void) | undefined |
handleTimePickerClose | (() => void) | undefined |
handleTimePickerConfirm | (() => void) | undefined |
handleTimePickerCancel | (() => void) | undefined |
timePickerRef | RefObject<HTMLDivElement | null> | undefined |
timeDisplayValue | string | undefined |
Các trường thời gian cho khoảng ngày (tùy chọn, DateRangeTimePicker)
| Trường | Kiểu |
|---|---|
startHour | number | undefined |
startMinute | number | undefined |
startSecond | number | undefined |
startPeriod | TimePeriod | undefined |
endHour | number | undefined |
endMinute | number | undefined |
endSecond | number | undefined |
endPeriod | TimePeriod | 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 |
isStartTimePickerOpen | boolean | undefined |
isEndTimePickerOpen | boolean | 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 |
startTimePickerRef | RefObject<HTMLDivElement | null> | undefined |
endTimePickerRef | RefObject<HTMLDivElement | null> | undefined |
startTimeDisplayValue | string | undefined |
endTimeDisplayValue | string | undefined |
Context đã được định kiểu vs Context thống nhất
| Trường hợp sử dụng | Context nào |
|---|---|
| Xây dựng một thành phần dùng chung hoạt động trên tất cả các loại picker | usePickerContext() |
| Xây dựng một thành phần dành riêng cho một picker với an toàn kiểu đầy đủ | useDatePickerContext(), useDateRangePickerContext(), v.v. |
Các context đã được định kiểu trả về kiểu trả về chính xác của hook, vì vậy bạn nhận được suy luận kiểu TypeScript đầy đủ. Context thống nhất sử dụng các trường tùy chọn cho các giá trị dành riêng cho picker.
Mẫu Context kép
Mỗi provider hiển thị hai context lồng nhau:
<SpecificPickerContext.Provider value={hookReturn}> <PickerContext.Provider value={unifiedValue}>{children}</PickerContext.Provider></SpecificPickerContext.Provider>- Sử dụng
usePickerContext()cho các thành phần con dùng chung (hoạt động với mọi picker) - Sử dụng hook người dùng đã được định kiểu cho logic dành riêng cho picker (an toàn kiểu đầy đủ)
Ví dụ: Date Picker phức hợp
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> );}