Skip to content

Commit c763558

Browse files
NicolappsConvex, Inc.
authored andcommitted
dash: update react-day-picker to v9 (#42078)
GitOrigin-RevId: 1b8c7bccdeebd91b65bfbbdf9a3e04c29d3f56db
1 parent 59e5681 commit c763558

File tree

10 files changed

+331
-138
lines changed

10 files changed

+331
-138
lines changed

npm-packages/common/config/rush/pnpm-lock.yaml

Lines changed: 194 additions & 73 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

npm-packages/dashboard-common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"monaco-editor": "0.40.0",
4242
"object-sizeof": "~2.6.3",
4343
"prettier-old-sync": "npm:[email protected]",
44-
"react-day-picker": "~8.10.1",
44+
"react-day-picker": "~9.11.1",
4545
"react-dnd": "~16.0.1",
4646
"react-dnd-scrolling": "~1.3.3",
4747
"react-dnd-html5-backend": "~16.0.1",

npm-packages/dashboard-common/src/elements/Calendar.stories.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ export const Range: Story = {
1818
},
1919
};
2020

21+
const rangeStart = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
22+
const rangeEnd = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000); // 3 days from now
2123
export const RestrictedRange: Story = {
2224
args: {
2325
mode: "single",
2426
selected: new Date(Date.now() - 24 * 60 * 60 * 1000), // Yesterday
25-
fromDate: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), // 3 days ago
26-
toDate: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), // 3 days from now
27+
startMonth: rangeStart,
28+
endMonth: rangeEnd,
29+
disabled: {
30+
before: rangeStart,
31+
after: rangeEnd,
32+
},
2733
},
2834
};
2935

npm-packages/dashboard-common/src/elements/Calendar.tsx

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,78 @@
11
"use client";
22

33
import * as React from "react";
4-
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons";
5-
import { DayPicker } from "react-day-picker";
4+
import {
5+
ChevronLeftIcon,
6+
ChevronRightIcon,
7+
ChevronUpIcon,
8+
ChevronDownIcon,
9+
} from "@radix-ui/react-icons";
10+
import { ChevronProps, ClassNames, DayPicker } from "react-day-picker";
611

712
import { cn } from "@ui/cn";
813

914
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
1015

