dsikeres1
componentdate-range-pickersource: ok · hit
Date Range Picker
Compound component date range picker suite.
npx shadcn add "https://dsikeres1.github.io/react-date-range-picker/r/date-range-picker.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightreact-date-range-picker-headless@floating-ui/reactclsxtailwind-merge
Files46 · source cached 10/15/2026, 8:08:47 AM
registry/components/DatePicker/Compound.tsx
import { forwardRef } from "react";
import {
DatePicker as Primitive,
type DatePickerRootProps,
type TriggerProps,
type ContentProps,
type HeaderProps,
type NavButtonProps,
type TitleProps,
type GridProps,
type DayProps,
type FooterProps,
type ActionButtonProps,
} from "../../ui-primitives/index";
import { cn } from "../../utils";
import {
rootClassNames,
triggerClassNames,
contentClassNames,
headerClassNames,
gridClassNames,
dayClassNames,
footerClassNames,
} from "../../theme";
export const Root = forwardRef<HTMLDivElement, DatePickerRootProps>(
({ className, size, ...props }, ref) => (
<Primitive.Root
ref={ref}
className={cn(rootClassNames.root, size && `rdrp-size-${size}`, className)}
cn={cn}
size={size}
{...props}
/>
),
);
Root.displayName = "DatePicker.Root";
export const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(
({ className, placeholderClassName, clearClassName, ...props }, ref) => (
<Primitive.Trigger
ref={ref}
className={cn(triggerClassNames.trigger, className)}
placeholderClassName={cn(triggerClassNames.placeholder, placeholderClassName)}
clearClassName={cn(triggerClassNames.clear, clearClassName)}
cn={cn}
{...props}
/>
),
);
Trigger.displayName = "DatePicker.Trigger";
export const Content = forwardRef<HTMLDivElement, ContentProps>(
({ className, inlineClassName, ...props }, ref) => (
<Primitive.Content
ref={ref}
className={cn(rootClassNames.root, contentClassNames.content, className)}
inlineClassName={cn(contentClassNames.contentInline, inlineClassName)}
cn={cn}
{...props}
/>
),
);
Content.displayName = "DatePicker.Content";
export const Header = forwardRef<HTMLDivElement, HeaderProps>(({ className, ...props }, ref) => (
<Primitive.Header
ref={ref}
className={cn(headerClassNames.header, className)}
cn={cn}
{...props}
/>
));
Header.displayName = "DatePicker.Header";
export const PrevButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.PrevButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
PrevButton.displayName = "DatePicker.PrevButton";
export const NextButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.NextButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
NextButton.displayName = "DatePicker.NextButton";
export const Title = forwardRef<HTMLDivElement, TitleProps>(
({ className, selectClassName, ...props }, ref) => (
<Primitive.Title
ref={ref}
className={cn(headerClassNames.title, className)}
selectClassName={cn(headerClassNames.select, selectClassName)}
cn={cn}
{...props}
/>
),
);
Title.displayName = "DatePicker.Title";
export const Grid = forwardRef<HTMLDivElement, GridProps>(
({ className, weekdayClassName, weekdaysClassName, weekClassName, ...props }, ref) => (
<Primitive.Grid
ref={ref}
className={cn(gridClassNames.panel, className)}
weekdayClassName={cn(gridClassNames.weekday, weekdayClassName)}
weekdaysClassName={cn(gridClassNames.weekdays, weekdaysClassName)}
weekClassName={cn(gridClassNames.week, weekClassName)}
gridClassName={gridClassNames.grid}
dayClassName={dayClassNames.day}
dayTodayClassName={dayClassNames.today}
daySelectedClassName={dayClassNames.selected}
dayDisabledClassName={dayClassNames.disabled}
dayOutsideClassName={dayClassNames.outside}
dayHighlightedClassName={dayClassNames.highlighted}
dayInRangeClassName={dayClassNames.inRange}
dayRangeStartClassName={dayClassNames.rangeStart}
dayRangeEndClassName={dayClassNames.rangeEnd}
dayRangeSingleClassName={dayClassNames.rangeSingle}
dayHoverRangeClassName={dayClassNames.hoverRange}
dayHoverTargetClassName={dayClassNames.hoverTarget}
dayFocusedClassName={dayClassNames.focused}
dayEmptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
),
);
Grid.displayName = "DatePicker.Grid";
export const Day = forwardRef<HTMLButtonElement, DayProps>(({ className, ...props }, ref) => (
<Primitive.Day
ref={ref}
className={cn(dayClassNames.day, className)}
todayClassName={dayClassNames.today}
selectedClassName={dayClassNames.selected}
disabledClassName={dayClassNames.disabled}
outsideClassName={dayClassNames.outside}
highlightedClassName={dayClassNames.highlighted}
inRangeClassName={dayClassNames.inRange}
rangeStartClassName={dayClassNames.rangeStart}
rangeEndClassName={dayClassNames.rangeEnd}
rangeSingleClassName={dayClassNames.rangeSingle}
hoverRangeClassName={dayClassNames.hoverRange}
hoverTargetClassName={dayClassNames.hoverTarget}
focusedClassName={dayClassNames.focused}
emptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
));
Day.displayName = "DatePicker.Day";
export const Footer = forwardRef<HTMLDivElement, FooterProps>(({ className, ...props }, ref) => (
<Primitive.Footer
ref={ref}
className={cn(footerClassNames.footer, className)}
cn={cn}
{...props}
/>
));
Footer.displayName = "DatePicker.Footer";
export const ClearButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ClearButton
ref={ref}
className={cn(footerClassNames.clearButton, className)}
cn={cn}
{...props}
/>
),
);
ClearButton.displayName = "DatePicker.ClearButton";
export const CancelButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.CancelButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
CancelButton.displayName = "DatePicker.CancelButton";
export const ConfirmButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ConfirmButton
ref={ref}
className={cn(footerClassNames.confirmButton, className)}
cn={cn}
{...props}
/>
),
);
ConfirmButton.displayName = "DatePicker.ConfirmButton";
export const TodayButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.TodayButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
TodayButton.displayName = "DatePicker.TodayButton";
registry/components/DatePicker/index.tsx
import * as C from "./Compound";
import type { DatePickerRootProps } from "../../ui-primitives/index";
import { rangeClassNames } from "../../theme";
export interface DatePickerProps extends Omit<DatePickerRootProps, "children"> {
hideHeader?: boolean;
hideFooter?: boolean;
}
export function DatePickerSimple({ hideHeader, hideFooter, ...props }: DatePickerProps) {
const showFooter =
hideFooter !== undefined ? !hideFooter : !props.inline && !props.shouldCloseOnSelect;
const showNav = props.captionLayout !== "dropdown";
const numMonths = props.numberOfMonths ?? 1;
return (
<C.Root {...props}>
{!props.inline && <C.Trigger />}
<C.Content>
{!hideHeader && (
<C.Header>
{showNav && <C.PrevButton />}
<C.Title />
{showNav && <C.NextButton />}
</C.Header>
)}
{numMonths > 1 ? (
<div className={rangeClassNames.calendarsContainer}>
{Array.from({ length: numMonths }, (_, i) => (
<C.Grid key={i} calendarIndex={i} />
))}
</div>
) : (
<C.Grid />
)}
{showFooter && (
<C.Footer>
<C.ClearButton />
<C.CancelButton />
<C.ConfirmButton />
</C.Footer>
)}
</C.Content>
</C.Root>
);
}
export const DatePicker = Object.assign(DatePickerSimple, C);
registry/components/DateRangePicker/Compound.tsx
import { forwardRef } from "react";
import {
DateRangePicker as Primitive,
type DateRangePickerRootProps,
type TriggerProps,
type ContentProps,
type HeaderProps,
type NavButtonProps,
type TitleProps,
type GridProps,
type DayProps,
type FooterProps,
type ActionButtonProps,
type CalendarsProps,
type PresetsProps,
type PresetItemProps,
} from "../../ui-primitives/index";
import { cn } from "../../utils";
import {
rootClassNames,
triggerClassNames,
contentClassNames,
headerClassNames,
gridClassNames,
dayClassNames,
footerClassNames,
rangeClassNames,
} from "../../theme";
export const Root = forwardRef<HTMLDivElement, DateRangePickerRootProps>(
({ className, size, ...props }, ref) => (
<Primitive.Root
ref={ref}
className={cn(rootClassNames.root, size && `rdrp-size-${size}`, className)}
cn={cn}
size={size}
{...props}
/>
),
);
Root.displayName = "DateRangePicker.Root";
export const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(
({ className, placeholderClassName, clearClassName, ...props }, ref) => (
<Primitive.Trigger
ref={ref}
className={cn(triggerClassNames.trigger, className)}
placeholderClassName={cn(triggerClassNames.placeholder, placeholderClassName)}
clearClassName={cn(triggerClassNames.clear, clearClassName)}
cn={cn}
{...props}
/>
),
);
Trigger.displayName = "DateRangePicker.Trigger";
export const Content = forwardRef<HTMLDivElement, ContentProps>(
({ className, inlineClassName, ...props }, ref) => (
<Primitive.Content
ref={ref}
className={cn(rootClassNames.root, contentClassNames.content, className)}
inlineClassName={cn(contentClassNames.contentInline, inlineClassName)}
cn={cn}
{...props}
/>
),
);
Content.displayName = "DateRangePicker.Content";
export const Header = forwardRef<HTMLDivElement, HeaderProps>(({ className, ...props }, ref) => (
<Primitive.Header
ref={ref}
className={cn(headerClassNames.header, className)}
cn={cn}
{...props}
/>
));
Header.displayName = "DateRangePicker.Header";
export const PrevButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.PrevButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
PrevButton.displayName = "DateRangePicker.PrevButton";
export const NextButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.NextButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
NextButton.displayName = "DateRangePicker.NextButton";
export const Title = forwardRef<HTMLDivElement, TitleProps>(
({ className, selectClassName, ...props }, ref) => (
<Primitive.Title
ref={ref}
className={cn(headerClassNames.title, className)}
selectClassName={cn(headerClassNames.select, selectClassName)}
cn={cn}
{...props}
/>
),
);
Title.displayName = "DateRangePicker.Title";
export const Grid = forwardRef<HTMLDivElement, GridProps>(
({ className, weekdayClassName, weekdaysClassName, weekClassName, ...props }, ref) => (
<Primitive.Grid
ref={ref}
className={cn(gridClassNames.panel, className)}
weekdayClassName={cn(gridClassNames.weekday, weekdayClassName)}
weekdaysClassName={cn(gridClassNames.weekdays, weekdaysClassName)}
weekClassName={cn(gridClassNames.week, weekClassName)}
gridClassName={gridClassNames.grid}
dayClassName={dayClassNames.day}
dayTodayClassName={dayClassNames.today}
daySelectedClassName={dayClassNames.selected}
dayDisabledClassName={dayClassNames.disabled}
dayOutsideClassName={dayClassNames.outside}
dayHighlightedClassName={dayClassNames.highlighted}
dayInRangeClassName={dayClassNames.inRange}
dayRangeStartClassName={dayClassNames.rangeStart}
dayRangeEndClassName={dayClassNames.rangeEnd}
dayRangeSingleClassName={dayClassNames.rangeSingle}
dayHoverRangeClassName={dayClassNames.hoverRange}
dayHoverTargetClassName={dayClassNames.hoverTarget}
dayFocusedClassName={dayClassNames.focused}
dayEmptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
),
);
Grid.displayName = "DateRangePicker.Grid";
export const Day = forwardRef<HTMLButtonElement, DayProps>(({ className, ...props }, ref) => (
<Primitive.Day
ref={ref}
className={cn(dayClassNames.day, className)}
todayClassName={dayClassNames.today}
selectedClassName={dayClassNames.selected}
disabledClassName={dayClassNames.disabled}
outsideClassName={dayClassNames.outside}
highlightedClassName={dayClassNames.highlighted}
inRangeClassName={dayClassNames.inRange}
rangeStartClassName={dayClassNames.rangeStart}
rangeEndClassName={dayClassNames.rangeEnd}
rangeSingleClassName={dayClassNames.rangeSingle}
hoverRangeClassName={dayClassNames.hoverRange}
hoverTargetClassName={dayClassNames.hoverTarget}
focusedClassName={dayClassNames.focused}
emptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
));
Day.displayName = "DateRangePicker.Day";
export const Calendars = forwardRef<HTMLDivElement, CalendarsProps>(
({ className, ...props }, ref) => (
<Primitive.Calendars
ref={ref}
className={cn(rangeClassNames.calendarsContainer, className)}
cn={cn}
{...props}
/>
),
);
Calendars.displayName = "DateRangePicker.Calendars";
export const Presets = forwardRef<HTMLDivElement, PresetsProps>(({ className, ...props }, ref) => (
<Primitive.Presets
ref={ref}
className={cn(rangeClassNames.presetsContainer, className)}
cn={cn}
{...props}
/>
));
Presets.displayName = "DateRangePicker.Presets";
export const PresetItem = forwardRef<HTMLButtonElement, PresetItemProps>(
({ className, activeClassName, ...props }, ref) => (
<Primitive.PresetItem
ref={ref}
className={cn(rangeClassNames.presetButton, className)}
activeClassName={cn(rangeClassNames.presetButtonActive, activeClassName)}
cn={cn}
{...props}
/>
),
);
PresetItem.displayName = "DateRangePicker.PresetItem";
export const Footer = forwardRef<HTMLDivElement, FooterProps>(({ className, ...props }, ref) => (
<Primitive.Footer
ref={ref}
className={cn(footerClassNames.footer, className)}
cn={cn}
{...props}
/>
));
Footer.displayName = "DateRangePicker.Footer";
export const ClearButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ClearButton
ref={ref}
className={cn(footerClassNames.clearButton, className)}
cn={cn}
{...props}
/>
),
);
ClearButton.displayName = "DateRangePicker.ClearButton";
export const CancelButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.CancelButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
CancelButton.displayName = "DateRangePicker.CancelButton";
export const ConfirmButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ConfirmButton
ref={ref}
className={cn(footerClassNames.confirmButton, className)}
cn={cn}
{...props}
/>
),
);
ConfirmButton.displayName = "DateRangePicker.ConfirmButton";
export const TodayButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.TodayButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
TodayButton.displayName = "DateRangePicker.TodayButton";
registry/components/DateRangePicker/index.tsx
import * as C from "./Compound";
import type { DateRangePickerRootProps } from "../../ui-primitives/index";
import { cn } from "../../utils";
import { headerClassNames, rangeClassNames } from "../../theme";
export interface DateRangePickerProps extends Omit<DateRangePickerRootProps, "children"> {
hideHeader?: boolean;
hideFooter?: boolean;
}
export function DateRangePickerSimple({ hideHeader, hideFooter, ...props }: DateRangePickerProps) {
const hasPresets = props.presets && props.presets.length > 0;
const showFooter =
hideFooter !== undefined ? !hideFooter : !props.inline && !props.shouldCloseOnSelect;
const showNav = props.captionLayout !== "dropdown";
return (
<C.Root {...props}>
{!props.inline && <C.Trigger />}
<C.Content>
<div className={cn(hasPresets && rangeClassNames.presetsLayout)}>
{hasPresets && (
<C.Presets>
{props.presets?.map((p, i) => (
<C.PresetItem key={p.label} index={i} />
))}
</C.Presets>
)}
<div>
{!hideHeader && (
<C.Header>
{showNav && <C.PrevButton />}
<C.Title calendarIndex={0} />
<div className={headerClassNames.spacer} />
<C.Title calendarIndex={1} />
{showNav && <C.NextButton />}
</C.Header>
)}
<C.Calendars>
<C.Grid calendarIndex={0} />
<C.Grid calendarIndex={1} />
</C.Calendars>
{showFooter && (
<C.Footer>
<C.ClearButton />
<C.CancelButton />
<C.ConfirmButton />
</C.Footer>
)}
</div>
</div>
</C.Content>
</C.Root>
);
}
export const DateRangePicker = Object.assign(DateRangePickerSimple, C);
registry/components/DateRangeTimePicker/Compound.tsx
import { forwardRef } from "react";
import {
DateRangeTimePicker as Primitive,
type DateRangeTimePickerRootProps,
type TriggerProps,
type ContentProps,
type HeaderProps,
type NavButtonProps,
type TitleProps,
type GridProps,
type DayProps,
type FooterProps,
type ActionButtonProps,
type CalendarsProps,
type PresetsProps,
type PresetItemProps,
type StartTimeSectionProps,
type EndTimeSectionProps,
type TimePanelProps,
} from "../../ui-primitives/index";
import { cn } from "../../utils";
import {
rootClassNames,
triggerClassNames,
contentClassNames,
headerClassNames,
gridClassNames,
dayClassNames,
footerClassNames,
rangeClassNames,
dateTimeClassNames,
timePanelClassNames,
} from "../../theme";
export const Root = forwardRef<HTMLDivElement, DateRangeTimePickerRootProps>(
({ className, size, ...props }, ref) => (
<Primitive.Root
ref={ref}
className={cn(rootClassNames.root, size && `rdrp-size-${size}`, className)}
cn={cn}
size={size}
{...props}
/>
),
);
Root.displayName = "DateRangeTimePicker.Root";
export const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(
({ className, placeholderClassName, clearClassName, ...props }, ref) => (
<Primitive.Trigger
ref={ref}
className={cn(triggerClassNames.trigger, className)}
placeholderClassName={cn(triggerClassNames.placeholder, placeholderClassName)}
clearClassName={cn(triggerClassNames.clear, clearClassName)}
cn={cn}
{...props}
/>
),
);
Trigger.displayName = "DateRangeTimePicker.Trigger";
export const Content = forwardRef<HTMLDivElement, ContentProps>(
({ className, inlineClassName, ...props }, ref) => (
<Primitive.Content
ref={ref}
className={cn(rootClassNames.root, contentClassNames.content, className)}
inlineClassName={cn(contentClassNames.contentInline, inlineClassName)}
cn={cn}
{...props}
/>
),
);
Content.displayName = "DateRangeTimePicker.Content";
export const Header = forwardRef<HTMLDivElement, HeaderProps>(({ className, ...props }, ref) => (
<Primitive.Header
ref={ref}
className={cn(headerClassNames.header, className)}
cn={cn}
{...props}
/>
));
Header.displayName = "DateRangeTimePicker.Header";
export const PrevButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.PrevButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
PrevButton.displayName = "DateRangeTimePicker.PrevButton";
export const NextButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.NextButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
NextButton.displayName = "DateRangeTimePicker.NextButton";
export const Title = forwardRef<HTMLDivElement, TitleProps>(
({ className, selectClassName, ...props }, ref) => (
<Primitive.Title
ref={ref}
className={cn(headerClassNames.title, className)}
selectClassName={cn(headerClassNames.select, selectClassName)}
cn={cn}
{...props}
/>
),
);
Title.displayName = "DateRangeTimePicker.Title";
export const Grid = forwardRef<HTMLDivElement, GridProps>(
({ className, weekdayClassName, weekdaysClassName, weekClassName, ...props }, ref) => (
<Primitive.Grid
ref={ref}
className={cn(gridClassNames.panel, className)}
weekdayClassName={cn(gridClassNames.weekday, weekdayClassName)}
weekdaysClassName={cn(gridClassNames.weekdays, weekdaysClassName)}
weekClassName={cn(gridClassNames.week, weekClassName)}
gridClassName={gridClassNames.grid}
dayClassName={dayClassNames.day}
dayTodayClassName={dayClassNames.today}
daySelectedClassName={dayClassNames.selected}
dayDisabledClassName={dayClassNames.disabled}
dayOutsideClassName={dayClassNames.outside}
dayHighlightedClassName={dayClassNames.highlighted}
dayInRangeClassName={dayClassNames.inRange}
dayRangeStartClassName={dayClassNames.rangeStart}
dayRangeEndClassName={dayClassNames.rangeEnd}
dayRangeSingleClassName={dayClassNames.rangeSingle}
dayHoverRangeClassName={dayClassNames.hoverRange}
dayHoverTargetClassName={dayClassNames.hoverTarget}
dayFocusedClassName={dayClassNames.focused}
dayEmptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
),
);
Grid.displayName = "DateRangeTimePicker.Grid";
export const Day = forwardRef<HTMLButtonElement, DayProps>(({ className, ...props }, ref) => (
<Primitive.Day
ref={ref}
className={cn(dayClassNames.day, className)}
todayClassName={dayClassNames.today}
selectedClassName={dayClassNames.selected}
disabledClassName={dayClassNames.disabled}
outsideClassName={dayClassNames.outside}
highlightedClassName={dayClassNames.highlighted}
inRangeClassName={dayClassNames.inRange}
rangeStartClassName={dayClassNames.rangeStart}
rangeEndClassName={dayClassNames.rangeEnd}
rangeSingleClassName={dayClassNames.rangeSingle}
hoverRangeClassName={dayClassNames.hoverRange}
hoverTargetClassName={dayClassNames.hoverTarget}
focusedClassName={dayClassNames.focused}
emptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
));
Day.displayName = "DateRangeTimePicker.Day";
export const Calendars = forwardRef<HTMLDivElement, CalendarsProps>(
({ className, ...props }, ref) => (
<Primitive.Calendars
ref={ref}
className={cn(rangeClassNames.calendarsContainer, className)}
cn={cn}
{...props}
/>
),
);
Calendars.displayName = "DateRangeTimePicker.Calendars";
export const Presets = forwardRef<HTMLDivElement, PresetsProps>(({ className, ...props }, ref) => (
<Primitive.Presets
ref={ref}
className={cn(rangeClassNames.presetsContainer, className)}
cn={cn}
{...props}
/>
));
Presets.displayName = "DateRangeTimePicker.Presets";
export const PresetItem = forwardRef<HTMLButtonElement, PresetItemProps>(
({ className, activeClassName, ...props }, ref) => (
<Primitive.PresetItem
ref={ref}
className={cn(rangeClassNames.presetButton, className)}
activeClassName={cn(rangeClassNames.presetButtonActive, activeClassName)}
cn={cn}
{...props}
/>
),
);
PresetItem.displayName = "DateRangeTimePicker.PresetItem";
export const StartTimeSection = forwardRef<HTMLDivElement, StartTimeSectionProps>(
({ className, ...props }, ref) => (
<Primitive.StartTimeSection
ref={ref}
className={cn(dateTimeClassNames.timeSection, className)}
cn={cn}
{...props}
/>
),
);
StartTimeSection.displayName = "DateRangeTimePicker.StartTimeSection";
export const EndTimeSection = forwardRef<HTMLDivElement, EndTimeSectionProps>(
({ className, ...props }, ref) => (
<Primitive.EndTimeSection
ref={ref}
className={cn(dateTimeClassNames.timeSection, className)}
cn={cn}
{...props}
/>
),
);
EndTimeSection.displayName = "DateRangeTimePicker.EndTimeSection";
export const TimePanel = forwardRef<HTMLDivElement, TimePanelProps>(
({ className, ...props }, ref) => (
<Primitive.TimePanel
ref={ref}
className={cn(timePanelClassNames.timePanel, className)}
highlightClassName={timePanelClassNames.timeHighlight}
columnsClassName={timePanelClassNames.timeColumns}
columnClassName={timePanelClassNames.timeColumn}
itemClassName={timePanelClassNames.timeItem}
itemActiveClassName={timePanelClassNames.timeItemActive}
paddingClassName={timePanelClassNames.timePadding}
colonClassName={timePanelClassNames.timeColon}
periodButtonClassName={timePanelClassNames.timePeriodButton}
cn={cn}
{...props}
/>
),
);
TimePanel.displayName = "DateRangeTimePicker.TimePanel";
export const Footer = forwardRef<HTMLDivElement, FooterProps>(({ className, ...props }, ref) => (
<Primitive.Footer
ref={ref}
className={cn(footerClassNames.footer, className)}
cn={cn}
{...props}
/>
));
Footer.displayName = "DateRangeTimePicker.Footer";
export const ClearButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ClearButton
ref={ref}
className={cn(footerClassNames.clearButton, className)}
cn={cn}
{...props}
/>
),
);
ClearButton.displayName = "DateRangeTimePicker.ClearButton";
export const CancelButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.CancelButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
CancelButton.displayName = "DateRangeTimePicker.CancelButton";
export const ConfirmButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ConfirmButton
ref={ref}
className={cn(footerClassNames.confirmButton, className)}
cn={cn}
{...props}
/>
),
);
ConfirmButton.displayName = "DateRangeTimePicker.ConfirmButton";
export const TodayButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.TodayButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
TodayButton.displayName = "DateRangeTimePicker.TodayButton";
registry/components/DateRangeTimePicker/index.tsx
import * as C from "./Compound";
import type { DateRangeTimePickerRootProps } from "../../ui-primitives/index";
import { cn } from "../../utils";
import { headerClassNames, rangeClassNames, dateTimeClassNames } from "../../theme";
export interface DateRangeTimePickerProps extends Omit<DateRangeTimePickerRootProps, "children"> {
hideHeader?: boolean;
hideFooter?: boolean;
}
export function DateRangeTimePickerSimple({
hideHeader,
hideFooter,
...props
}: DateRangeTimePickerProps) {
const hasPresets = props.presets && props.presets.length > 0;
const showFooter =
hideFooter !== undefined ? !hideFooter : !props.inline && !props.shouldCloseOnSelect;
const showNav = props.captionLayout !== "dropdown";
return (
<C.Root {...props}>
{!props.inline && <C.Trigger />}
<C.Content>
<div className={cn(hasPresets && rangeClassNames.presetsLayout)}>
{hasPresets && (
<C.Presets>
{props.presets?.map((p, i) => (
<C.PresetItem key={p.label} index={i} />
))}
</C.Presets>
)}
<div>
{!hideHeader && (
<C.Header>
{showNav && <C.PrevButton />}
<C.Title calendarIndex={0} />
<div className={headerClassNames.spacer} />
<C.Title calendarIndex={1} />
{showNav && <C.NextButton />}
</C.Header>
)}
<C.Calendars>
<div>
<C.Grid calendarIndex={0} />
<C.StartTimeSection>
<div className={dateTimeClassNames.timeLabel}>Start Time</div>
<C.TimePanel target="start" />
</C.StartTimeSection>
</div>
<div>
<C.Grid calendarIndex={1} />
<C.EndTimeSection>
<div className={dateTimeClassNames.timeLabel}>End Time</div>
<C.TimePanel target="end" />
</C.EndTimeSection>
</div>
</C.Calendars>
{showFooter && (
<C.Footer>
<C.ClearButton />
<C.CancelButton />
<C.ConfirmButton />
</C.Footer>
)}
</div>
</div>
</C.Content>
</C.Root>
);
}
export const DateRangeTimePicker = Object.assign(DateRangeTimePickerSimple, C);
registry/components/DateTimePicker/Compound.tsx
import { forwardRef } from "react";
import {
DateTimePicker as Primitive,
type DateTimePickerRootProps,
type TriggerProps,
type ContentProps,
type HeaderProps,
type NavButtonProps,
type TitleProps,
type GridProps,
type DayProps,
type FooterProps,
type ActionButtonProps,
type DateTimeSectionProps,
type TimePanelProps,
} from "../../ui-primitives/index";
import { cn } from "../../utils";
import {
rootClassNames,
triggerClassNames,
contentClassNames,
headerClassNames,
gridClassNames,
dayClassNames,
footerClassNames,
dateTimeClassNames,
timePanelClassNames,
} from "../../theme";
export const Root = forwardRef<HTMLDivElement, DateTimePickerRootProps>(
({ className, size, ...props }, ref) => (
<Primitive.Root
ref={ref}
className={cn(rootClassNames.root, size && `rdrp-size-${size}`, className)}
cn={cn}
size={size}
{...props}
/>
),
);
Root.displayName = "DateTimePicker.Root";
export const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(
({ className, placeholderClassName, clearClassName, ...props }, ref) => (
<Primitive.Trigger
ref={ref}
className={cn(triggerClassNames.trigger, className)}
placeholderClassName={cn(triggerClassNames.placeholder, placeholderClassName)}
clearClassName={cn(triggerClassNames.clear, clearClassName)}
cn={cn}
{...props}
/>
),
);
Trigger.displayName = "DateTimePicker.Trigger";
export const Content = forwardRef<HTMLDivElement, ContentProps>(
({ className, inlineClassName, ...props }, ref) => (
<Primitive.Content
ref={ref}
className={cn(rootClassNames.root, contentClassNames.content, className)}
inlineClassName={cn(contentClassNames.contentInline, inlineClassName)}
cn={cn}
{...props}
/>
),
);
Content.displayName = "DateTimePicker.Content";
export const Header = forwardRef<HTMLDivElement, HeaderProps>(({ className, ...props }, ref) => (
<Primitive.Header
ref={ref}
className={cn(headerClassNames.header, className)}
cn={cn}
{...props}
/>
));
Header.displayName = "DateTimePicker.Header";
export const PrevButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.PrevButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
PrevButton.displayName = "DateTimePicker.PrevButton";
export const NextButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, ...props }, ref) => (
<Primitive.NextButton
ref={ref}
className={cn(headerClassNames.navButton, className)}
cn={cn}
{...props}
/>
),
);
NextButton.displayName = "DateTimePicker.NextButton";
export const Title = forwardRef<HTMLDivElement, TitleProps>(
({ className, selectClassName, ...props }, ref) => (
<Primitive.Title
ref={ref}
className={cn(headerClassNames.title, className)}
selectClassName={cn(headerClassNames.select, selectClassName)}
cn={cn}
{...props}
/>
),
);
Title.displayName = "DateTimePicker.Title";
export const Grid = forwardRef<HTMLDivElement, GridProps>(
({ className, weekdayClassName, weekdaysClassName, weekClassName, ...props }, ref) => (
<Primitive.Grid
ref={ref}
className={cn(gridClassNames.panel, className)}
weekdayClassName={cn(gridClassNames.weekday, weekdayClassName)}
weekdaysClassName={cn(gridClassNames.weekdays, weekdaysClassName)}
weekClassName={cn(gridClassNames.week, weekClassName)}
gridClassName={gridClassNames.grid}
dayClassName={dayClassNames.day}
dayTodayClassName={dayClassNames.today}
daySelectedClassName={dayClassNames.selected}
dayDisabledClassName={dayClassNames.disabled}
dayOutsideClassName={dayClassNames.outside}
dayHighlightedClassName={dayClassNames.highlighted}
dayInRangeClassName={dayClassNames.inRange}
dayRangeStartClassName={dayClassNames.rangeStart}
dayRangeEndClassName={dayClassNames.rangeEnd}
dayRangeSingleClassName={dayClassNames.rangeSingle}
dayHoverRangeClassName={dayClassNames.hoverRange}
dayHoverTargetClassName={dayClassNames.hoverTarget}
dayFocusedClassName={dayClassNames.focused}
dayEmptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
),
);
Grid.displayName = "DateTimePicker.Grid";
export const Day = forwardRef<HTMLButtonElement, DayProps>(({ className, ...props }, ref) => (
<Primitive.Day
ref={ref}
className={cn(dayClassNames.day, className)}
todayClassName={dayClassNames.today}
selectedClassName={dayClassNames.selected}
disabledClassName={dayClassNames.disabled}
outsideClassName={dayClassNames.outside}
highlightedClassName={dayClassNames.highlighted}
inRangeClassName={dayClassNames.inRange}
rangeStartClassName={dayClassNames.rangeStart}
rangeEndClassName={dayClassNames.rangeEnd}
rangeSingleClassName={dayClassNames.rangeSingle}
hoverRangeClassName={dayClassNames.hoverRange}
hoverTargetClassName={dayClassNames.hoverTarget}
focusedClassName={dayClassNames.focused}
emptyClassName={dayClassNames.empty}
cn={cn}
{...props}
/>
));
Day.displayName = "DateTimePicker.Day";
export const TimeSection = forwardRef<HTMLDivElement, DateTimeSectionProps>(
({ className, ...props }, ref) => (
<Primitive.TimeSection
ref={ref}
className={cn(dateTimeClassNames.timeSection, className)}
cn={cn}
{...props}
/>
),
);
TimeSection.displayName = "DateTimePicker.TimeSection";
export const TimePanel = forwardRef<HTMLDivElement, TimePanelProps>(
({ className, ...props }, ref) => (
<Primitive.TimePanel
ref={ref}
className={cn(timePanelClassNames.timePanel, className)}
highlightClassName={timePanelClassNames.timeHighlight}
columnsClassName={timePanelClassNames.timeColumns}
columnClassName={timePanelClassNames.timeColumn}
itemClassName={timePanelClassNames.timeItem}
itemActiveClassName={timePanelClassNames.timeItemActive}
paddingClassName={timePanelClassNames.timePadding}
colonClassName={timePanelClassNames.timeColon}
periodButtonClassName={timePanelClassNames.timePeriodButton}
cn={cn}
{...props}
/>
),
);
TimePanel.displayName = "DateTimePicker.TimePanel";
export const Footer = forwardRef<HTMLDivElement, FooterProps>(({ className, ...props }, ref) => (
<Primitive.Footer
ref={ref}
className={cn(footerClassNames.footer, className)}
cn={cn}
{...props}
/>
));
Footer.displayName = "DateTimePicker.Footer";
export const ClearButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ClearButton
ref={ref}
className={cn(footerClassNames.clearButton, className)}
cn={cn}
{...props}
/>
),
);
ClearButton.displayName = "DateTimePicker.ClearButton";
export const CancelButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.CancelButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
CancelButton.displayName = "DateTimePicker.CancelButton";
export const ConfirmButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ConfirmButton
ref={ref}
className={cn(footerClassNames.confirmButton, className)}
cn={cn}
{...props}
/>
),
);
ConfirmButton.displayName = "DateTimePicker.ConfirmButton";
export const TodayButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.TodayButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
TodayButton.displayName = "DateTimePicker.TodayButton";
registry/components/DateTimePicker/index.tsx
import * as C from "./Compound";
import type { DateTimePickerRootProps } from "../../ui-primitives/index";
import { dateTimeClassNames } from "../../theme";
export interface DateTimePickerProps extends Omit<DateTimePickerRootProps, "children"> {
hideHeader?: boolean;
hideFooter?: boolean;
}
export function DateTimePickerSimple({ hideHeader, hideFooter, ...props }: DateTimePickerProps) {
const showFooter =
hideFooter !== undefined ? !hideFooter : !props.inline && !props.shouldCloseOnSelect;
const showNav = props.captionLayout !== "dropdown";
return (
<C.Root {...props}>
{!props.inline && <C.Trigger />}
<C.Content>
{!hideHeader && (
<C.Header>
{showNav && <C.PrevButton />}
<C.Title />
{showNav && <C.NextButton />}
</C.Header>
)}
<C.Grid />
<C.TimeSection>
<div className={dateTimeClassNames.timeLabel}>Time</div>
<C.TimePanel target="single" />
</C.TimeSection>
{showFooter && (
<C.Footer>
<C.ClearButton />
<C.CancelButton />
<C.ConfirmButton />
</C.Footer>
)}
</C.Content>
</C.Root>
);
}
export const DateTimePicker = Object.assign(DateTimePickerSimple, C);
registry/components/TimePicker/Compound.tsx
import { forwardRef } from "react";
import {
TimePicker as Primitive,
type TimePickerRootProps,
type TriggerProps,
type ContentProps,
type FooterProps,
type ActionButtonProps,
type TimePanelProps,
type TimePickerPeriodToggleProps,
} from "../../ui-primitives/index";
import { cn } from "../../utils";
import {
rootClassNames,
triggerClassNames,
contentClassNames,
footerClassNames,
timePanelClassNames,
timePickerClassNames,
} from "../../theme";
export const Root = forwardRef<HTMLDivElement, TimePickerRootProps>(
({ className, ...props }, ref) => (
<Primitive.Root
ref={ref}
className={cn(rootClassNames.root, timePickerClassNames.root, className)}
cn={cn}
{...props}
/>
),
);
Root.displayName = "TimePicker.Root";
export const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(
({ className, placeholderClassName, clearClassName, ...props }, ref) => (
<Primitive.Trigger
ref={ref}
className={cn(triggerClassNames.trigger, className)}
placeholderClassName={cn(triggerClassNames.placeholder, placeholderClassName)}
clearClassName={cn(triggerClassNames.clear, clearClassName)}
cn={cn}
{...props}
/>
),
);
Trigger.displayName = "TimePicker.Trigger";
export const Content = forwardRef<HTMLDivElement, ContentProps>(
({ className, inlineClassName, ...props }, ref) => (
<Primitive.Content
ref={ref}
className={cn(
rootClassNames.root,
contentClassNames.content,
timePickerClassNames.content,
className,
)}
inlineClassName={cn(contentClassNames.contentInline, inlineClassName)}
cn={cn}
{...props}
/>
),
);
Content.displayName = "TimePicker.Content";
export const TimePanel = forwardRef<HTMLDivElement, TimePanelProps>(
({ className, ...props }, ref) => (
<Primitive.TimePanel
ref={ref}
className={cn(timePanelClassNames.timePanel, className)}
highlightClassName={timePanelClassNames.timeHighlight}
columnsClassName={timePanelClassNames.timeColumns}
columnClassName={timePanelClassNames.timeColumn}
itemClassName={timePanelClassNames.timeItem}
itemActiveClassName={timePanelClassNames.timeItemActive}
paddingClassName={timePanelClassNames.timePadding}
colonClassName={timePanelClassNames.timeColon}
periodButtonClassName={timePanelClassNames.timePeriodButton}
cn={cn}
{...props}
/>
),
);
TimePanel.displayName = "TimePicker.TimePanel";
export const PeriodToggle = forwardRef<HTMLButtonElement, TimePickerPeriodToggleProps>(
({ className, ...props }, ref) => (
<Primitive.PeriodToggle
ref={ref}
className={cn(timePanelClassNames.timePeriodButton, className)}
cn={cn}
{...props}
/>
),
);
PeriodToggle.displayName = "TimePicker.PeriodToggle";
export const Footer = forwardRef<HTMLDivElement, FooterProps>(({ className, ...props }, ref) => (
<Primitive.Footer
ref={ref}
className={cn(footerClassNames.footer, className)}
cn={cn}
{...props}
/>
));
Footer.displayName = "TimePicker.Footer";
export const ClearButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ClearButton
ref={ref}
className={cn(footerClassNames.clearButton, className)}
cn={cn}
{...props}
/>
),
);
ClearButton.displayName = "TimePicker.ClearButton";
export const CancelButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.CancelButton
ref={ref}
className={cn(footerClassNames.button, className)}
cn={cn}
{...props}
/>
),
);
CancelButton.displayName = "TimePicker.CancelButton";
export const ConfirmButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, ...props }, ref) => (
<Primitive.ConfirmButton
ref={ref}
className={cn(footerClassNames.confirmButton, className)}
cn={cn}
{...props}
/>
),
);
ConfirmButton.displayName = "TimePicker.ConfirmButton";
registry/components/TimePicker/index.tsx
import * as C from "./Compound";
import type { TimePickerRootProps } from "../../ui-primitives/index";
export interface TimePickerProps extends Omit<TimePickerRootProps, "children"> {
hideFooter?: boolean;
}
export function TimePickerSimple({ hideFooter, ...props }: TimePickerProps) {
const showFooter = hideFooter !== undefined ? !hideFooter : !props.inline;
return (
<C.Root {...props}>
{!props.inline && <C.Trigger />}
<C.Content>
<C.TimePanel target="single" />
{showFooter && (
<C.Footer>
<C.ClearButton />
<C.CancelButton />
<C.ConfirmButton />
</C.Footer>
)}
</C.Content>
</C.Root>
);
}
export const TimePicker = Object.assign(TimePickerSimple, C);
registry/theme.ts
// Theme values for tailwind4
export const rootClassNames = {
root: "relative w-max font-sans text-[13px] text-foreground select-none",
};
export const triggerClassNames = {
trigger:
"flex items-center gap-2 justify-between min-w-[200px] px-3.5 py-2 text-sm bg-background border border-input rounded-md shadow-sm hover:border-ring/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-ring",
placeholder: "text-muted-foreground",
clear:
"ml-2 p-1 -m-1 appearance-none border-0 bg-transparent text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm cursor-pointer",
};
export const headerClassNames = {
header: "flex items-center justify-between mb-3",
navButton:
"flex items-center justify-center w-7 h-7 text-muted-foreground rounded-md hover:bg-accent cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
title: "flex-1 flex items-center justify-center gap-1 font-semibold text-[15px] text-foreground",
select:
"px-1 py-0.5 text-sm bg-transparent border border-transparent rounded hover:border-border focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer",
spacer: "flex-1",
};
export const gridClassNames = {
panel: "flex flex-col",
weekdays: "grid grid-cols-7 mb-1",
weekday: "flex items-center justify-center h-7 text-xs font-medium text-muted-foreground",
grid: "flex flex-col gap-1",
week: "grid grid-cols-7",
};
export const dayClassNames = {
day: "flex items-center justify-center min-w-9 h-9 mx-[1px] rounded-lg transition-colors cursor-pointer hover:bg-accent focus-visible:outline-none",
empty: "invisible pointer-events-none",
today: "font-bold text-primary",
selected:
"bg-primary text-primary-foreground font-semibold hover:bg-primary hover:text-primary-foreground",
disabled: "text-muted-foreground opacity-50 pointer-events-none",
outside: "text-muted-foreground/50",
highlighted:
"relative after:absolute after:bottom-[3px] after:left-1/2 after:-translate-x-1/2 after:w-1 after:h-1 after:bg-primary after:rounded-full",
inRange: "bg-primary/10 text-foreground rounded-none mx-0 hover:bg-primary/10",
rangeStart:
"bg-primary text-primary-foreground rounded-l-lg rounded-r-none mx-0 hover:bg-primary hover:text-primary-foreground",
rangeEnd:
"bg-primary text-primary-foreground rounded-r-lg rounded-l-none mx-0 hover:bg-primary hover:text-primary-foreground",
rangeSingle:
"bg-primary text-primary-foreground mx-0 hover:bg-primary hover:text-primary-foreground",
hoverRange: "bg-primary/5 text-foreground rounded-none mx-0",
hoverTarget: "bg-primary/15 text-foreground",
focused: "ring-2 ring-ring ring-offset-1 ring-offset-background",
};
export const footerClassNames = {
footer: "flex justify-end gap-2 mt-3 pt-3 border-t border-border",
button:
"px-3.5 py-1.5 text-muted-foreground border border-input hover:bg-accent hover:border-ring/50 rounded-md transition-colors motion-reduce:transition-none cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
confirmButton:
"px-3.5 py-1.5 text-primary-foreground bg-primary hover:bg-primary/90 rounded-md transition-colors motion-reduce:transition-none disabled:opacity-50 disabled:pointer-events-none cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
clearButton:
"mr-auto px-3.5 py-1.5 text-destructive hover:text-foreground hover:bg-accent rounded-md transition-colors motion-reduce:transition-none cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
};
export const contentClassNames = {
content:
"absolute top-[calc(100%+8px)] left-0 z-50 p-4 bg-popover text-popover-foreground border border-border rounded-xl shadow-lg",
contentInline: "static z-auto shadow-none mt-0",
};
export const rangeClassNames = {
presetsContainer: "flex flex-col gap-0.5 pr-4 border-r border-border min-w-[140px]",
presetButton:
"px-4 py-2 text-left rounded-md transition-colors motion-reduce:transition-none hover:bg-accent cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
presetButtonActive: "bg-accent text-primary font-medium",
presetsLayout: "flex flex-wrap gap-4",
calendarsContainer: "flex flex-wrap gap-6 max-sm:flex-col max-sm:gap-4",
};
export const dateTimeClassNames = {
timeSection: "flex flex-col gap-3 mt-3 pt-3 border-t border-border",
timeLabel: "text-xs font-medium text-muted-foreground",
};
export const timePanelClassNames = {
timePanel: "relative flex items-center justify-center w-fit mx-auto min-w-[120px] h-[176px]",
timeHighlight: "absolute left-1 right-1 h-8 rounded-md bg-primary/10 pointer-events-none",
timeColumns: "relative flex items-center w-full h-full",
timeColumn:
"flex-1 h-[160px] overflow-y-auto [scrollbar-width:none] [scroll-snap-type:y_mandatory] [&::-webkit-scrollbar]:hidden focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset rounded-md",
timeItem:
"h-8 flex items-center justify-center cursor-pointer text-sm text-muted-foreground [scroll-snap-align:center] transition-colors select-none hover:text-foreground",
timeItemActive: "text-foreground font-semibold",
timePadding: "h-8 shrink-0",
timeColon: "text-sm font-semibold text-foreground w-3 text-center shrink-0",
timePeriodButton:
"h-8 flex items-center justify-center cursor-pointer text-sm text-foreground font-semibold border border-border bg-background hover:bg-accent hover:text-accent-foreground rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
};
export const timePickerClassNames = {
root: "relative w-max font-sans text-[13px] text-foreground select-none",
content:
"min-w-[250px] [&.rdrp-size-small]:min-w-[210px] [&.rdrp-size-large]:min-w-[290px] [&.rdrp-size-x-large]:min-w-[340px]",
};
registry/ui-primitives/DatePicker/Root.tsx
import { mergeRefs } from "../../utils/mergeRefs";
import { forwardRef, type ReactNode } from "react";
import { usePickerContext, formatBasic } from "react-date-range-picker-headless";
import { DatePickerProvider, type DatePickerProviderProps } from "react-date-range-picker-headless";
export interface DatePickerRootProps extends DatePickerProviderProps {
className?: string;
cn?: (...args: unknown[]) => string;
}
interface InnerProps {
className?: string;
children?: ReactNode;
name?: string;
value?: Date | null;
}
const Inner = forwardRef<HTMLDivElement, InnerProps>(
({ className, children, name, value }, ref) => {
const context = usePickerContext();
return (
<div ref={mergeRefs(ref, context.containerRef)} className={className} data-slot="root">
{name && (
<input type="hidden" name={name} value={value ? formatBasic(value, "YYYY-MM-DD") : ""} />
)}
{children}
</div>
);
},
);
Inner.displayName = "Inner";
export const DatePickerRoot = forwardRef<HTMLDivElement, DatePickerRootProps>(
({ className, cn: cnFn, children, ...options }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<DatePickerProvider {...options}>
<Inner ref={ref} className={cn(className)} name={options.name} value={options.value}>
{children}
</Inner>
</DatePickerProvider>
);
},
);
DatePickerRoot.displayName = "DatePickerRoot";
registry/ui-primitives/DatePicker/index.ts
import { DatePickerRoot } from "./Root";
import { Trigger } from "../shared/Trigger";
import { Content } from "../shared/Content";
import { Header } from "../shared/Header";
import { PrevButton } from "../shared/PrevButton";
import { NextButton } from "../shared/NextButton";
import { Title } from "../shared/Title";
import { Grid } from "../shared/Grid";
import { Day } from "../shared/Day";
import { Footer } from "../shared/Footer";
import { ClearButton } from "../shared/ClearButton";
import { CancelButton } from "../shared/CancelButton";
import { ConfirmButton } from "../shared/ConfirmButton";
import { TodayButton } from "../shared/TodayButton";
export const DatePicker = {
Root: DatePickerRoot,
Trigger,
Content,
Header,
PrevButton,
NextButton,
Title,
Grid,
Day,
Footer,
ClearButton,
CancelButton,
ConfirmButton,
TodayButton,
};
registry/ui-primitives/DateRangePicker/Calendars.tsx
import { type ReactNode, forwardRef } from "react";
export interface CalendarsProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const Calendars = forwardRef<HTMLDivElement, CalendarsProps>(
({ className, children, cn: cnFn }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<div ref={ref} className={cn("rdrp-calendars-container", className)}>
{children}
</div>
);
},
);
Calendars.displayName = "Calendars";
registry/ui-primitives/DateRangePicker/PresetItem.tsx
import { type ReactNode, forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
export interface PresetItemProps {
index: number;
className?: string;
activeClassName?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const PresetItem = forwardRef<HTMLButtonElement, PresetItemProps>(
({ index, className, activeClassName, children, cn: cnFn }, ref) => {
const { presets, activePresetIndex, handlePresetClick } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
if (!presets) {
throw new Error("PresetItem can only be used within DateRangePicker, DateRangeTimePicker");
}
if (!presets[index]) return null;
const preset = presets[index];
const isActive = index === activePresetIndex;
return (
<button
ref={ref}
type="button"
className={cn(
"rdrp-preset-button",
className,
isActive && "rdrp-preset-button-active",
isActive && activeClassName,
)}
onClick={() => handlePresetClick?.(preset)}
>
{children || preset.label}
</button>
);
},
);
PresetItem.displayName = "PresetItem";
registry/ui-primitives/DateRangePicker/Presets.tsx
import { type ReactNode, forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
export interface PresetsProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const Presets = forwardRef<HTMLDivElement, PresetsProps>(
({ className, children, cn: cnFn }, ref) => {
const { presets } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
if (!presets) {
throw new Error("Presets can only be used within DateRangePicker, DateRangeTimePicker");
}
if (presets.length === 0) return null;
return (
<div ref={ref} className={cn("rdrp-presets-container", className)}>
{children}
</div>
);
},
);
Presets.displayName = "Presets";
registry/ui-primitives/DateRangePicker/Root.tsx
import { mergeRefs } from "../../utils/mergeRefs";
import { forwardRef, type ReactNode } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import {
DateRangePickerProvider,
type DateRangePickerProviderProps,
} from "react-date-range-picker-headless";
import { formatBasic } from "react-date-range-picker-headless";
export interface DateRangePickerRootProps extends DateRangePickerProviderProps {
className?: string;
endName?: string;
cn?: (...args: unknown[]) => string;
}
interface InnerProps {
className?: string;
children?: ReactNode;
name?: string;
endName?: string;
value?: { start: Date | null; end: Date | null };
}
const Inner = forwardRef<HTMLDivElement, InnerProps>(
({ className, children, name, endName, value }, ref) => {
const context = usePickerContext();
return (
<div ref={mergeRefs(ref, context.containerRef)} className={className} data-slot="root">
{name && (
<input
type="hidden"
name={name}
value={value?.start && value?.end ? formatBasic(value.start, "YYYY-MM-DD") : ""}
/>
)}
{name && (
<input
type="hidden"
name={endName || `${name}-end`}
value={value?.start && value?.end ? formatBasic(value.end, "YYYY-MM-DD") : ""}
/>
)}
{children}
</div>
);
},
);
Inner.displayName = "Inner";
export const DateRangePickerRoot = forwardRef<HTMLDivElement, DateRangePickerRootProps>(
({ className, cn: cnFn, children, endName, ...options }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<DateRangePickerProvider {...options}>
<Inner
ref={ref}
className={cn(className)}
endName={endName}
name={options.name}
value={options.value}
>
{children}
</Inner>
</DateRangePickerProvider>
);
},
);
DateRangePickerRoot.displayName = "DateRangePickerRoot";
registry/ui-primitives/DateRangePicker/index.ts
import { DateRangePickerRoot } from "./Root";
import { Calendars } from "./Calendars";
import { Presets } from "./Presets";
import { PresetItem } from "./PresetItem";
import { Trigger } from "../shared/Trigger";
import { Content } from "../shared/Content";
import { Header } from "../shared/Header";
import { PrevButton } from "../shared/PrevButton";
import { NextButton } from "../shared/NextButton";
import { Title } from "../shared/Title";
import { Grid } from "../shared/Grid";
import { Day } from "../shared/Day";
import { Footer } from "../shared/Footer";
import { ClearButton } from "../shared/ClearButton";
import { CancelButton } from "../shared/CancelButton";
import { ConfirmButton } from "../shared/ConfirmButton";
import { TodayButton } from "../shared/TodayButton";
export const DateRangePicker = {
Root: DateRangePickerRoot,
Trigger,
Content,
Header,
PrevButton,
NextButton,
Title,
Grid,
Day,
Calendars,
Presets,
PresetItem,
Footer,
ClearButton,
CancelButton,
ConfirmButton,
TodayButton,
};
registry/ui-primitives/DateRangeTimePicker/EndTimeSection.tsx
import { type ReactNode, forwardRef } from "react";
export interface EndTimeSectionProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const EndTimeSection = forwardRef<HTMLDivElement, EndTimeSectionProps>(
({ className, children, cn: cnFn }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<div ref={ref} className={cn("rdrp-time-section", className)}>
{children}
</div>
);
},
);
EndTimeSection.displayName = "EndTimeSection";
registry/ui-primitives/DateRangeTimePicker/Root.tsx
import { mergeRefs } from "../../utils/mergeRefs";
import { forwardRef, type ReactNode } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import {
DateRangeTimePickerProvider,
type DateRangeTimePickerProviderProps,
} from "react-date-range-picker-headless";
import { formatBasic } from "react-date-range-picker-headless";
export interface DateRangeTimePickerRootProps extends DateRangeTimePickerProviderProps {
className?: string;
endName?: string;
cn?: (...args: unknown[]) => string;
}
interface InnerProps {
className?: string;
children?: ReactNode;
name?: string;
endName?: string;
value?: { start: Date | null; end: Date | null };
}
const Inner = forwardRef<HTMLDivElement, InnerProps>(
({ className, children, name, endName, value }, ref) => {
const context = usePickerContext();
return (
<div ref={mergeRefs(ref, context.containerRef)} className={className} data-slot="root">
{name && (
<input
type="hidden"
name={name}
value={value?.start && value?.end ? formatBasic(value.start, "YYYY-MM-DD") : ""}
/>
)}
{name && (
<input
type="hidden"
name={endName || `${name}-end`}
value={value?.start && value?.end ? formatBasic(value.end, "YYYY-MM-DD") : ""}
/>
)}
{children}
</div>
);
},
);
Inner.displayName = "Inner";
export const DateRangeTimePickerRoot = forwardRef<HTMLDivElement, DateRangeTimePickerRootProps>(
({ className, cn: cnFn, children, endName, ...options }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<DateRangeTimePickerProvider {...options}>
<Inner
ref={ref}
className={cn(className)}
endName={endName}
name={options.name}
value={options.value}
>
{children}
</Inner>
</DateRangeTimePickerProvider>
);
},
);
DateRangeTimePickerRoot.displayName = "DateRangeTimePickerRoot";
registry/ui-primitives/DateRangeTimePicker/StartTimeSection.tsx
import { type ReactNode, forwardRef } from "react";
export interface StartTimeSectionProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const StartTimeSection = forwardRef<HTMLDivElement, StartTimeSectionProps>(
({ className, children, cn: cnFn }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<div ref={ref} className={cn("rdrp-time-section", className)}>
{children}
</div>
);
},
);
StartTimeSection.displayName = "StartTimeSection";
registry/ui-primitives/DateRangeTimePicker/index.ts
import { DateRangeTimePickerRoot } from "./Root";
import { StartTimeSection } from "./StartTimeSection";
import { EndTimeSection } from "./EndTimeSection";
import { Calendars } from "../DateRangePicker/Calendars";
import { Presets } from "../DateRangePicker/Presets";
import { PresetItem } from "../DateRangePicker/PresetItem";
import { Trigger } from "../shared/Trigger";
import { Content } from "../shared/Content";
import { Header } from "../shared/Header";
import { PrevButton } from "../shared/PrevButton";
import { NextButton } from "../shared/NextButton";
import { Title } from "../shared/Title";
import { Grid } from "../shared/Grid";
import { Day } from "../shared/Day";
import { Footer } from "../shared/Footer";
import { ClearButton } from "../shared/ClearButton";
import { CancelButton } from "../shared/CancelButton";
import { ConfirmButton } from "../shared/ConfirmButton";
import { TodayButton } from "../shared/TodayButton";
import { TimePanel } from "../shared/TimePanel";
export const DateRangeTimePicker = {
Root: DateRangeTimePickerRoot,
Trigger,
Content,
Header,
PrevButton,
NextButton,
Title,
Grid,
Day,
Calendars,
Presets,
PresetItem,
StartTimeSection,
EndTimeSection,
TimePanel,
Footer,
ClearButton,
CancelButton,
ConfirmButton,
TodayButton,
};
registry/ui-primitives/DateTimePicker/Root.tsx
import { mergeRefs } from "../../utils/mergeRefs";
import { forwardRef, type ReactNode } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import {
DateTimePickerProvider,
type DateTimePickerProviderProps,
formatBasic,
} from "react-date-range-picker-headless";
export interface DateTimePickerRootProps extends DateTimePickerProviderProps {
className?: string;
cn?: (...args: unknown[]) => string;
}
interface InnerProps {
className?: string;
children?: ReactNode;
name?: string;
value?: Date | null;
}
const Inner = forwardRef<HTMLDivElement, InnerProps>(
({ className, children, name, value }, ref) => {
const context = usePickerContext();
return (
<div ref={mergeRefs(ref, context.containerRef)} className={className} data-slot="root">
{name && (
<input
type="hidden"
name={name}
value={value ? formatBasic(value, "YYYY-MM-DD HH:mm:ss") : ""}
/>
)}
{children}
</div>
);
},
);
Inner.displayName = "Inner";
export const DateTimePickerRoot = forwardRef<HTMLDivElement, DateTimePickerRootProps>(
({ className, cn: cnFn, children, ...options }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<DateTimePickerProvider {...options}>
<Inner ref={ref} className={cn(className)} name={options.name} value={options.value}>
{children}
</Inner>
</DateTimePickerProvider>
);
},
);
DateTimePickerRoot.displayName = "DateTimePickerRoot";
registry/ui-primitives/DateTimePicker/TimeSection.tsx
import { type ReactNode, forwardRef } from "react";
export interface TimeSectionProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const TimeSection = forwardRef<HTMLDivElement, TimeSectionProps>(
({ className, children, cn: cnFn }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<div ref={ref} className={cn("rdrp-time-section", className)}>
{children}
</div>
);
},
);
TimeSection.displayName = "TimeSection";
registry/ui-primitives/DateTimePicker/index.ts
import { DateTimePickerRoot } from "./Root";
import { TimeSection } from "./TimeSection";
import { Trigger } from "../shared/Trigger";
import { Content } from "../shared/Content";
import { Header } from "../shared/Header";
import { PrevButton } from "../shared/PrevButton";
import { NextButton } from "../shared/NextButton";
import { Title } from "../shared/Title";
import { Grid } from "../shared/Grid";
import { Day } from "../shared/Day";
import { Footer } from "../shared/Footer";
import { ClearButton } from "../shared/ClearButton";
import { CancelButton } from "../shared/CancelButton";
import { ConfirmButton } from "../shared/ConfirmButton";
import { TodayButton } from "../shared/TodayButton";
import { TimePanel } from "../shared/TimePanel";
export const DateTimePicker = {
Root: DateTimePickerRoot,
Trigger,
Content,
Header,
PrevButton,
NextButton,
Title,
Grid,
Day,
TimeSection,
TimePanel,
Footer,
ClearButton,
CancelButton,
ConfirmButton,
TodayButton,
};
registry/ui-primitives/TimePicker/PeriodToggle.tsx
import { type ReactNode, forwardRef } from "react";
import { useStandaloneTimePickerContext } from "react-date-range-picker-headless";
export interface TimePickerPeriodToggleProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const TimePickerPeriodToggle = forwardRef<HTMLButtonElement, TimePickerPeriodToggleProps>(
({ className, children, cn: cnFn }, ref) => {
const { tempPeriod, handlePeriodChange, locale } = useStandaloneTimePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<button
ref={ref}
type="button"
className={cn("rdrp-time-period-button", className)}
onClick={() => handlePeriodChange(tempPeriod === "AM" ? "PM" : "AM")}
>
{children || (tempPeriod === "AM" ? locale.am : locale.pm)}
</button>
);
},
);
TimePickerPeriodToggle.displayName = "TimePickerPeriodToggle";
registry/ui-primitives/TimePicker/Root.tsx
import { mergeRefs } from "../../utils/mergeRefs";
import { forwardRef, type ReactNode } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import {
StandaloneTimePickerProvider,
type StandaloneTimePickerProviderProps,
formatBasic,
} from "react-date-range-picker-headless";
export interface TimePickerRootProps extends StandaloneTimePickerProviderProps {
className?: string;
cn?: (...args: unknown[]) => string;
}
interface InnerProps {
className?: string;
children?: ReactNode;
name?: string;
value?: Date | null;
}
const Inner = forwardRef<HTMLDivElement, InnerProps>(
({ className, children, name, value }, ref) => {
const context = usePickerContext();
return (
<div
ref={mergeRefs(ref, context.containerRef)}
className={`rdrp-root${className ? ` ${className}` : ""}`}
data-slot="root"
>
{name && (
<input type="hidden" name={name} value={value ? formatBasic(value, "HH:mm:ss") : ""} />
)}
{children}
</div>
);
},
);
Inner.displayName = "Inner";
export const TimePickerRoot = forwardRef<HTMLDivElement, TimePickerRootProps>(
({ className, cn: cnFn, children, ...options }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<StandaloneTimePickerProvider {...options}>
<Inner
ref={ref}
className={cn("rdrp-time-picker", className)}
name={options.name}
value={options.value}
>
{children}
</Inner>
</StandaloneTimePickerProvider>
);
},
);
TimePickerRoot.displayName = "TimePickerRoot";
registry/ui-primitives/TimePicker/index.ts
import { TimePickerRoot } from "./Root";
import { TimePickerPeriodToggle } from "./PeriodToggle";
import { Trigger } from "../shared/Trigger";
import { Content } from "../shared/Content";
import { TimePanel } from "../shared/TimePanel";
import { Footer } from "../shared/Footer";
import { ClearButton } from "../shared/ClearButton";
import { CancelButton } from "../shared/CancelButton";
import { ConfirmButton } from "../shared/ConfirmButton";
export const TimePicker = {
Root: TimePickerRoot,
Trigger,
Content,
TimePanel,
PeriodToggle: TimePickerPeriodToggle,
Footer,
ClearButton,
CancelButton,
ConfirmButton,
};
registry/ui-primitives/index.ts
// Utils
// Components
// Types
export type {
TriggerRenderProps,
HeaderRenderProps,
WeekdayRowRenderProps,
DayCellRenderProps,
FooterRenderProps,
TimeSectionRenderProps,
RangeTimeSectionRenderProps,
PresetsBarRenderProps,
RenderProps,
VisualProps,
FormNameProps,
RangeFormNameProps,
DatePickerProps,
DateRangePickerProps,
DateTimePickerProps,
DateRangeTimePickerProps,
CalendarGridProps,
TimePanelInternalProps,
// ClassNames types
CalendarGridClassNames,
TimePanelClassNames,
BasePickerClassNames,
DatePickerClassNames,
DateRangePickerClassNames,
DateTimePickerClassNames,
DateRangeTimePickerClassNames,
// User-facing classNames prop types
DatePickerUserClassNames,
DateRangePickerUserClassNames,
DateTimePickerUserClassNames,
DateRangeTimePickerUserClassNames,
TimePickerProps,
TimePickerUserClassNames,
TimePickerClassNames,
} from "./types";
export { DatePicker } from "./DatePicker";
export { DateRangePicker } from "./DateRangePicker";
export { DateTimePicker } from "./DateTimePicker";
export { DateRangeTimePicker } from "./DateRangeTimePicker";
export { TimePicker } from "./TimePicker";
export type { DatePickerRootProps } from "./DatePicker/Root";
export type { DateRangePickerRootProps } from "./DateRangePicker/Root";
export type { DateTimePickerRootProps } from "./DateTimePicker/Root";
export type { DateRangeTimePickerRootProps } from "./DateRangeTimePicker/Root";
export type { TimePickerRootProps } from "./TimePicker/Root";
export type { TimePickerPeriodToggleProps } from "./TimePicker/PeriodToggle";
export type { ContentProps } from "./shared/Content";
export type { HeaderProps } from "./shared/Header";
export type { NavButtonProps } from "./shared/PrevButton";
export type { TitleProps } from "./shared/Title";
export type { GridProps } from "./shared/Grid";
export type { DayProps, DayRenderProps } from "./shared/Day";
export type { FooterProps } from "./shared/Footer";
export type { ActionButtonProps } from "./shared/ClearButton";
export type { TimePanelProps } from "./shared/TimePanel";
export type { CalendarsProps } from "./DateRangePicker/Calendars";
export type { PresetsProps } from "./DateRangePicker/Presets";
export type { PresetItemProps } from "./DateRangePicker/PresetItem";
export type { TimeSectionProps as DateTimeSectionProps } from "./DateTimePicker/TimeSection";
export type { StartTimeSectionProps } from "./DateRangeTimePicker/StartTimeSection";
export type { EndTimeSectionProps } from "./DateRangeTimePicker/EndTimeSection";
export type { TriggerProps } from "./shared/Trigger";
registry/ui-primitives/types.ts
import type { CSSProperties, ReactNode } from "react";
import type {
Locale,
DayProps,
DatePickerSize,
DateRangePreset,
CaptionLayout,
CalendarMonth,
UseDatePickerOptions,
UseDateRangePickerOptions,
UseDateTimePickerOptions,
UseDateRangeTimePickerOptions,
TimeConfig,
TimePeriod,
UseStandaloneTimePickerOptions,
} from "react-date-range-picker-headless";
// ── Render prop context types ──
export interface TriggerRenderProps {
displayValue: string;
placeholder: string;
hasValue: boolean;
isOpen: boolean;
onToggle: () => void;
onClear: () => void;
locale: Locale;
/** Callback ref to attach to the trigger element for Floating UI positioning. */
triggerRef: (node: HTMLElement | null) => void;
}
export interface HeaderRenderProps {
month: Date;
formattedMonthYear: string;
onPrevMonth: () => void;
onNextMonth: () => void;
locale: Locale;
captionLayout: CaptionLayout;
years: number[];
months: number[];
onYearSelect: (year: number) => void;
onMonthSelect: (month: number) => void;
}
export interface WeekdayRowRenderProps {
weekdays: string[];
}
export interface DayCellRenderProps extends DayProps {
/** Unique id for the day cell, used by aria-activedescendant on the dialog. */
id: string;
onClick: () => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
}
export interface FooterRenderProps {
canConfirm: boolean;
hasValue: boolean;
onConfirm: () => void;
onCancel: () => void;
onClear: () => void;
locale: Locale;
}
export interface TimeSectionRenderProps {
displayValue: string;
label: string;
timeConfig: Required<TimeConfig>;
hour: number;
minute: number;
second: number;
period: TimePeriod;
}
export interface RangeTimeSectionRenderProps {
timeConfig: Required<TimeConfig>;
start: {
displayValue: string;
label: string;
hour: number;
minute: number;
second: number;
period: TimePeriod;
};
end: {
displayValue: string;
label: string;
hour: number;
minute: number;
second: number;
period: TimePeriod;
};
}
export interface PresetsBarRenderProps {
presets: DateRangePreset[];
activePresetIndex: number;
onPresetClick: (preset: DateRangePreset) => void;
}
// ── Shared visual/render props ──
export interface RenderProps {
renderTrigger?: (props: TriggerRenderProps) => ReactNode;
renderHeader?: (props: HeaderRenderProps) => ReactNode;
renderWeekdayRow?: (props: WeekdayRowRenderProps) => ReactNode;
renderDayCell?: (props: DayCellRenderProps) => ReactNode;
renderFooter?: (props: FooterRenderProps) => ReactNode;
renderTimeSection?: (props: TimeSectionRenderProps) => ReactNode;
renderPresetsBar?: (props: PresetsBarRenderProps) => ReactNode;
}
export interface VisualProps {
className?: string;
style?: CSSProperties;
inline?: boolean;
hideHeader?: boolean;
hideFooter?: boolean;
hideWeekdays?: boolean;
}
// ── Form integration props ──
export interface FormNameProps {
/** When provided, renders a hidden `<input>` with the selected value for native form submission. */
name?: string;
}
export interface RangeFormNameProps extends FormNameProps {
/** Name for the end-date hidden input. Defaults to `${name}-end` when only `name` is provided. */
endName?: string;
}
// ── User-facing classNames prop types ──
export interface DatePickerUserClassNames extends Partial<DatePickerClassNames> {
calendar?: Partial<CalendarGridClassNames>;
}
export interface DateRangePickerUserClassNames extends Partial<DateRangePickerClassNames> {
calendar?: Partial<CalendarGridClassNames>;
}
export interface DateTimePickerUserClassNames extends Partial<DateTimePickerClassNames> {
calendar?: Partial<CalendarGridClassNames>;
timePanel?: Partial<TimePanelClassNames>;
}
export interface DateRangeTimePickerUserClassNames extends Partial<DateRangeTimePickerClassNames> {
calendar?: Partial<CalendarGridClassNames>;
timePanel?: Partial<TimePanelClassNames>;
}
export interface TimePickerUserClassNames extends Partial<TimePickerClassNames> {
timePanel?: Partial<TimePanelClassNames>;
}
// ── Public component props ──
export interface DatePickerProps
extends UseDatePickerOptions, VisualProps, RenderProps, FormNameProps {
classNames?: DatePickerUserClassNames;
}
export interface DateRangePickerProps
extends UseDateRangePickerOptions, VisualProps, RenderProps, RangeFormNameProps {
classNames?: DateRangePickerUserClassNames;
}
export interface DateTimePickerProps
extends UseDateTimePickerOptions, VisualProps, RenderProps, FormNameProps {
classNames?: DateTimePickerUserClassNames;
}
export interface DateRangeTimePickerProps
extends
UseDateRangeTimePickerOptions,
VisualProps,
Omit<RenderProps, "renderTimeSection">,
RangeFormNameProps {
renderTimeSection?: (props: RangeTimeSectionRenderProps) => ReactNode;
classNames?: DateRangeTimePickerUserClassNames;
}
export interface TimePickerProps extends UseStandaloneTimePickerOptions, FormNameProps {
size?: DatePickerSize;
className?: string;
style?: CSSProperties;
inline?: boolean;
hideFooter?: boolean;
renderFooter?: (props: FooterRenderProps) => ReactNode;
classNames?: TimePickerUserClassNames;
}
// ── ClassNames types (for theme injection) ──
export interface CalendarGridClassNames {
calendarPanel?: string;
header?: string;
headerTitle?: string;
captionSelect?: string;
weekdays?: string;
weekday?: string;
grid?: string;
week?: string;
day?: string;
dayEmpty?: string;
dayToday?: string;
daySelected?: string;
dayDisabled?: string;
dayOutside?: string;
dayHighlighted?: string;
dayInRange?: string;
dayRangeStart?: string;
dayRangeEnd?: string;
dayRangeSingle?: string;
dayHoverRange?: string;
dayHoverTarget?: string;
dayFocused?: string;
}
export interface TimePanelClassNames {
timePanel?: string;
timeHighlight?: string;
timeColumns?: string;
timeColumn?: string;
timeItem?: string;
timeItemActive?: string;
timePadding?: string;
timeColon?: string;
timePeriodButton?: string;
}
/** Common classNames shared by all picker components */
export interface BasePickerClassNames {
root?: string;
content?: string;
contentInline?: string;
trigger?: string;
triggerPlaceholder?: string;
triggerClear?: string;
header?: string;
headerNavButton?: string;
headerTitle?: string;
headerSelect?: string;
headerSpacer?: string;
footer?: string;
footerButton?: string;
footerClearButton?: string;
footerConfirmButton?: string;
}
export type DatePickerClassNames = BasePickerClassNames;
export interface DateRangePickerClassNames extends BasePickerClassNames {
presetsContainer?: string;
presetButton?: string;
presetButtonActive?: string;
presetsLayout?: string;
calendarsContainer?: string;
}
export interface DateTimePickerClassNames extends BasePickerClassNames {
timeSection?: string;
timeLabel?: string;
}
export interface DateRangeTimePickerClassNames extends BasePickerClassNames {
presetsContainer?: string;
presetButton?: string;
presetButtonActive?: string;
presetsLayout?: string;
calendarsContainer?: string;
timeSection?: string;
timeLabel?: string;
}
export type TimePickerClassNames = BasePickerClassNames;
// ── Internal CalendarGrid props ──
export interface CalendarGridProps {
calendar: CalendarMonth;
locale: Locale;
focusedDate?: Date | null;
getDayProps: (date: Date, referenceMonth?: Date) => DayProps;
onDateClick: (date: Date) => void;
onDateHover?: (date: Date | null) => void;
showTitle?: boolean;
hideWeekdays?: boolean;
captionLayout?: CaptionLayout;
years?: number[];
monthOptions?: number[];
onYearSelect?: (year: number, calendarIndex?: number) => void;
onMonthSelect?: (month: number, calendarIndex?: number) => void;
calendarIndex?: number;
renderWeekdayRow?: (props: WeekdayRowRenderProps) => ReactNode;
renderDayCell?: (props: DayCellRenderProps) => ReactNode;
}
// ── Internal TimePanelInternal props ──
export interface TimePanelInternalProps {
hour: number;
minute: number;
second: number;
period: TimePeriod;
onHourChange: (h: number) => void;
onMinuteChange: (m: number) => void;
onSecondChange: (s: number) => void;
onPeriodChange: (p: TimePeriod) => void;
timeConfig: Required<TimeConfig>;
}
registry/ui-primitives/shared/CancelButton.tsx
import { forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import type { ActionButtonProps } from "./ClearButton";
export const CancelButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, children, cn: cnFn }, ref) => {
const { handleCancel, locale } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<button
ref={ref}
type="button"
className={cn("rdrp-footer-button", className)}
onClick={handleCancel}
>
{children || locale.cancel}
</button>
);
},
);
CancelButton.displayName = "CancelButton";
registry/ui-primitives/shared/ClearButton.tsx
import { type ReactNode, forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
export interface ActionButtonProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const ClearButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, children, cn: cnFn }, ref) => {
const { handleClear, locale, hasValue } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
if (!hasValue) return null;
return (
<button
ref={ref}
type="button"
className={cn("rdrp-footer-clear-button", className)}
onClick={handleClear}
>
{children || locale.clear}
</button>
);
},
);
ClearButton.displayName = "ClearButton";
registry/ui-primitives/shared/ConfirmButton.tsx
import { forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import type { ActionButtonProps } from "./ClearButton";
export const ConfirmButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, children, cn: cnFn }, ref) => {
const { handleConfirm, canConfirm, locale } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<button
ref={ref}
type="button"
className={cn("rdrp-footer-confirm-button", className)}
onClick={handleConfirm}
disabled={!canConfirm}
>
{children || locale.confirm}
</button>
);
},
);
ConfirmButton.displayName = "ConfirmButton";
registry/ui-primitives/shared/Content.tsx
import { type ReactNode, forwardRef, useRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import {
useFloating,
autoUpdate,
offset,
flip,
shift,
FloatingPortal,
FloatingFocusManager,
} from "@floating-ui/react";
import { mergeRefs } from "../../utils/mergeRefs";
export interface ContentProps {
className?: string;
inlineClassName?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const Content = forwardRef<HTMLDivElement, ContentProps>(
({ className, inlineClassName, children, cn: cnFn }, forwardedRef) => {
const context = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
const {
isOpen,
inline,
containerRef,
popupRef,
handleKeyDown,
locale,
focusedDate,
calendars,
} = context;
const localPopupRef = useRef<HTMLDivElement>(null);
const showContent = inline || isOpen;
const {
refs,
floatingStyles,
context: floatingContext,
} = useFloating({
open: !inline && isOpen,
placement: "bottom-start",
middleware: [offset(8), flip(), shift({ padding: 8 })],
whileElementsMounted: autoUpdate,
});
if (!inline && containerRef?.current && !refs.reference.current) {
refs.setPositionReference(containerRef.current);
}
if (!showContent) return null;
// Find which calendar "owns" the focused date (matching month) for unique id
let focusedCalIdx = 0;
if (focusedDate && calendars.length > 1) {
const idx = calendars.findIndex(
(cal) =>
cal.month.getFullYear() === focusedDate.getFullYear() &&
cal.month.getMonth() === focusedDate.getMonth(),
);
if (idx >= 0) focusedCalIdx = idx;
}
const activeDescendant = focusedDate
? `rdrp-day-${focusedCalIdx}-${locale.formatDate(focusedDate)}`
: undefined;
if (inline) {
return (
<div
ref={mergeRefs(forwardedRef, popupRef, localPopupRef)}
className={cn(
"rdrp-root",
"rdrp-content",
"rdrp-content-inline",
className,
inlineClassName,
)}
onKeyDown={handleKeyDown}
tabIndex={0}
role="group"
aria-label={locale.placeholder}
aria-activedescendant={activeDescendant}
>
{children}
</div>
);
}
return (
<FloatingPortal>
<FloatingFocusManager context={floatingContext} initialFocus={localPopupRef} returnFocus>
<div
ref={mergeRefs(forwardedRef, refs.setFloating, popupRef, localPopupRef)}
style={floatingStyles}
className={cn("rdrp-root", "rdrp-content", className)}
onKeyDown={handleKeyDown}
tabIndex={0}
role="dialog"
aria-label={locale.placeholder}
aria-activedescendant={activeDescendant}
>
{children}
</div>
</FloatingFocusManager>
</FloatingPortal>
);
},
);
Content.displayName = "Content";
registry/ui-primitives/shared/Day.tsx
import { type ReactNode, forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
export interface DayRenderProps {
date: Date;
day: number;
isToday: boolean;
isSelected: boolean;
isDisabled: boolean;
isInRange: boolean;
isRangeStart: boolean;
isRangeEnd: boolean;
isInHoverRange: boolean;
isHoverTarget: boolean;
hasLeftConnection: boolean;
hasRightConnection: boolean;
isConsecutiveRange: boolean;
isFocused: boolean;
dayOfWeek: number;
isOutsideDay: boolean;
isHighlighted: boolean;
isRangeSingle: boolean;
onClick: () => void;
onMouseEnter: () => void;
onMouseLeave: () => void;
}
export interface DayProps {
date: Date;
calendarIndex?: number;
className?: string;
todayClassName?: string;
selectedClassName?: string;
disabledClassName?: string;
outsideClassName?: string;
highlightedClassName?: string;
inRangeClassName?: string;
rangeStartClassName?: string;
rangeEndClassName?: string;
rangeSingleClassName?: string;
hoverRangeClassName?: string;
hoverTargetClassName?: string;
focusedClassName?: string;
emptyClassName?: string;
children?: ReactNode | ((props: DayRenderProps) => ReactNode);
cn?: (...args: unknown[]) => string;
}
export const Day = forwardRef<HTMLButtonElement, DayProps>(
(
{
date,
calendarIndex = 0,
className,
todayClassName,
selectedClassName,
disabledClassName,
outsideClassName,
highlightedClassName,
inRangeClassName,
rangeStartClassName,
rangeEndClassName,
rangeSingleClassName,
hoverRangeClassName,
hoverTargetClassName,
focusedClassName,
emptyClassName: _emptyClassName,
children,
cn: cnFn,
},
ref,
) => {
const context = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
// Pass reference month if needed for outside days computation
const props = context.getDayProps(date);
const onClick = () => context.handleDateClick(date);
const onMouseEnter = () => context.handleDateHover?.(date);
const onMouseLeave = () => context.handleDateHover?.(null);
if (typeof children === "function") {
return (
<>
{children({
...props,
onClick,
onMouseEnter,
onMouseLeave,
})}
</>
);
}
const {
day,
isToday,
isSelected,
isDisabled,
isInRange,
isRangeStart,
isRangeEnd,
isInHoverRange,
isHoverTarget,
isFocused,
isOutsideDay,
isHighlighted,
isRangeSingle,
} = props;
const showFocusRing = isFocused && !isSelected && !isRangeStart && !isRangeEnd;
return (
<button
ref={ref}
type="button"
id={`rdrp-day-${calendarIndex}-${context.locale.formatDate(date)}`}
className={cn(
"rdrp-day",
className,
isToday && "rdrp-day-today",
isToday && todayClassName,
isOutsideDay && "rdrp-day-outside",
isOutsideDay && outsideClassName,
isInHoverRange && "rdrp-day-hover-range",
isInHoverRange && hoverRangeClassName,
isHoverTarget && "rdrp-day-hover-target",
isHoverTarget && hoverTargetClassName,
isInRange && "rdrp-day-in-range",
isInRange && inRangeClassName,
isHighlighted && "rdrp-day-highlighted",
isHighlighted && highlightedClassName,
isSelected && "rdrp-day-selected",
isSelected && selectedClassName,
isRangeStart && "rdrp-day-range-start",
isRangeStart && rangeStartClassName,
isRangeEnd && "rdrp-day-range-end",
isRangeEnd && rangeEndClassName,
isRangeSingle && "rdrp-day-range-single",
isRangeSingle && rangeSingleClassName,
isDisabled && "rdrp-day-disabled",
isDisabled && disabledClassName,
showFocusRing && "rdrp-day-focused",
showFocusRing && focusedClassName,
)}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
disabled={isDisabled}
tabIndex={isFocused ? 0 : -1}
aria-label={context.locale.formatDate(date)}
aria-current={isToday ? "date" : undefined}
role="gridcell"
aria-selected={isSelected}
data-slot="day"
>
{day}
</button>
);
},
);
Day.displayName = "Day";
registry/ui-primitives/shared/Footer.tsx
import { type ReactNode, forwardRef } from "react";
export interface FooterProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const Footer = forwardRef<HTMLDivElement, FooterProps>(
({ className, children, cn: cnFn }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<div ref={ref} className={cn("rdrp-footer", className)}>
{children}
</div>
);
},
);
Footer.displayName = "Footer";
registry/ui-primitives/shared/Grid.tsx
import { type ReactNode, forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import { formatBasic } from "react-date-range-picker-headless";
import { Day } from "./Day";
export interface GridProps {
className?: string;
weekdayClassName?: string;
weekdaysClassName?: string;
weekClassName?: string;
gridClassName?: string;
children?: (props: { date: Date; calendarIndex: number }) => ReactNode;
calendarIndex?: number;
cn?: (...args: unknown[]) => string;
// Fallback for simple usage without custom day renderer
dayClassName?: string;
dayTodayClassName?: string;
daySelectedClassName?: string;
dayDisabledClassName?: string;
dayOutsideClassName?: string;
dayHighlightedClassName?: string;
dayInRangeClassName?: string;
dayRangeStartClassName?: string;
dayRangeEndClassName?: string;
dayRangeSingleClassName?: string;
dayHoverRangeClassName?: string;
dayHoverTargetClassName?: string;
dayFocusedClassName?: string;
dayEmptyClassName?: string;
}
export const Grid = forwardRef<HTMLDivElement, GridProps>(
(
{
className,
weekdayClassName,
weekdaysClassName,
weekClassName,
gridClassName,
children,
calendarIndex = 0,
cn: cnFn,
...dayClasses
},
ref,
) => {
const { calendars, locale } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
const cal = calendars[calendarIndex];
if (!cal) return null;
return (
<div ref={ref} className={cn("rdrp-grid-panel", className)}>
<div className={cn("rdrp-weekdays", weekdaysClassName)} role="row">
{locale.weekdays.map((wd) => (
<div key={wd} className={cn("rdrp-weekday", weekdayClassName)} role="columnheader">
{wd}
</div>
))}
</div>
<div className={cn("rdrp-grid", gridClassName)} role="grid">
{cal.weeks.map((week, wIdx) => (
<div key={wIdx} className={cn("rdrp-week", weekClassName)} role="row">
{week.map((date, dIdx) => {
if (!date) {
return (
<div
key={`empty-${wIdx}-${dIdx}`}
className={cn("rdrp-day-empty", dayClasses.dayEmptyClassName)}
/>
);
}
const key = formatBasic(date, "YYYY-MM-DD");
if (typeof children === "function") {
return <span key={key}>{children({ date, calendarIndex })}</span>;
}
return (
<Day
key={key}
date={date}
calendarIndex={calendarIndex}
cn={cnFn}
className={dayClasses.dayClassName}
todayClassName={dayClasses.dayTodayClassName}
selectedClassName={dayClasses.daySelectedClassName}
disabledClassName={dayClasses.dayDisabledClassName}
outsideClassName={dayClasses.dayOutsideClassName}
highlightedClassName={dayClasses.dayHighlightedClassName}
inRangeClassName={dayClasses.dayInRangeClassName}
rangeStartClassName={dayClasses.dayRangeStartClassName}
rangeEndClassName={dayClasses.dayRangeEndClassName}
rangeSingleClassName={dayClasses.dayRangeSingleClassName}
hoverRangeClassName={dayClasses.dayHoverRangeClassName}
hoverTargetClassName={dayClasses.dayHoverTargetClassName}
focusedClassName={dayClasses.dayFocusedClassName}
emptyClassName={dayClasses.dayEmptyClassName}
/>
);
})}
</div>
))}
</div>
</div>
);
},
);
Grid.displayName = "Grid";
registry/ui-primitives/shared/Header.tsx
import { type ReactNode, forwardRef } from "react";
export interface HeaderProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const Header = forwardRef<HTMLDivElement, HeaderProps>(
({ className, children, cn: cnFn }, ref) => {
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<div ref={ref} className={cn("rdrp-header", className)}>
{children}
</div>
);
},
);
Header.displayName = "Header";
registry/ui-primitives/shared/NextButton.tsx
import { forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import type { NavButtonProps } from "./PrevButton";
export const NextButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, children, cn: cnFn }, ref) => {
const { handleNextMonth, locale } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<button
ref={ref}
type="button"
className={cn("rdrp-header-nav-button", className)}
onClick={handleNextMonth}
aria-label={locale.nextMonthLabel}
>
{children || locale.nextMonth}
</button>
);
},
);
NextButton.displayName = "NextButton";
registry/ui-primitives/shared/PrevButton.tsx
import { type ReactNode, forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
export interface NavButtonProps {
className?: string;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const PrevButton = forwardRef<HTMLButtonElement, NavButtonProps>(
({ className, children, cn: cnFn }, ref) => {
const { handlePrevMonth, locale } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<button
ref={ref}
type="button"
className={cn("rdrp-header-nav-button", className)}
onClick={handlePrevMonth}
aria-label={locale.prevMonthLabel}
>
{children || locale.prevMonth}
</button>
);
},
);
PrevButton.displayName = "PrevButton";
registry/ui-primitives/shared/TimePanel.tsx
import { forwardRef, useEffect } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import { useTimePicker } from "react-date-range-picker-headless";
export interface TimePanelProps {
target?: "start" | "end" | "single";
className?: string;
highlightClassName?: string;
columnsClassName?: string;
columnClassName?: string;
itemClassName?: string;
itemActiveClassName?: string;
paddingClassName?: string;
colonClassName?: string;
periodButtonClassName?: string;
cn?: (...args: unknown[]) => string;
}
export const TimePanel = forwardRef<HTMLDivElement, TimePanelProps>(
(
{
target = "single",
className,
highlightClassName,
columnsClassName,
columnClassName,
itemClassName,
itemActiveClassName,
paddingClassName,
colonClassName,
periodButtonClassName,
cn: cnFn,
},
ref,
) => {
const context = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
let config = context.resolvedTimeConfig;
if (!config) config = {} as Required<import("react-date-range-picker-headless").TimeConfig>; // Not a time picker
let hour, minute, second, period, onHour, onMinute, onSecond, onPeriod;
if (target === "single") {
hour = context.tempHour ?? 0;
minute = context.tempMinute ?? 0;
second = context.tempSecond ?? 0;
period = context.tempPeriod ?? "AM";
onHour = context.handleHourChange!;
onMinute = context.handleMinuteChange!;
onSecond = context.handleSecondChange!;
onPeriod = context.handlePeriodChange!;
} else if (target === "start") {
hour = context.startHour ?? 0;
minute = context.startMinute ?? 0;
second = context.startSecond ?? 0;
period = context.startPeriod ?? "AM";
onHour = context.handleStartHourChange!;
onMinute = context.handleStartMinuteChange!;
onSecond = context.handleStartSecondChange!;
onPeriod = context.handleStartPeriodChange!;
} else {
hour = context.endHour ?? 0;
minute = context.endMinute ?? 0;
second = context.endSecond ?? 0;
period = context.endPeriod ?? "AM";
onHour = context.handleEndHourChange!;
onMinute = context.handleEndMinuteChange!;
onSecond = context.handleEndSecondChange!;
onPeriod = context.handleEndPeriodChange!;
}
const tp = useTimePicker({
hour,
minute,
second,
period,
onHourChange: onHour,
onMinuteChange: onMinute,
onSecondChange: onSecond,
onPeriodChange: onPeriod,
precision: config?.precision,
hourFormat: config?.hourFormat,
minuteStep: config?.minuteStep,
secondStep: config?.secondStep,
itemHeight: config?.itemHeight,
});
// Auto scroll when panel appears
useEffect(() => {
tp.scrollToValues();
}, [tp.scrollToValues]);
return (
<div ref={ref} className={cn("rdrp-time-panel", className)}>
<div className={cn("rdrp-time-highlight", highlightClassName)} />
<div className={cn("rdrp-time-columns", columnsClassName)}>
<div
ref={tp.hourListRef}
className={cn("rdrp-time-column", columnClassName)}
onScroll={tp.handleHourScroll}
tabIndex={0}
role="listbox"
aria-label={context.locale.hourLabel}
aria-orientation="vertical"
>
{tp.hours.map((val, i) => (
<div
key={`hour-${val}-${i}`}
role={val !== -1 ? "option" : "presentation"}
tabIndex={val !== -1 ? 0 : -1}
aria-selected={val !== -1 ? i === tp.hourIndex : undefined}
className={cn(
val === -1 ? "rdrp-time-padding" : "rdrp-time-item",
val === -1 ? paddingClassName : itemClassName,
i === tp.hourIndex && val !== -1 && "rdrp-time-item-active",
i === tp.hourIndex && val !== -1 && itemActiveClassName,
)}
onClick={() => {
if (val !== -1) tp.handleHourClick(val);
}}
onKeyDown={(e) => {
if (val !== -1 && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
tp.handleHourClick(val);
}
}}
>
{val !== -1 ? val.toString().padStart(2, "0") : ""}
</div>
))}
</div>
{tp.showMinutes && (
<>
<div className={cn("rdrp-time-colon", colonClassName)}>:</div>
<div
ref={tp.minuteListRef}
className={cn("rdrp-time-column", columnClassName)}
onScroll={tp.handleMinuteScroll}
tabIndex={0}
role="listbox"
aria-label={context.locale.minuteLabel}
aria-orientation="vertical"
>
{tp.minutes.map((val, i) => (
<div
key={`min-${val}-${i}`}
role={val !== -1 ? "option" : "presentation"}
tabIndex={val !== -1 ? 0 : -1}
aria-selected={val !== -1 ? i === tp.minuteIndex : undefined}
className={cn(
val === -1 ? "rdrp-time-padding" : "rdrp-time-item",
val === -1 ? paddingClassName : itemClassName,
i === tp.minuteIndex && val !== -1 && "rdrp-time-item-active",
i === tp.minuteIndex && val !== -1 && itemActiveClassName,
)}
onClick={() => {
if (val !== -1) tp.handleMinuteClick(val);
}}
onKeyDown={(e) => {
if (val !== -1 && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
tp.handleMinuteClick(val);
}
}}
>
{val !== -1 ? val.toString().padStart(2, "0") : ""}
</div>
))}
</div>
</>
)}
{tp.showSeconds && (
<>
<div className={cn("rdrp-time-colon", colonClassName)}>:</div>
<div
ref={tp.secondListRef}
className={cn("rdrp-time-column", columnClassName)}
onScroll={tp.handleSecondScroll}
tabIndex={0}
role="listbox"
aria-label={context.locale.secondLabel}
aria-orientation="vertical"
>
{tp.seconds.map((val, i) => (
<div
key={`sec-${val}-${i}`}
role={val !== -1 ? "option" : "presentation"}
tabIndex={val !== -1 ? 0 : -1}
aria-selected={val !== -1 ? i === tp.secondIndex : undefined}
className={cn(
val === -1 ? "rdrp-time-padding" : "rdrp-time-item",
val === -1 ? paddingClassName : itemClassName,
i === tp.secondIndex && val !== -1 && "rdrp-time-item-active",
i === tp.secondIndex && val !== -1 && itemActiveClassName,
)}
onClick={() => {
if (val !== -1) tp.handleSecondClick(val);
}}
onKeyDown={(e) => {
if (val !== -1 && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
tp.handleSecondClick(val);
}
}}
>
{val !== -1 ? val.toString().padStart(2, "0") : ""}
</div>
))}
</div>
</>
)}
{tp.is12Hour && (
<button
type="button"
className={cn("rdrp-time-period-button", periodButtonClassName)}
aria-label={`${tp.period === "AM" ? context.locale.am : context.locale.pm}, toggle AM/PM`}
onClick={tp.handlePeriodToggle}
>
{tp.period === "AM" ? context.locale.am : context.locale.pm}
</button>
)}
</div>
</div>
);
},
);
TimePanel.displayName = "TimePanel";
registry/ui-primitives/shared/Title.tsx
import { type ReactNode, forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
export interface TitleProps {
className?: string;
selectClassName?: string;
calendarIndex?: number;
children?: ReactNode;
cn?: (...args: unknown[]) => string;
}
export const Title = forwardRef<HTMLDivElement, TitleProps>(
({ className, selectClassName, calendarIndex = 0, children, cn: cnFn }, ref) => {
const { calendars, locale, captionLayout, years, months, handleYearSelect, handleMonthSelect } =
usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
const monthObj = calendars[calendarIndex]?.month;
if (!monthObj) return null;
if (captionLayout === "dropdown") {
return (
<div ref={ref} className={cn("rdrp-header-title", className)}>
<select
className={cn("rdrp-header-select", selectClassName)}
value={monthObj.getMonth()}
onChange={(e) => handleMonthSelect(Number(e.target.value), calendarIndex)}
aria-label={locale.selectMonthLabel}
>
{months.map((m) => (
<option key={m} value={m}>
{locale.months[m]}
</option>
))}
</select>
<select
className={cn("rdrp-header-select", selectClassName)}
value={monthObj.getFullYear()}
onChange={(e) => handleYearSelect(Number(e.target.value), calendarIndex)}
aria-label={locale.selectYearLabel}
>
{years.map((y) => (
<option key={y} value={y}>
{y}
</option>
))}
</select>
</div>
);
}
return (
<div ref={ref} className={cn("rdrp-header-title", className)}>
{children || locale.formatMonthYear(monthObj)}
</div>
);
},
);
Title.displayName = "Title";
registry/ui-primitives/shared/TodayButton.tsx
import { forwardRef } from "react";
import { usePickerContext } from "react-date-range-picker-headless";
import type { ActionButtonProps } from "./ClearButton";
export const TodayButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
({ className, children, cn: cnFn }, ref) => {
const { handleGoToToday, locale } = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
return (
<button
ref={ref}
type="button"
className={cn("rdrp-footer-button", className)}
onClick={handleGoToToday}
>
{children || locale.today}
</button>
);
},
);
TodayButton.displayName = "TodayButton";
registry/ui-primitives/shared/Trigger.tsx
import { type ReactNode, forwardRef, type MutableRefObject } from "react";
import { usePickerContext, type Locale } from "react-date-range-picker-headless";
export interface TriggerRenderProps {
displayValue: string;
placeholder: string;
hasValue: boolean;
isOpen: boolean;
onToggle: () => void;
onClear: () => void;
locale: Locale;
triggerRef: (node: HTMLElement | null) => void;
}
export interface TriggerProps {
className?: string;
placeholderClassName?: string;
clearClassName?: string;
placeholder?: string;
children?: ReactNode | ((props: TriggerRenderProps) => ReactNode);
cn?: (...args: unknown[]) => string;
}
export const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(
({ className, placeholderClassName, clearClassName, placeholder, children, cn: cnFn }, ref) => {
const context = usePickerContext();
const cn = cnFn ?? ((...args: unknown[]) => args.filter(Boolean).join(" "));
const handleClearClick = (e: React.MouseEvent) => {
e.stopPropagation();
context.handleClear();
};
const handleClearKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
context.handleClear();
}
};
if (typeof children === "function") {
return (
<>
{children({
displayValue: context.displayValue,
placeholder: placeholder || context.placeholder || context.locale.placeholder,
hasValue: context.hasValue,
isOpen: context.isOpen,
onToggle: context.handleToggle,
onClear: context.handleClear,
locale: context.locale,
triggerRef: (node) => {
if (typeof ref === "function") ref(node as unknown as HTMLButtonElement);
else if (ref && "current" in ref)
(ref as MutableRefObject<HTMLElement | null>).current = node;
},
})}
</>
);
}
return (
<button
ref={ref}
type="button"
className={cn("rdrp-trigger", className)}
onClick={context.handleToggle}
aria-expanded={context.isOpen}
aria-haspopup="dialog"
data-slot="trigger"
>
<span
className={
context.displayValue ? undefined : cn("rdrp-trigger-placeholder", placeholderClassName)
}
>
{context.displayValue || placeholder || context.placeholder || context.locale.placeholder}
</span>
{context.hasValue && !context.required && (
<span
role="button"
tabIndex={0}
className={cn("rdrp-trigger-clear", clearClassName)}
aria-label={context.locale.clear}
onClick={handleClearClick}
onKeyDown={handleClearKeyDown}
>
×
</span>
)}
</button>
);
},
);
Trigger.displayName = "Trigger";
registry/utils.ts
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
/** Merge class names with Tailwind conflict resolution. */
export function cn(...args: unknown[]): string {
return twMerge(clsx(args));
}
import type { DatePickerSize } from "react-date-range-picker-headless";
/** Return the size modifier class. Uses data-slot attributes set by ui-primitives. */
export function sizeClass(size: DatePickerSize = "medium"): string {
const sizeMap: Record<DatePickerSize, string> = {
small:
"text-[11px] [&_[data-slot=day]]:w-7 [&_[data-slot=day]]:h-7 [&_[data-slot=day]]:text-[11px] [&_[data-slot=nav-button]]:w-6 [&_[data-slot=nav-button]]:h-6 [&_[data-slot=trigger]]:min-w-[160px] [&_[data-slot=trigger]]:px-2.5 [&_[data-slot=trigger]]:py-1.5 [&_[data-slot=time-item]]:text-[11px] [&_[data-slot=time-item]]:h-7 [&_[data-slot=time-panel]]:w-[100px] [&_[data-slot=time-panel]]:h-[148px]",
medium:
"text-[13px] [&_[data-slot=day]]:w-9 [&_[data-slot=day]]:h-9 [&_[data-slot=day]]:text-[13px] [&_[data-slot=nav-button]]:w-7 [&_[data-slot=nav-button]]:h-7 [&_[data-slot=trigger]]:min-w-[200px] [&_[data-slot=trigger]]:px-3.5 [&_[data-slot=trigger]]:py-2 [&_[data-slot=time-item]]:text-[14px] [&_[data-slot=time-item]]:h-8 [&_[data-slot=time-panel]]:w-[120px] [&_[data-slot=time-panel]]:h-[176px]",
large:
"text-[14px] [&_[data-slot=day]]:w-11 [&_[data-slot=day]]:h-11 [&_[data-slot=day]]:text-[14px] [&_[data-slot=nav-button]]:w-8 [&_[data-slot=nav-button]]:h-8 [&_[data-slot=trigger]]:min-w-[240px] [&_[data-slot=trigger]]:px-4 [&_[data-slot=trigger]]:py-2.5 [&_[data-slot=time-item]]:text-[15px] [&_[data-slot=time-item]]:h-9 [&_[data-slot=time-panel]]:w-[140px] [&_[data-slot=time-panel]]:h-[196px]",
"x-large":
"text-[15px] [&_[data-slot=day]]:w-[52px] [&_[data-slot=day]]:h-[52px] [&_[data-slot=day]]:text-[15px] [&_[data-slot=nav-button]]:w-9 [&_[data-slot=nav-button]]:h-9 [&_[data-slot=trigger]]:min-w-[280px] [&_[data-slot=trigger]]:px-5 [&_[data-slot=trigger]]:py-3 [&_[data-slot=time-item]]:text-[16px] [&_[data-slot=time-item]]:h-10 [&_[data-slot=time-panel]]:w-[160px] [&_[data-slot=time-panel]]:h-[216px]",
};
return sizeMap[size] || sizeMap["medium"];
}
registry/utils/mergeRefs.ts
import type { MutableRefObject, Ref } from "react";
/** Merge multiple refs (callback or object) into a single callback ref. */
export function mergeRefs<T>(...refs: (Ref<T> | undefined)[]) {
return (node: T | null) => {
const cleanups: (() => void)[] = [];
refs.forEach((ref) => {
if (typeof ref === "function") {
const cleanup = ref(node);
if (typeof cleanup === "function") {
cleanups.push(cleanup);
}
} else if (ref) {
(ref as MutableRefObject<T | null>).current = node;
}
});
if (cleanups.length > 0) {
return () => cleanups.forEach((fn) => fn());
}
};
}