react-day-picker 9.0.3 → 9.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/DayPicker.js +8 -2
  3. package/dist/cjs/DayPicker.js.map +1 -1
  4. package/dist/cjs/selection/useRange.js +3 -19
  5. package/dist/cjs/selection/useRange.js.map +1 -1
  6. package/dist/cjs/types/props.d.ts +4 -0
  7. package/dist/cjs/useCalendar.js +2 -2
  8. package/dist/cjs/useCalendar.js.map +1 -1
  9. package/dist/cjs/utils/addToRange.d.ts +6 -2
  10. package/dist/cjs/utils/addToRange.js +68 -24
  11. package/dist/cjs/utils/addToRange.js.map +1 -1
  12. package/dist/esm/DayPicker.js +8 -2
  13. package/dist/esm/DayPicker.js.map +1 -1
  14. package/dist/esm/selection/useRange.js +3 -19
  15. package/dist/esm/selection/useRange.js.map +1 -1
  16. package/dist/esm/types/props.d.ts +4 -0
  17. package/dist/esm/useCalendar.js +2 -2
  18. package/dist/esm/useCalendar.js.map +1 -1
  19. package/dist/esm/utils/addToRange.d.ts +6 -2
  20. package/dist/esm/utils/addToRange.js +68 -24
  21. package/dist/esm/utils/addToRange.js.map +1 -1
  22. package/examples/CustomDayButton.test.tsx +26 -0
  23. package/examples/CustomDayButton.tsx +21 -10
  24. package/examples/MultipleRequired.tsx +7 -0
  25. package/examples/RangeMinMax.test.tsx +5 -16
  26. package/examples/RangeMinMax.tsx +5 -6
  27. package/examples/RangeRequired.tsx +30 -0
  28. package/examples/RangeShiftKey.tsx +4 -6
  29. package/examples/StartEndMonths.test.tsx +30 -0
  30. package/examples/index.ts +2 -0
  31. package/package.json +3 -3
  32. package/src/DayPicker.test.tsx +25 -2
  33. package/src/DayPicker.tsx +24 -0
  34. package/src/selection/useRange.test.tsx +2 -2
  35. package/src/selection/useRange.tsx +3 -25
  36. package/src/style.css +15 -9
  37. package/src/style.module.css +15 -9
  38. package/src/types/props.ts +4 -0
  39. package/src/useCalendar.ts +7 -2
  40. package/src/utils/addToRange.test.ts +95 -97
  41. package/src/utils/addToRange.ts +64 -28
  42. package/website/docs/docs/selection-modes.mdx +51 -75
  43. package/website/docs/guides/custom-components.mdx +65 -88
  44. package/website/docs/intro.mdx +1 -1
  45. package/website/docs/upgrading.mdx +4 -9
  46. package/examples/CustomDayDate.test.tsx +0 -16
@@ -7,41 +7,85 @@ import { dateLib as defaultDateLib } from "../lib/index.js";
7
7
  *
8
8
  * @group Utilities
9
9
  */
