Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[datetime2] feat: DateRangePicker3 using react-day-picker v8 #6375

Merged
merged 36 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
48825e3
[datetime2] feat: DateRangePicker3 (initial commit)
adidahiya Sep 7, 2023
28bdeb2
load date-fns locale
adidahiya Sep 7, 2023
05209bc
render DRP3 using range DayPicker
adidahiya Sep 7, 2023
5007262
DRP3 contiguous multi-month working
adidahiya Sep 8, 2023
733b79b
add locale select to DRP3 example
adidahiya Sep 8, 2023
f9f30fb
fix layout of caption dropdowns & buttons
adidahiya Sep 8, 2023
a9430bd
progress on non-contiguous range picker
adidahiya Sep 8, 2023
002fdff
Merge remote-tracking branch 'origin/develop' into ad/date-range-picker3
adidahiya Sep 11, 2023
560cc19
use date-fns instead of moment in docs example
adidahiya Sep 11, 2023
22c6f44
display selected range in example
adidahiya Sep 11, 2023
979ddf3
fix lint
adidahiya Sep 11, 2023
fc76dc3
fix non-contiguous calendar nav button handlers
adidahiya Sep 11, 2023
84ee75d
remove unused code; destructure props
adidahiya Sep 11, 2023
52e8bb6
ContiguousDatePicker component
adidahiya Sep 11, 2023
d3f0ec6
move onSelect handler to contiguousDayRangePicker
adidahiya Sep 11, 2023
3e875a4
react to shortcuts correctly
adidahiya Sep 11, 2023
38bfd6b
minor cleanup, refactor
adidahiya Sep 11, 2023
c8f79bc
implement contiguous reverseMonthAndYearMenus
adidahiya Sep 11, 2023
ad71d07
export symbols in datetime public API for use in datetime2
adidahiya Sep 11, 2023
273bbc9
address self-review
adidahiya Sep 12, 2023
879c327
minor cleanup
adidahiya Sep 12, 2023
546f375
remove extraneous unused caption components
adidahiya Sep 12, 2023
8fefef6
fix non-contiguous month bounds
adidahiya Sep 12, 2023
4f4b5e7
fix eslint
adidahiya Sep 12, 2023
1f841eb
fix stylelint
adidahiya Sep 12, 2023
15cc534
fix recursive update loop
adidahiya Sep 12, 2023
4c2274a
port failing test suite
adidahiya Sep 12, 2023
ae30369
adjust some classes, fix some tests
adidahiya Sep 12, 2023
387e720
fix lint, apply custom class names to both kinds of range pickers
adidahiya Sep 13, 2023
45a8623
fix test:iso
adidahiya Sep 13, 2023
aeba9e5
[datetime] fix tests
adidahiya Sep 13, 2023
b9dbe65
[datetime2] clean up test console output by faking loadDateFnsLocale …
adidahiya Sep 13, 2023
5bed963
fix more tests
adidahiya Sep 13, 2023
99c2cfb
fix remaining tests
adidahiya Sep 13, 2023
3d9e27f
skip locale utils in coverage
adidahiya Sep 13, 2023
3d86143
fix lint
adidahiya Sep 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/datetime/src/common/dateRangeSelectionStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface DateRangeSelectionState {
/**
* The boundary that would be modified by clicking the provided `day`.
*/
boundary?: Boundary;
boundary: Boundary;

/**
* The date range that would be selected after clicking the provided `day`.
Expand Down Expand Up @@ -124,11 +124,13 @@ export class DateRangeSelectionStrategy {
const [start, end] = selectedRange;

let nextDateRange: DateRange;
let nextBoundary: Boundary = Boundary.START;

if (start == null && end == null) {
nextDateRange = [day, null];
} else if (start != null && end == null) {
nextDateRange = this.createRange(day, start, allowSingleDayRange);
nextBoundary = Boundary.END;
} else if (start == null && end != null) {
nextDateRange = this.createRange(day, end, allowSingleDayRange);
} else {
Expand All @@ -140,12 +142,13 @@ export class DateRangeSelectionStrategy {
nextDateRange = [null, end];
} else if (isEnd) {
nextDateRange = [start, null];
nextBoundary = Boundary.END;
} else {
nextDateRange = [day, null];
}
}

return { dateRange: nextDateRange };
return { dateRange: nextDateRange, boundary: nextBoundary };
}

private static getOtherBoundary(boundary: Boundary) {
Expand Down
4 changes: 3 additions & 1 deletion packages/datetime/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

import * as Classes from "./classes";
import * as DateUtils from "./dateUtils";
import * as Errors from "./errors";
import * as TimezoneNameUtils from "./timezoneNameUtils";
import type { TimezoneWithNames } from "./timezoneTypes";
import * as TimezoneUtils from "./timezoneUtils";

export { Classes, DateUtils, TimezoneUtils, TimezoneWithNames };
export { Classes, DateUtils, Errors, TimezoneNameUtils, TimezoneUtils, TimezoneWithNames };

export { DatePickerBaseProps, DatePickerModifiers } from "./datePickerBaseProps";
export { DateFormatProps } from "./dateFormatProps";
Expand Down
2 changes: 1 addition & 1 deletion packages/datetime/src/common/monthAndYear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { getDateNextMonth, getDatePreviousMonth } from "./dateUtils";

export class MonthAndYear {
public static fromDate(date: Date) {
public static fromDate(date: Date | null) {
return date == null ? undefined : new MonthAndYear(date.getMonth(), date.getFullYear());
}

Expand Down
2 changes: 1 addition & 1 deletion packages/datetime/src/common/timezoneUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { getCurrentTimezone } from "./getTimezone";
import { TimePrecision } from "./timePickerProps";
import { UTC_TIME } from "./timezoneItems";

export { getCurrentTimezone };
export { getCurrentTimezone, UTC_TIME };

const NO_TIME_PRECISION = "date";

Expand Down
4 changes: 2 additions & 2 deletions packages/datetime/src/components/date-picker/datePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { AbstractPureComponent, Button, DISPLAYNAME_PREFIX, Divider, Props } fro

import { Classes, DatePickerBaseProps, DateUtils } from "../../common";
import * as Errors from "../../common/errors";
import { DatePickerShortcut, DateRangeShortcut, Shortcuts } from "../shortcuts/shortcuts";
import { DatePickerShortcut, DatePickerShortcutMenu, DateRangeShortcut } from "../shortcuts/shortcuts";
import { TimePicker } from "../time-picker/timePicker";
import { DatePickerCaption } from "./datePickerCaption";
import { getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";
Expand Down Expand Up @@ -325,7 +325,7 @@ export class DatePicker extends AbstractPureComponent<DatePickerProps, DatePicke
dateRange: [shortcut.date, undefined],
}));
return [
<Shortcuts
<DatePickerShortcutMenu
key="shortcuts"
{...{
allowSingleDayRange: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { getFormattedDateString } from "../../common/dateFormatProps";
import { measureTextWidth } from "../../common/utils";
import { getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";

/**
Expand All @@ -25,4 +26,5 @@ export const DatePickerUtils = {
getDefaultMaxDate,
getDefaultMinDate,
getFormattedDateString,
measureTextWidth,
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
SELECTED_RANGE_MODIFIER,
} from "../date-picker/datePickerCore";
import { DatePickerNavbar } from "../date-picker/datePickerNavbar";
import { DateRangeShortcut, Shortcuts } from "../shortcuts/shortcuts";
import { DatePickerShortcutMenu, DateRangeShortcut } from "../shortcuts/shortcuts";
import { TimePicker } from "../time-picker/timePicker";

export interface DateRangePickerProps extends DatePickerBaseProps, Props {
Expand Down Expand Up @@ -81,7 +81,11 @@ export interface DateRangePickerProps extends DatePickerBaseProps, Props {
* When triggered from mouseenter, it will pass the date range that would result from next click.
* When triggered from mouseleave, it will pass `undefined`.
*/
onHoverChange?: (hoveredDates: DateRange, hoveredDay: Date, hoveredBoundary: Boundary) => void;
onHoverChange?: (
hoveredDates: DateRange | undefined,
hoveredDay: Date,
hoveredBoundary: Boundary | undefined,
) => void;

/**
* Called when the `shortcuts` props is enabled and the user changes the shortcut.
Expand Down Expand Up @@ -330,7 +334,7 @@ export class DateRangePicker extends AbstractPureComponent<DateRangePickerProps,
const { selectedShortcutIndex } = this.state;
const { allowSingleDayRange, maxDate, minDate, timePrecision } = this.props;
return [
<Shortcuts
<DatePickerShortcutMenu
key="shortcuts"
{...{
allowSingleDayRange,
Expand Down
12 changes: 9 additions & 3 deletions packages/datetime/src/components/shortcuts/shortcuts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface DatePickerShortcut extends DateShortcutBase {
date: Date;
}

export interface ShortcutsProps {
export interface DatePickerShortcutMenuProps {
allowSingleDayRange: boolean;
minDate: Date;
maxDate: Date;
Expand All @@ -69,8 +69,14 @@ export interface ShortcutsProps {
useSingleDateShortcuts?: boolean;
}

export class Shortcuts extends React.PureComponent<ShortcutsProps> {
public static defaultProps: Partial<ShortcutsProps> = {
/**
* Menu of {@link DateRangeShortcut} items, typically displayed in the UI to the left of a day picker calendar.
*
* This component may be used for single date pickers as well as range pickers by toggling the
* `useSingleDateShortcuts` option.
*/
export class DatePickerShortcutMenu extends React.PureComponent<DatePickerShortcutMenuProps> {
public static defaultProps: Partial<DatePickerShortcutMenuProps> = {
selectedShortcutIndex: -1,
};

Expand Down
9 changes: 8 additions & 1 deletion packages/datetime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type DatePickerLocaleUtils = typeof LocaleUtils;
export { DatePickerLocaleUtils, DatePickerDayModifiers };

export { DateFormatProps } from "./common/dateFormatProps";
export { DateRangeSelectionStrategy, DateRangeSelectionState } from "./common/dateRangeSelectionStrategy";
export { MonthAndYear } from "./common/monthAndYear";
export { TimePickerProps, TimePrecision } from "./common/timePickerProps";

export { DateInput, DateInputProps } from "./components/date-input/dateInput";
Expand All @@ -32,5 +34,10 @@ export { DatePickerBaseProps, DatePickerModifiers } from "./common/datePickerBas
export { DateRangeInput, DateRangeInputProps } from "./components/date-range-input/dateRangeInput";
export { DateRangePicker, DateRangePickerProps } from "./components/date-range-picker/dateRangePicker";
export { TimePicker } from "./components/time-picker/timePicker";
export { DatePickerShortcut, DateRangeShortcut } from "./components/shortcuts/shortcuts";
export {
DatePickerShortcut,
DatePickerShortcutMenu,
DatePickerShortcutMenuProps,
DateRangeShortcut,
} from "./components/shortcuts/shortcuts";
export { TimezoneSelect, TimezoneSelectProps } from "./components/timezone-select/timezoneSelect";
15 changes: 11 additions & 4 deletions packages/datetime/test/components/datePickerTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Classes, DatePicker, DatePickerModifiers, DatePickerProps, TimePicker,
import { DateUtils, Months } from "../../src/common";
import * as Errors from "../../src/common/errors";
import { DatePickerState } from "../../src/components/date-picker/datePicker";
import { DatePickerShortcut, Shortcuts } from "../../src/components/shortcuts/shortcuts";
import { DatePickerShortcut, DatePickerShortcutMenu } from "../../src/components/shortcuts/shortcuts";
import { assertDayDisabled, assertDayHidden } from "../common/dayPickerTestUtils";

describe("<DatePicker>", () => {
Expand Down Expand Up @@ -478,16 +478,23 @@ describe("<DatePicker>", () => {
it("all shortcuts are displayed as inactive when none are selected", () => {
const { root } = wrap(<DatePicker shortcuts={true} />);

assert.isFalse(root.find(Shortcuts).find(Menu).find(MenuItem).find(`.${CoreClasses.ACTIVE}`).exists());
assert.isFalse(
root.find(DatePickerShortcutMenu).find(Menu).find(MenuItem).find(`.${CoreClasses.ACTIVE}`).exists(),
);
});

it("corresponding shortcut is displayed as active when selected", () => {
const selectedShortcut = 0;
const { root } = wrap(<DatePicker shortcuts={true} selectedShortcutIndex={selectedShortcut} />);

assert.isTrue(root.find(Shortcuts).find(Menu).find(MenuItem).find(`.${CoreClasses.ACTIVE}`).exists());
assert.isTrue(
root.find(DatePickerShortcutMenu).find(Menu).find(MenuItem).find(`.${CoreClasses.ACTIVE}`).exists(),
);

assert.lengthOf(root.find(Shortcuts).find(Menu).find(MenuItem).find(`.${CoreClasses.ACTIVE}`), 1);
assert.lengthOf(
root.find(DatePickerShortcutMenu).find(Menu).find(MenuItem).find(`.${CoreClasses.ACTIVE}`),
1,
);

assert.isTrue(root.state("selectedShortcutIndex") === selectedShortcut);
});
Expand Down
15 changes: 11 additions & 4 deletions packages/datetime/test/components/dateRangePickerTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import * as Errors from "../../src/common/errors";
import { Months } from "../../src/common/months";
import { DatePickerNavbar } from "../../src/components/date-picker/datePickerNavbar";
import { DateRangePickerState } from "../../src/components/date-range-picker/dateRangePicker";
import { DateRangeShortcut, Shortcuts } from "../../src/components/shortcuts/shortcuts";
import { DatePickerShortcutMenu, DateRangeShortcut } from "../../src/components/shortcuts/shortcuts";
import { assertDayDisabled } from "../common/dayPickerTestUtils";

describe("<DateRangePicker>", () => {
Expand Down Expand Up @@ -930,16 +930,23 @@ describe("<DateRangePicker>", () => {
it("all shortcuts are displayed as inactive when none are selected", () => {
const { wrapper } = render();

assert.isFalse(wrapper.find(Shortcuts).find(Menu).find(MenuItem).find(`.${Classes.ACTIVE}`).exists());
assert.isFalse(
wrapper.find(DatePickerShortcutMenu).find(Menu).find(MenuItem).find(`.${Classes.ACTIVE}`).exists(),
);
});

it("corresponding shortcut is displayed as active when selected", () => {
const selectedShortcut = 0;
const { wrapper } = render({ selectedShortcutIndex: selectedShortcut });

assert.isTrue(wrapper.find(Shortcuts).find(Menu).find(MenuItem).find(`.${Classes.ACTIVE}`).exists());
assert.isTrue(
wrapper.find(DatePickerShortcutMenu).find(Menu).find(MenuItem).find(`.${Classes.ACTIVE}`).exists(),
);

assert.lengthOf(wrapper.find(Shortcuts).find(Menu).find(MenuItem).find(`.${Classes.ACTIVE}`), 1);
assert.lengthOf(
wrapper.find(DatePickerShortcutMenu).find(Menu).find(MenuItem).find(`.${Classes.ACTIVE}`),
1,
);

assert.isTrue(wrapper.state("selectedShortcutIndex") === selectedShortcut);
});
Expand Down
3 changes: 1 addition & 2 deletions packages/datetime/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
* Copyright 2015 Palantir Technologies, Inc. All rights reserved.
*/

// tslint:disable no-submodule-imports
// tslint:disable-next-line no-submodule-imports
import "@blueprintjs/core/lib/css/blueprint.css";
// tslint:enable no-submodule-imports
import "../lib/css/blueprint-datetime.css";
import "./test-debugging-styles.scss";

Expand Down
18 changes: 18 additions & 0 deletions packages/datetime/test/isotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

require("@blueprintjs/test-commons/bootstrap");
const { generateIsomorphicTests } = require("@blueprintjs/test-commons");
const { add } = require("date-fns");

const DateTime = require("../lib/cjs");

Expand All @@ -25,8 +26,25 @@ describe("DateTime isomorphic rendering", () => {
placeholder: "enter date",
};

const today = new Date();
const maxDate = add(today, { days: 1 });
const minDate = add(today, { years: -4 });

generateIsomorphicTests(DateTime, {
DateInput: { props: formatProps },
DateRangeInput: { props: formatProps },
DatePickerShortcutMenu: {
className: false,
props: {
allowSingleDayRange: true,
maxDate,
minDate,
onShortcutClick: () => {
/* no-op */
},
shortcuts: true,
timePrecision: "second",
},
},
});
});
4 changes: 4 additions & 0 deletions packages/datetime2/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ module.exports = async function (config) {
config.set(
createKarmaConfig({
dirname: __dirname,
coverageExcludes: [
// we stub this out in tests because it relies on dynamic imports
"src/common/dateFnsLocaleUtils.ts",
],
coverageOverrides: {
"src/*": {
lines: 75,
Expand Down
2 changes: 2 additions & 0 deletions packages/datetime2/src/blueprint-datetime2.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
// Licensed under the Apache License, Version 2.0.

@import "common/react-day-picker-overrides";
@import "components/date-picker3/date-picker3-caption";
@import "components/date-picker3/date-picker3";
@import "components/date-range-picker3/date-range-picker3";
52 changes: 49 additions & 3 deletions packages/datetime2/src/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,68 @@
* limitations under the License.
*/

import classNames from "classnames";
import type { StyledElement } from "react-day-picker";

import { Classes as CoreClasses } from "@blueprintjs/core";
import { Classes as DatetimeClasses } from "@blueprintjs/datetime";

const RDP_DAY = "rdp-day";
const RDP = "rdp";
const RDP_DAY = `${RDP}-day`;
const DATEPICKER_NAV_BUTTON = `${DatetimeClasses.DATEPICKER}-nav-button`;

export const Classes = {
...DatetimeClasses,
const ReactDayPickerClasses = {
RDP,
RDP_CAPTION_LABEL: `${RDP}-caption_label`,
RDP_DAY,
RDP_VHIDDEN: `${RDP}-vhidden`,
};

const DatePicker3Classes = {
// these classes need the "3" suffix because they overlap with DatePicker v1 / react-day-picker v7 classes
DATEPICKER3_DAY: RDP_DAY,
DATEPICKER3_DAY_DISABLED: `${RDP_DAY}_disabled`,
DATEPICKER3_DAY_IS_TODAY: `${RDP_DAY}_today`,
DATEPICKER3_DAY_OUTSIDE: `${RDP_DAY}_outside`,
DATEPICKER3_DAY_SELECTED: `${RDP_DAY}_selected`,
// these classes intentionally left without "3" suffix because they do not overlap with DatePicker v1, and this way we don't need to migrate them later, which reduces code churn
DATEPICKER_DROPDOWN_CONTAINER: `${DatetimeClasses.DATEPICKER}-dropdown-container`,
DATEPICKER_DROPDOWN_MONTH: `${RDP}-dropdown_month`,
DATEPICKER_DROPDOWN_YEAR: `${RDP}-dropdown_year`,
DATEPICKER_HIGHLIGHT_CURRENT_DAY: `${DatetimeClasses.DATEPICKER}-highlight-current-day`,
DATEPICKER_NAV_BUTTON,
DATEPICKER_NAV_BUTTON_HIDDEN: `${DATEPICKER_NAV_BUTTON}-hidden`,
DATEPICKER_NAV_BUTTON_NEXT: `${DATEPICKER_NAV_BUTTON}-next`,
DATEPICKER_NAV_BUTTON_PREVIOUS: `${DATEPICKER_NAV_BUTTON}-previous`,
};

const DateRangePicker3Classes = {
DATERANGEPICKER3_DAY_HOVERED_RANGE: `${RDP_DAY}_hovered`,
DATERANGEPICKER3_DAY_HOVERED_RANGE_END: `${RDP_DAY}_hovered_end`,
DATERANGEPICKER3_DAY_HOVERED_RANGE_START: `${RDP_DAY}_hovered_start`,
DATERANGEPICKER3_DAY_RANGE_END: `${RDP_DAY}_range_end`,
DATERANGEPICKER3_DAY_RANGE_MIDDLE: `${RDP_DAY}_range_middle`,
DATERANGEPICKER3_DAY_RANGE_START: `${RDP_DAY}_range_start`,
DATERANGEPICKER_REVERSE_MONTH_AND_YEAR: `${DatetimeClasses.DATERANGEPICKER}-reverse-month-and-year`,
};

export const Classes = {
...DatetimeClasses,
...DatePicker3Classes,
...DateRangePicker3Classes,
...ReactDayPickerClasses,
};

/**
* Class name overrides for components rendered by react-day-picker. These are helpful so that @blueprintjs/datetime2
* can have more predictable and standard DOM selectors in custom styles & tests.
*/
export const dayPickerClassNameOverrides: Partial<StyledElement<string>> = {
/* eslint-disable camelcase */
button: classNames(CoreClasses.BUTTON, CoreClasses.MINIMAL),
button_reset: undefined,
nav_button: Classes.DATEPICKER_NAV_BUTTON,
nav_button_next: Classes.DATEPICKER_NAV_BUTTON_NEXT,
nav_button_previous: Classes.DATEPICKER_NAV_BUTTON_PREVIOUS,
/* eslint-enable camelcase */
};
Loading