11-
function Calendar({ ...props }: CalendarProps) {
16+
export function Calendar({ ...props }: CalendarProps) {
1217
return (
1318
<DayPicker
14-
classNames={{
15-
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
16-
month: "space-y-4",
17-
caption: "flex justify-center pt-1 relative items-center",
18-
caption_label: "text-sm font-medium",
19-
nav: "space-x-1 flex items-center",
20-
nav_button: cn(
21-
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
22-
),
23-
nav_button_previous: "absolute left-1",
24-
nav_button_next: "absolute right-1",
25-
table: "w-full border-collapse space-y-1",
26-
head_row: "flex",
27-
head_cell: "rounded-md w-8 font-normal text-[0.8rem]",
28-
row: "flex w-fit mt-2 first:ml-auto",
29-
cell: cn(
30-
"p-0 text-center text-sm focus-within:relative focus-within:z-20",
31-
),
32-
day: cn(
33-
"h-8 w-8 p-0 font-normal aria-selected:opacity-100",
34-
"hover:rounded hover:bg-background-primary",
35-
),
36-
day_selected: "bg-background-tertiary border-y",
37-
day_range_start: "rounded-l bg-background-tertiary border",
38-
day_range_end: "rounded-r bg-background-tertiary border",
39-
day_range_middle:
40-
"bg-background-tertiary/30 border-y hover:text-primary-foreground focus:bg-primary",
41-
day_today: "font-semibold",
42-
day_outside: "day-outside opacity-30 aria-selected:opacity-50",
43-
day_disabled: "opacity-60 cursor-not-allowed hover:bg-transparent",
44-
day_hidden: "invisible",
45-
}}
19+
classNames={
20+
{
21+
root: "[--calendar-header-height:--spacing(7)] relative",
22+
months: "flex flex-col sm:flex-row gap-y-4 sm:gap-x-4 sm:gap-y-0",
23+
month: "flex flex-col gap-y-3",
24+
month_caption:
25+
"flex justify-center items-center min-h-(--calendar-header-height)",
26+
caption_label: "text-sm font-medium",
27+
nav: "absolute inset-x-0 top-0 flex items-center min-h-(--calendar-header-height) justify-between px-1 pointer-events-none",
28+
button_previous: cn(
29+
"pointer-events-auto flex size-(--calendar-header-height) items-center justify-center bg-transparent p-0 opacity-50 hover:opacity-100 aria-disabled:cursor-not-allowed aria-disabled:hover:opacity-50",
30+
),
31+
button_next: cn(
32+
"pointer-events-auto flex size-(--calendar-header-height) items-center justify-center bg-transparent p-0 opacity-50 hover:opacity-100 aria-disabled:cursor-not-allowed aria-disabled:hover:opacity-50",
33+
),
34+
month_grid: "w-full border-collapse space-y-1",
35+
weekdays: "flex",
36+
weekday: "rounded-md w-8 font-normal text-[0.8rem]",
37+
week: "flex w-fit mt-2 first:ml-auto",
38+
day: cn(
39+
"size-8 p-0 text-center text-sm focus-within:relative focus-within:z-20",
40+
),
41+
day_button: cn(
42+
"size-full p-0 aria-selected:opacity-100",
43+
"hover:rounded hover:bg-background-primary",
44+
),
45+
selected: "bg-background-tertiary border-y",
46+
range_start: "rounded-l bg-background-tertiary border",
47+
range_end: "rounded-r bg-background-tertiary border",
48+
range_middle:
49+
"bg-background-tertiary/30 border-y hover:text-primary-foreground focus:bg-primary",
50+
today: "font-semibold",
51+
outside: "day-outside opacity-30 aria-selected:opacity-50",
52+
disabled:
53+
"opacity-60 [&>button]:cursor-not-allowed [&>button]:hover:bg-transparent",
54+
hidden: "invisible",
55+
} satisfies Partial<ClassNames>
56+
}
4657
components={{
47-
IconLeft: () => <ChevronLeftIcon className="h-4 w-4" />,
48-
IconRight: () => <ChevronRightIcon className="h-4 w-4" />,
58+
Chevron: function CustomChevron({ orientation }: ChevronProps) {
59+
if (orientation === "left") {
60+
return <ChevronLeftIcon className="size-4" />;
61+
}
62+
if (orientation === "right") {
63+
return <ChevronRightIcon className="size-4" />;
64+
}
65+
if (orientation === "up") {
66+
return <ChevronUpIcon className="size-4" />;
67+
}
68+
if (orientation === "down") {
69+
return <ChevronDownIcon className="size-4" />;
70+
}
71+
orientation satisfies undefined;
72+
return <span />;
73+
},
4974
}}
5075
{...props}
5176
/>
5277
);
5378
}
54-
Calendar.displayName = "Calendar";
55-
56-
export { Calendar };

npm-packages/dashboard-common/src/elements/DateRangePicker.stories.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@ import { Meta, StoryObj } from "@storybook/nextjs";
22
import { DateRangePicker } from "@common/elements/DateRangePicker";
33
import { fn } from "storybook/test";
44

5-
export const Primary: Story = {
5+
const meta = {
6+
component: DateRangePicker,
67
args: {
78
date: {
89
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
910
to: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
1011
},
1112
setDate: fn(),
1213
},
13-
};
14-
15-
const meta = { component: DateRangePicker } satisfies Meta<
16-
typeof DateRangePicker
17-
>;
14+
} satisfies Meta<typeof DateRangePicker>;
1815

1916
export default meta;
2017
type Story = StoryObj<typeof meta>;
18+
19+
export const Primary: Story = {};
20+
21+
export const RestrictedRange: Story = {
22+
args: {
23+
minDate: new Date(Date.now() - 4 * 7 * 24 * 60 * 60 * 1000),
24+
maxDate: new Date(Date.now() + 4 * 7 * 24 * 60 * 60 * 1000),
25+
},
26+
};

npm-packages/dashboard-common/src/elements/DateRangePicker.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { DateRange } from "react-day-picker";
88
import { Popover } from "@ui/Popover";
99
import { Button } from "@ui/Button";
1010
import { Calendar } from "@common/elements/Calendar";
11+
import { disabledFromRange } from "@common/features/data/components/FilterEditor/DateTimePicker";
1112

1213
export type DateRangeShortcut = {
1314
value: string;
@@ -150,10 +151,11 @@ export function DateRangePicker({
150151
</div>
151152
)}
152153
<Calendar
153-
initialFocus
154+
autoFocus
154155
mode="range"
155-
fromDate={minDate}
156-
toDate={maxDate}
156+
startMonth={minDate}
157+
endMonth={maxDate}
158+
disabled={disabledFromRange({ minDate, maxDate })}
157159
defaultMonth={from || new Date()}
158160
selected={selectedRange}
159161
onSelect={(d) => {

npm-packages/dashboard-common/src/features/data/components/FilterEditor/DateTimePicker.stories.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@ import { Meta, StoryObj } from "@storybook/nextjs";
22
import { DateTimePicker } from "@common/features/data/components/FilterEditor/DateTimePicker";
33
import { fn } from "storybook/test";
44

5-
export const Primary: Story = {
5+
const meta = {
6+
component: DateTimePicker,
67
args: {
7-
date: new Date(),
88
onChange: fn(),
9+
date: new Date(),
910
},
10-
};
11-
12-
const meta = { component: DateTimePicker } satisfies Meta<
13-
typeof DateTimePicker
14-
>;
11+
} satisfies Meta<typeof DateTimePicker>;
1512

1613
export default meta;
1714
type Story = StoryObj<typeof meta>;
15+
16+
export const Primary: Story = {};
17+
18+
export const RestrictedRange: Story = {
19+
args: {
20+
minDate: new Date(Date.now() - 4 * 7 * 24 * 60 * 60 * 1000),
21+
maxDate: new Date(Date.now() + 4 * 7 * 24 * 60 * 60 * 1000),
22+
onChange: fn(),
23+
},
24+
};

npm-packages/dashboard-common/src/features/data/components/FilterEditor/DateTimePicker.test.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ describe("DateTimePicker", () => {
8888
expect(screen.getByRole("dialog")).not.toHaveClass("hidden");
8989

9090
// Find a different date button in the calendar (e.g., day 20)
91-
// The calendar should have buttons for each day of the month
92-
const dayButton = screen.getByRole("gridcell", { name: "20" });
91+
const dayButton = screen.getByRole("button", {
92+
name: "Saturday, January 20th, 2024",
93+
});
9394

9495
// Click on the date button
9596
await user.click(dayButton);

npm-packages/dashboard-common/src/features/data/components/FilterEditor/DateTimePicker.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react";
44
import { usePopper } from "react-popper";
55
import { Calendar } from "@common/elements/Calendar";
66
import { TextInput } from "@ui/TextInput";
7+
import { Matcher } from "react-day-picker";
78

89
const dateTimeFormat = "M/d/yyyy, h:mm:ss aa";
910

@@ -117,6 +118,13 @@ export function DateTimePicker({
117118

118119
const handleTextInputBlur = () => {
119120
const parsedDate = parse(inputValue, dateTimeFormat, new Date());
121+
122+
// Don’t fire event if there was no change.
123+
const timeAtSecondPrecision = (d: Date) => Math.floor(d.getTime() / 1000);
124+
if (timeAtSecondPrecision(parsedDate) === timeAtSecondPrecision(dateTime)) {
125+
return;
126+
}
127+
120128
if (!Number.isNaN(parsedDate.getTime())) {
121129
setDateTime(parsedDate);
122130
onChange(parsedDate);
@@ -256,8 +264,9 @@ export function DateTimePicker({
256264
// Necessary so the calendar updates when changing the date via the text input.
257265
month={visibleMonth}
258266
onMonthChange={(newDate) => setVisibleMonth(newDate)}
259-
fromDate={minDate}
260-
toDate={maxDate}
267+
startMonth={minDate}
268+
endMonth={maxDate}
269+
disabled={disabledFromRange({ minDate, maxDate })}
261270
/>
262271
<input
263272
type="time"
@@ -272,3 +281,23 @@ export function DateTimePicker({
272281
</div>
273282
);
274283
}
284+
285+
// This is necessary to make the type checker happy
286+
export function disabledFromRange({
287+
minDate,
288+
maxDate,
289+
}: {
290+
minDate: Date | undefined;
291+
maxDate: Date | undefined;
292+
}): Matcher | undefined {
293+
if (minDate && maxDate) {
294+
return { before: minDate, after: maxDate };
295+
}
296+
if (minDate) {
297+
return { before: minDate };
298+
}
299+
if (maxDate) {
300+
return { after: maxDate };
301+
}
302+
return undefined;
303+
}

npm-packages/dashboard/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"next": "^14.2.33",
4646
"nprogress": "~0.2.0",
4747
"react": "^18.0.0",
48-
"react-day-picker": "~8.10.1",
48+
"react-day-picker": "~9.11.1",
4949
"react-dom": "^18.0.0",
5050
"react-popper": "^2.3.0",
5151
"react-use": "~17.6.0",
@@ -79,7 +79,6 @@
7979
"@types/node": "^18.17.0",
8080
"@types/nprogress": "~0.2.0",
8181
"@types/react": "^18.0.0",
82-
"@types/react-day-picker": "~5.3.0",
8382
"@types/react-dom": "^18.0.0",
8483
"@types/react-table": "^7.7.2",
8584
"@types/react-window": "^1.8.5",

0 commit comments

Comments
 (0)