({
value,
onChange,
now,
presets,
size = "sm",
})
| 116 | } |
| 117 | |
| 118 | export const DateRangePicker: FC<DateRangePickerProps> = ({ |
| 119 | value, |
| 120 | onChange, |
| 121 | now, |
| 122 | presets, |
| 123 | size = "sm", |
| 124 | }) => { |
| 125 | const [open, setOpen] = useState(false); |
| 126 | const currentTime = now ?? new Date(); |
| 127 | const resolvedPresets = presets ?? buildDefaultPresets(now); |
| 128 | |
| 129 | // Internal selection state kept separate from the committed value |
| 130 | // so the user can freely adjust the range before applying. This |
| 131 | // uses raw calendar dates (inclusive), not the API boundary format. |
| 132 | const [selection, setSelection] = useState<DayPickerDateRange | undefined>( |
| 133 | () => fromBoundary(value), |
| 134 | ); |
| 135 | |
| 136 | const commit = () => { |
| 137 | if (selection?.from && selection?.to) { |
| 138 | onChange(toBoundary(selection.from, selection.to, now ?? new Date())); |
| 139 | } |
| 140 | setOpen(false); |
| 141 | }; |
| 142 | |
| 143 | const handlePreset = (preset: DateRangePreset) => { |
| 144 | const { from, to } = preset.range(); |
| 145 | setSelection({ from, to }); |
| 146 | // Presets are a complete selection — commit immediately. |
| 147 | onChange(toBoundary(from, to, now ?? new Date())); |
| 148 | setOpen(false); |
| 149 | }; |
| 150 | |
| 151 | const handleCalendarSelect = (range: DayPickerDateRange | undefined) => { |
| 152 | if (!range) return; |
| 153 | setSelection(range); |
| 154 | }; |
| 155 | |
| 156 | // Sync local selection when the popover opens so it reflects the |
| 157 | // latest committed value. Reverse the boundary normalization so |
| 158 | // the calendar highlights the correct inclusive dates. |
| 159 | const handleOpenChange = (next: boolean) => { |
| 160 | if (next) { |
| 161 | setSelection(fromBoundary(value)); |
| 162 | } |
| 163 | setOpen(next); |
| 164 | }; |
| 165 | |
| 166 | // Compare in the same coordinate space (raw calendar dates) so |
| 167 | // re-selecting the identical range doesn't enable Apply. |
| 168 | const committed = fromBoundary(value); |
| 169 | const canApply = |
| 170 | selection?.from && |
| 171 | selection?.to && |
| 172 | (selection.from.getTime() !== committed.from?.getTime() || |
| 173 | selection.to.getTime() !== committed.to?.getTime()); |
| 174 | |
| 175 | return ( |
nothing calls this directly
no test coverage detected