web: fix day picker to allow selecting all months within min/max range (#9879)

* web: fix day picker to allow selecting all months within min/max range
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

* web: disable month nav buttons if they exceed min/max range in DayPicker
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
01zulfi
2026-05-22 11:07:21 +05:00
committed by GitHub
parent 91e0437554
commit 9b588c50bf

View File

@@ -16,6 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useDatePicker } from "@rehookify/datepicker";
import { Box, Button, Flex, Text } from "@theme-ui/components";
import { useState } from "react";
@@ -31,6 +32,7 @@ type DayPickerProps = {
export function DayPicker(props: DayPickerProps) {
const { selected, sx, maxDate, minDate, onSelect } = props;
const [selectedDates, onDatesChange] = useState<Date[]>([selected]);
const {
data: { weekDays, months, years, calendars },
propGetters: { dayButton, addOffset, subtractOffset, setOffset }
@@ -52,6 +54,24 @@ export function DayPicker(props: DayPickerProps) {
const { month, year, days } = calendars[0];
const currentMonthDate = months.find((m) => m.month === month)?.$date;
const isNextMonthBeyondMax =
maxDate && currentMonthDate
? new Date(
currentMonthDate.getFullYear(),
currentMonthDate.getMonth() + 1,
1
) > stripTime(maxDate)
: false;
const isPrevMonthBeforeMin =
minDate && currentMonthDate
? new Date(
currentMonthDate.getFullYear(),
currentMonthDate.getMonth(),
1
) <= stripTime(minDate)
: false;
return (
<Flex
sx={{
@@ -66,7 +86,7 @@ export function DayPicker(props: DayPickerProps) {
<Button
variant="icon"
sx={{ p: 0 }}
{...subtractOffset({ months: 1 })}
{...subtractOffset({ months: 1 }, { disabled: isPrevMonthBeforeMin })}
>
<ChevronLeft />
</Button>
@@ -83,9 +103,12 @@ export function DayPicker(props: DayPickerProps) {
value={month}
onChange={(e) => {
const selectedOption = e.target.selectedOptions[0];
setOffset(new Date(selectedOption.dataset.date || ""))?.onClick?.(
e
const d = clampDate(
new Date(selectedOption.dataset.date || ""),
minDate,
maxDate
);
setOffset(d)?.onClick?.(e as any);
}}
>
{months.map((month) => (
@@ -112,9 +135,12 @@ export function DayPicker(props: DayPickerProps) {
value={year}
onChange={(e) => {
const selectedOption = e.target.selectedOptions[0];
setOffset(new Date(selectedOption.dataset.date || ""))?.onClick?.(
e
const d = clampDate(
new Date(selectedOption.dataset.date || ""),
minDate,
maxDate
);
setOffset(d)?.onClick?.(e as any);
}}
>
{years.map((year) => (
@@ -129,7 +155,16 @@ export function DayPicker(props: DayPickerProps) {
))}
</select>
</Flex>
<Button variant="icon" sx={{ p: 0 }} {...addOffset({ months: 1 })}>
<Button
variant="icon"
sx={{ p: 0 }}
{...addOffset(
{ months: 1 },
{
disabled: isNextMonthBeyondMax
}
)}
>
<ChevronRight />
</Button>
</Flex>
@@ -195,3 +230,24 @@ export function DayPicker(props: DayPickerProps) {
</Flex>
);
}
function clampDate(d: Date, minDate?: Date, maxDate?: Date) {
/**
* Strip time for accurate comparison
*/
const normalizedMaxDate = maxDate ? stripTime(maxDate) : null;
const normalizedMinDate = minDate ? stripTime(minDate) : null;
if (normalizedMaxDate && d > normalizedMaxDate) {
return normalizedMaxDate;
}
if (normalizedMinDate && d < normalizedMinDate) {
return normalizedMinDate;
}
return d;
}
function stripTime(date: Date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}