10
- export function addToRange(date, range,
10
+ export function addToRange(
11
+ /** The date to add to the range. */
12
+ date,
13
+ /** The range where to add `date`. */
14
+ initialRange, min = 0, max = 0, required = false,
11
15
  /** @ignore */
12
16
  dateLib = defaultDateLib) {
13
- const { from, to } = range || {};
17
+ const { from, to } = initialRange || {};
14
18
  const { isSameDay, isAfter, isBefore } = dateLib;
15
- if (from && to) {
16
- if (isSameDay(to, date) && isSameDay(from, date)) {
17
- return { from: undefined, to: undefined };
18
- }
19
- if (isSameDay(to, date)) {
20
- return { from: to, to: undefined };
21
- }
19
+ let range;
20
+ if (!from && !to) {
21
+ // the range is empty, add the date
22
+ range = { from: date, to: min > 0 ? undefined : date };
23
+ }
24
+ else if (from && !to) {
25
+ // adding date to an incomplete range
22
26
  if (isSameDay(from, date)) {
23
- return { from: undefined, to: undefined };
27
+ // adding a date equal to the start of the range
28
+ if (required) {
29
+ range = { from, to: undefined };
30
+ }
31
+ else {
32
+ range = undefined;
33
+ }
24
34
  }
25
- if (isAfter(from, date)) {
26
- return { from: date, to };
35
+ else if (isBefore(date, from)) {
36
+ // adding a date before the start of the range
37
+ range = { from: date, to: from };
38
+ }
39
+ else {
40
+ // adding a date after the start of the range
41
+ range = { from, to: date };
27
42
  }
28
- return { from, to: date };
29
43
  }
30
- if (to) {
31
- if (isAfter(date, to)) {
32
- return { from: to, to: date };
44
+ else if (from && to) {
45
+ // adding date to a complete range
46
+ if (isSameDay(from, date) && isSameDay(to, date)) {
47
+ // adding a date that is equal to both start and end of the range
48
+ if (required) {
49
+ range = { from, to };
50
+ }
51
+ else {
52
+ range = undefined;
53
+ }
54
+ }
55
+ else if (isSameDay(from, date)) {
56
+ // adding a date equal to the the start of the range
57
+ range = { from, to: min > 0 ? undefined : date };
58
+ }
59
+ else if (isSameDay(to, date)) {
60
+ // adding a dare equal to the end of the range
61
+ range = { from: date, to: min > 0 ? undefined : date };
62
+ }
63
+ else if (isBefore(date, from)) {
64
+ // adding a date before the start of the range
65
+ range = { from: date, to: to };
66
+ }
67
+ else if (isAfter(date, from)) {
68
+ // adding a date after the start of the range
69
+ range = { from, to: date };
70
+ }
71
+ else if (isAfter(date, to)) {
72
+ // adding a date after the end of the range
73
+ range = { from, to: date };
74
+ }
75
+ else {
76
+ throw new Error("Invalid range");
33
77
  }
34
- return { from: date, to };
35
78
  }
36
- if (from) {
37
- if (isBefore(date, from)) {
38
- return { from: date, to: from };
79
+ // check for min / max
80
+ if (range?.from && range?.to) {
81
+ const diff = dateLib.differenceInCalendarDays(range.to, range.from);
82
+ if (max > 0 && diff > max) {
83
+ range = { from: date, to: undefined };
39
84
  }
40
- if (isSameDay(date, from)) {
41
- return { from: undefined, to: undefined };
85
+ else if (min > 1 && diff < min) {
86
+ range = { from: date, to: undefined };
42
87
  }
43
- return { from, to: date };
44
88
  }
45
- return { from: date, to: undefined };
89
+ return range;
46
90
  }
47
91
  //# sourceMappingURL=addToRange.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"addToRange.js","sourceRoot":"","sources":["../../../src/utils/addToRange.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG5D;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,IAAU,EACV,KAA4B;AAC5B,cAAc;AACd,UAAmB,cAAc;IAEjC,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,CAAC;IACjC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IACjD,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QACf,IAAI,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACjD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;QAC5C,CAAC;QACD,IAAI,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;YACxB,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;QACrC,CAAC;QACD,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;QAC5C,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACxB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAC5B,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IACD,IAAI,EAAE,EAAE,CAAC;QACP,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QAChC,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAC5B,CAAC;IACD,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QAClC,CAAC;QACD,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"addToRange.js","sourceRoot":"","sources":["../../../src/utils/addToRange.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+
3
+ import { startOfMonth } from "date-fns";
4
+
5
+ import { dateButton } from "@/test/elements";
6
+ import { render, screen, fireEvent } from "@/test/render";
7
+
8
+ import { CustomDayButton } from "./CustomDayButton";
9
+
10
+ const today = new Date(2021, 10, 25);
11
+ beforeAll(() => jest.setSystemTime(today));
12
+ afterAll(() => jest.useRealTimers());
13
+
14
+ beforeEach(() => {
15
+ render(<CustomDayButton />);
16
+ });
17
+
18
+ test("update the footer when a day is double clicked", () => {
19
+ fireEvent.doubleClick(dateButton(today));
20
+ expect(screen.getByText(today.toDateString())).toBeInTheDocument();
21
+ });
22
+
23
+ test("update the footer when a day is single clicked", () => {
24
+ fireEvent.click(dateButton(startOfMonth(today)));
25
+ expect(screen.getByText("Double click to select a date")).toBeInTheDocument();
26
+ });
@@ -1,16 +1,27 @@
1
1
  import React from "react";
2
2
 
3
- import { DayPicker, type DayButtonProps } from "react-day-picker";
3
+ import { DayPicker } from "react-day-picker";
4
4
 
5
- function HighlightDay(props: DayButtonProps) {
6
- const { day, modifiers, ...buttonProps } = props;
5
+ export function CustomDayButton() {
6
+ const [selected, setSelected] = React.useState<Date>();
7
7
  return (
8
- <button {...buttonProps} style={{ whiteSpace: "nowrap" }}>
9
- {props.day.date.getDate() === 19 ? `🎉` : props.children}
10
- </button>
8
+ <DayPicker
9
+ mode="single"
10
+ onSelect={setSelected}
11
+ selected={selected}
12
+ components={{
13
+ DayButton: (props) => {
14
+ const { day, modifiers, ...buttonProps } = props;
15
+ return (
16
+ <button
17
+ {...buttonProps}
18
+ onDoubleClick={() => setSelected(day.date)}
19
+ onClick={() => setSelected(undefined)}
20
+ />
21
+ );
22
+ }
23
+ }}
24
+ footer={selected?.toDateString() || "Double click to select a date"}
25
+ />
11
26
  );
12
27
  }
13
-
14
- export function CustomDayButton() {
15
- return <DayPicker mode="single" components={{ DayButton: HighlightDay }} />;
16
- }
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+
3
+ import { DayPicker } from "react-day-picker";
4
+
5
+ export function MultipleRequired() {
6
+ return <DayPicker mode="multiple" required selected={[new Date()]} />;
7
+ }
@@ -8,6 +8,11 @@ import { user } from "@/test/user";
8
8
 
9
9
  import { RangeMinMax } from "./RangeMinMax";
10
10
 
11
+ const today = new Date(2022, 8, 12);
12
+
13
+ beforeAll(() => jest.setSystemTime(today));
14
+ afterAll(() => jest.useRealTimers());
15
+
11
16
  beforeEach(() => render(<RangeMinMax />));
12
17
 
13
18
  describe("when a day is clicked", () => {
@@ -18,22 +23,6 @@ describe("when a day is clicked", () => {
18
23
  test("should be selected", () => {
19
24
  expect(gridcell(firstDay, true)).toHaveAttribute("aria-selected", "true");
20
25
  });
21
- describe("when the day before min is clicked", () => {
22
- const dayAfter = addDays(firstDay, 1);
23
- beforeEach(async () => {
24
- await user.click(dateButton(dayAfter));
25
- });
26
- test("the first day should not be selected", () => {
27
- expect(gridcell(firstDay, true)).not.toHaveAttribute(
28
- "aria-selected",
29
- "true"
30
- );
31
- });
32
- test("the day after should be selected", () => {
33
- expect(gridcell(dayAfter, true)).toHaveAttribute("aria-selected", "true");
34
- });
35
- });
36
-
37
26
  describe("when the day after min is clicked", () => {
38
27
  const dayAfter = addDays(firstDay, 4);
39
28
  beforeEach(async () => {
@@ -9,20 +9,19 @@ export function RangeMinMax() {
9
9
  let footer = `Please pick the first day.`;
10
10
  if (range?.from) {
11
11
  if (!range.to) {
12
- footer = format(range.from, "PPP");
12
+ footer = `${format(range.from, "PPP")}—`;
13
13
  } else if (range.to) {
14
- footer = `${format(range.from, "PPP")}–${format(range.to, "PPP")}`;
14
+ footer = `${format(range.from, "PPP")}—${format(range.to, "PPP")}`;
15
15
  }
16
16
  }
17
17
 
18
18
  return (
19
19
  <div>
20
- <p>Select a range between 3 and 9 days.</p>
20
+ <p>Select up to 6 nights.</p>
21
21
  <DayPicker
22
- defaultMonth={new Date(2022, 8)}
23
22
  mode="range"
24
- min={3}
25
- max={9}
23
+ min={1}
24
+ max={6}
26
25
  selected={range}
27
26
  onSelect={setRange}
28
27
  footer={footer}
@@ -0,0 +1,30 @@
1
+ import React, { useState } from "react";
2
+
3
+ import { addDays, format, startOfMonth } from "date-fns";
4
+ import { DateRange, DayPicker } from "react-day-picker";
5
+
6
+ export function RangeRequired() {
7
+ const [range, setRange] = useState<DateRange>({
8
+ from: startOfMonth(new Date()),
9
+ to: addDays(startOfMonth(new Date()), 4)
10
+ });
11
+
12
+ let footer = `Please pick the first day.`;
13
+ if (range?.from) {
14
+ if (!range.to) {
15
+ footer = `${format(range.from, "PPP")}—`;
16
+ } else if (range.to) {
17
+ footer = `${format(range.from, "PPP")}—${format(range.to, "PPP")}`;
18
+ }
19
+ }
20
+
21
+ return (
22
+ <DayPicker
23
+ mode="range"
24
+ required
25
+ selected={range}
26
+ onSelect={setRange}
27
+ footer={footer}
28
+ />
29
+ );
30
+ }
@@ -12,12 +12,10 @@ function DayWithShiftKey(props: DayButtonProps) {
12
12
  const { selected } = useDayPicker({ mode: "range" });
13
13
 
14
14
  const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
15
- if (
16
- selected?.from &&
17
- !selected.to &&
18
- !isSameDay(props.day.date, selected.from) &&
19
- !e.shiftKey
20
- ) {
15
+ const requireShiftKey =
16
+ selected?.from && !isSameDay(props.day.date, selected.from);
17
+
18
+ if (!e.shiftKey && requireShiftKey) {
21
19
  return;
22
20
  }
23
21
  props.onClick?.(e);
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+
3
+ import { grid, gridcell, nextButton } from "@/test/elements";
4
+ import { render } from "@/test/render";
5
+ import { user } from "@/test/user";
6
+
7
+ import { StartEndMonths } from "./StartEndMonths";
8
+
9
+ beforeEach(() => {
10
+ render(<StartEndMonths />);
11
+ });
12
+
13
+ test("the first month should be January 2024", () => {
14
+ expect(grid("January 2024")).toBeInTheDocument();
15
+ expect(gridcell(new Date(2024, 0, 31))).toBeInTheDocument();
16
+ });
17
+
18
+ describe("when navigating to the last month", () => {
19
+ beforeEach(async () => {
20
+ // click next button 24 times
21
+ for (let i = 0; i < 24; i++) {
22
+ await user.click(nextButton());
23
+ }
24
+ });
25
+
26
+ test("the last month should be December 2025", () => {
27
+ expect(grid("December 2025")).toBeInTheDocument();
28
+ expect(gridcell(new Date(2025, 11, 31))).toBeInTheDocument();
29
+ });
30
+ });
package/examples/index.ts CHANGED
@@ -38,6 +38,7 @@ export * from "./ModifiersStyle";
38
38
  export * from "./ModifiersToday";
39
39
  export * from "./Multiple";
40
40
  export * from "./MultipleMinMax";
41
+ export * from "./MultipleRequired";
41
42
  export * from "./MultipleMonths";
42
43
  export * from "./MultipleMonthsPaged";
43
44
  export * from "./NumberingSystem";
@@ -45,6 +46,7 @@ export * from "./OutsideDays";
45
46
  export * from "./Range";
46
47
  export * from "./RangeExcludeDisabled";
47
48
  export * from "./RangeMinMax";
49
+ export * from "./RangeRequired";
48
50
  export * from "./RangeShiftKey";
49
51
  export * from "./Rtl";
50
52
  export * from "./Single";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-day-picker",
3
- "version": "9.0.3",
3
+ "version": "9.0.5",
4
4
  "description": "Customizable Date Picker for React",
5
5
  "author": "Giampaolo Bellavite <io@gpbl.dev>",
6
6
  "homepage": "https://daypicker.dev",
@@ -144,7 +144,7 @@
144
144
  "@types/node": "^20.14.10",
145
145
  "@types/react": "^18.3.3",
146
146
  "@types/react-dom": "^18.3.0",
147
- "@typescript-eslint/eslint-plugin": "^7.16.1",
147
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
148
148
  "@typescript-eslint/parser": "^7.16.0",
149
149
  "date-fns": "^3.6.0",
150
150
  "date-fns-jalali": "3.6.0-1",
@@ -169,7 +169,7 @@
169
169
  "ts-jest": "^29.2.3",
170
170
  "ts-node": "^10.9.2",
171
171
  "tslib": "^2.6.3",
172
- "typescript": "~5.5.3",
172
+ "typescript": "~5.5.4",
173
173
  "typescript-css-modules": "^1.0.4"
174
174
  },
175
175
  "peerDependencies": {
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
- import { render, screen } from "@testing-library/react";
4
- import { startOfMonth } from "date-fns";
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { startOfDay, startOfMonth } from "date-fns";
5
5
 
6
6
  import {
7
7
  activeElement,
@@ -108,3 +108,26 @@ describe("when the grid is focused", () => {
108
108
  });
109
109
  });
110
110
  });
111
+
112
+ describe("when a day is mouse entered", () => {
113
+ const handleDayMouseEnter = jest.fn();
114
+ const handleDayMouseLeave = jest.fn();
115
+ const today = startOfDay(new Date());
116
+ beforeEach(async () => {
117
+ render(
118
+ <DayPicker
119
+ today={today}
120
+ defaultMonth={today}
121
+ mode="single"
122
+ onDayMouseEnter={handleDayMouseEnter}
123
+ onDayMouseLeave={handleDayMouseLeave}
124
+ />
125
+ );
126
+ fireEvent.mouseEnter(dateButton(today));
127
+ fireEvent.mouseLeave(dateButton(today));
128
+ });
129
+ test("should call the event handler", async () => {
130
+ expect(handleDayMouseEnter).toHaveBeenCalled();
131
+ expect(handleDayMouseLeave).toHaveBeenCalled();
132
+ });
133
+ });
package/src/DayPicker.tsx CHANGED
@@ -69,6 +69,8 @@ export function DayPicker(props: DayPickerProps) {
69
69
  onDayClick,
70
70
  onDayFocus,
71
71
  onDayKeyDown,
72
+ onDayMouseEnter,
73
+ onDayMouseLeave,
72
74
  onNextClick,
73
75
  onPrevClick,
74
76
  showWeekNumber,
@@ -209,6 +211,20 @@ export function DayPicker(props: DayPickerProps) {
209
211
  [moveFocus, onDayKeyDown, props.dir]
210
212
  );
211
213
 
214
+ const handleDayMouseEnter = useCallback(
215
+ (day: CalendarDay, modifiers: Modifiers) => (e: MouseEvent) => {
216
+ onDayMouseEnter?.(day.date, modifiers, e);
217
+ },
218
+ [onDayMouseEnter]
219
+ );
220
+
221
+ const handleDayMouseLeave = useCallback(
222
+ (day: CalendarDay, modifiers: Modifiers) => (e: MouseEvent) => {
223
+ onDayMouseLeave?.(day.date, modifiers, e);
224
+ },
225
+ [onDayMouseLeave]
226
+ );
227
+
212
228
  const { className, style } = useMemo(
213
229
  () => ({
214
230
  className: [classNames[UI.Root], props.className]
@@ -552,6 +568,14 @@ export function DayPicker(props: DayPickerProps) {
552
568
  onBlur={handleDayBlur(day, modifiers)}
553
569
  onFocus={handleDayFocus(day, modifiers)}
554
570
  onKeyDown={handleDayKeyDown(day, modifiers)}
571
+ onMouseEnter={handleDayMouseEnter(
572
+ day,
573
+ modifiers
574
+ )}
575
+ onMouseLeave={handleDayMouseLeave(
576
+ day,
577
+ modifiers
578
+ )}
555
579
  >
556
580
  {formatDay(date, formatOptions, dateLib)}
557
581
  </components.DayButton>
@@ -90,7 +90,7 @@ describe("useRange", () => {
90
90
 
91
91
  expect(result.current.selected).toEqual({
92
92
  from: new Date(2023, 6, 10),
93
- to: undefined
93
+ to: new Date(2023, 6, 10)
94
94
  });
95
95
  });
96
96
 
@@ -135,7 +135,7 @@ describe("useRange", () => {
135
135
 
136
136
  expect(result.current.selected).toEqual({
137
137
  from: new Date(2023, 6, 10),
138
- to: undefined
138
+ to: new Date(2023, 6, 10)
139
139
  });
140
140
  });
141
141
  });
@@ -24,7 +24,6 @@ export function useRange<T extends DayPickerProps>(
24
24
  onSelect
25
25
  } = props as PropsRange;
26
26
 
27
- const { differenceInCalendarDays } = dateLib;
28
27
  const [selected, setSelected] = React.useState<DateRange | undefined>(
29
28
  initiallySelected
30
29
  );
@@ -49,32 +48,10 @@ export function useRange<T extends DayPickerProps>(
49
48
  modifiers: Modifiers,
50
49
  e: React.MouseEvent | React.KeyboardEvent
51
50
  ) => {
51
+ const { min, max } = props as PropsRange;
52
52
  const newRange = triggerDate
53
- ? addToRange(triggerDate, selected, dateLib)
53
+ ? addToRange(triggerDate, selected, min, max, required, dateLib)
54
54
  : undefined;
55
- const { min, max } = props as PropsRange;
56
-
57
- if (min) {
58
- if (
59
- newRange?.from &&
60
- newRange.to &&
61
- differenceInCalendarDays(newRange.to, newRange.from) < min - 1
62
- ) {
63
- newRange.from = triggerDate;
64
- newRange.to = undefined;
65
- }
66
- }
67
-
68
- if (max) {
69
- if (
70
- newRange?.from &&
71
- newRange.to &&
72
- differenceInCalendarDays(newRange.to, newRange.from) >= max
73
- ) {
74
- newRange.from = triggerDate;
75
- newRange.to = undefined;
76
- }
77
- }
78
55
 
79
56
  if (newRange?.from && newRange.to) {
80
57
  let newDate = newRange.from;
@@ -85,6 +62,7 @@ export function useRange<T extends DayPickerProps>(
85
62
  disabled &&
86
63
  dateMatchModifiers(newDate, disabled, dateLib)
87
64
  ) {
65
+ // if a disabled days is found, the range is reset
88
66
  newRange.from = triggerDate;
89
67
  newRange.to = undefined;
90
68
  break;
package/src/style.css CHANGED
@@ -49,7 +49,8 @@
49
49
  --rdp-week_number-height: var(--rdp-day-height); /* The height of the week number cells. */
50
50
  --rdp-week_number-opacity: 0.75; /* The opacity of the week number. */
51
51
  --rdp-week_number-width: var(--rdp-day-width); /* The width of the week number cells. */
52
-
52
+ --rdp-weeknumber-text-align: center; /* The text alignment of the weekday cells. */
53
+
53
54
  --rdp-weekday-font: 500 smaller var(--rdp-font-family); /* The font of the weekday. */
54
55
  --rdp-weekday-opacity: 0.75; /* The opacity of the weekday. */
55
56
  --rdp-weekday-padding: 0.5rem 0rem; /* The padding of the weekday. */
@@ -87,6 +88,7 @@
87
88
  width: var(--rdp-day-width);
88
89
  height: var(--rdp-day-height);
89
90
  font: var(--rdp-day-font);
91
+ text-align: center;
90
92
  }
91
93
 
92
94
  .rdp-day_button {
@@ -164,9 +166,10 @@
164
166
  gap: var(--rdp-dropdown-gap);
165
167
  }
166
168
  .rdp-dropdown {
167
- z-index: 2; /* Remove ? */
169
+ z-index: 2;
168
170
 
169
171
  /* Reset */
172
+ opacity: 0;
170
173
  appearance: none;
171
174
  position: absolute;
172
175
  inset-block-start: 0;
@@ -176,9 +179,7 @@
176
179
  margin: 0;
177
180
  padding: 0;
178
181
  cursor: inherit;
179
- color: transparent;
180
182
  border: none;
181
- background-color: transparent;
182
183
  line-height: inherit;
183
184
  }
184
185
 
@@ -207,6 +208,10 @@
207
208
  max-width: fit-content;
208
209
  }
209
210
 
211
+ .rdp-month_grid {
212
+ border-collapse: collapse;
213
+ }
214
+
210
215
  .rdp-nav {
211
216
  position: absolute;
212
217
  inset-block-start: 0;
@@ -233,10 +238,7 @@
233
238
  width: var(--rdp-week_number-width);
234
239
  border: var(--rdp-week_number-border);
235
240
  border-radius: var(--rdp-week_number-border-radius);
236
- }
237
-
238
- .rdp-week_number_interactive {
239
- cursor: pointer;
241
+ text-align: var(--rdp-weeknumber-text-align);
240
242
  }
241
243
 
242
244
  /* DAY MODIFIERS */
@@ -262,7 +264,7 @@
262
264
 
263
265
  .rdp-hidden {
264
266
  visibility: hidden;
265
- color: var(end-range_start-color);
267
+ color: var(--rdp-range_start-color);
266
268
  }
267
269
 
268
270
  .rdp-range_start {
@@ -296,6 +298,10 @@
296
298
  background-color: var(--rdp-range_end-date-background-color);
297
299
  }
298
300
 
301
+ .rdp-range_start.rdp-range_end {
302
+ background: revert;
303
+ }
304
+
299
305
  .rdp-focusable {
300
306
  cursor: pointer;
301
307
  }