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 19 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, DateRangeShortcut, DatePickerShortcutMenu } 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 { DateRangeShortcut, DatePickerShortcutMenu } 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
6 changes: 3 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,8 @@ export interface ShortcutsProps {
useSingleDateShortcuts?: boolean;
}

export class Shortcuts extends React.PureComponent<ShortcutsProps> {
public static defaultProps: Partial<ShortcutsProps> = {
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";
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";
18 changes: 16 additions & 2 deletions packages/datetime2/src/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,31 @@ import { Classes as DatetimeClasses } from "@blueprintjs/datetime";
const RDP_DAY = "rdp-day";
const DATEPICKER_NAV_BUTTON = `${DatetimeClasses.DATEPICKER}-nav-button`;

export const Classes = {
...DatetimeClasses,
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_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 = {
DATERANGEPICKER_REVERSE_MONTH_AND_YEAR: `${DatetimeClasses.DATERANGEPICKER}-reverse-month-and-year`,
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`,
};

export const Classes = {
...DatetimeClasses,
...DatePicker3Classes,
...DateRangePicker3Classes,
};
28 changes: 28 additions & 0 deletions packages/datetime2/src/common/dateFnsLocaleProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2023 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export interface DateFnsLocaleProps {
/**
* date-fns locale code ((ISO 639-1 + optional country code) used to localize the date picker.
*
* If you are unable to load a specific locale, make sure it is included in the list of date-fns'
* [supported locales](https://github.com/date-fns/date-fns/tree/main/src/locale).
*
* @default "en-US"
* @see https://date-fns.org/docs/Locale
*/
locale?: string;
}
33 changes: 33 additions & 0 deletions packages/datetime2/src/common/dateFnsLocaleUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { Locale } from "date-fns";

import { Utils } from "@blueprintjs/core";

export async function loadDateFnsLocale(localeCode: string): Promise<Locale | undefined> {
try {
const { default: locale } = await import(`date-fns/esm/locale/${localeCode}/index.js`);
return locale;
} catch {
if (!Utils.isNodeEnv("production")) {
console.error(
`[Blueprint] Could not load "${localeCode}" date-fns locale, please check that this locale code is supported: https://github.com/date-fns/date-fns/tree/main/src/locale`,
);
}
return undefined;
}
}
47 changes: 47 additions & 0 deletions packages/datetime2/src/common/dayPickerModifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2023 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { DayModifiers } from "react-day-picker";

export const DISABLED_MODIFIER = "disabled";
export const HOVERED_RANGE_MODIFIER = "hovered-range";
export const OUTSIDE_MODIFIER = "outside";
export const SELECTED_MODIFIER = "selected";
export const SELECTED_RANGE_MODIFIER = "selected-range";
// modifiers the user can't set because they are used by Blueprint or react-day-picker
export const DISALLOWED_MODIFIERS = [
DISABLED_MODIFIER,
HOVERED_RANGE_MODIFIER,
OUTSIDE_MODIFIER,
SELECTED_MODIFIER,
SELECTED_RANGE_MODIFIER,
];

export function combineModifiers(baseModifiers: DayModifiers, userModifiers: DayModifiers | undefined): DayModifiers {
let modifiers = baseModifiers;
if (userModifiers !== undefined) {
modifiers = {};
for (const key of Object.keys(userModifiers)) {
if (DISALLOWED_MODIFIERS.indexOf(key) === -1) {
modifiers[key] = userModifiers[key];
}
}
for (const key of Object.keys(baseModifiers)) {
modifiers[key] = baseModifiers[key];
}
}
return modifiers;
}
26 changes: 26 additions & 0 deletions packages/datetime2/src/common/reactDayPickerUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { DateRange as RDPRange } from "react-day-picker";

import type { DateRange } from "@blueprintjs/datetime";

/**
* Converts a Blueprint `DateRange` to a react-day-picker `DateRange`.
*/
export function dateRangeToDayPickerRange(range: DateRange): RDPRange {
return { from: range[0] ?? undefined, to: range[1] ?? undefined };
}
46 changes: 46 additions & 0 deletions packages/datetime2/src/common/useMonthSelectRightOffset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2023 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as React from "react";

import { Classes, DatePickerUtils } from "@blueprintjs/datetime";
import { IconSize } from "@blueprintjs/icons";

export function useMonthSelectRightOffset(
monthSelectElement: React.RefObject<HTMLSelectElement>,
containerElement: React.RefObject<HTMLElement>,
displayedMonthText: string,
): number {
const [monthRightOffset, setMonthRightOffset] = React.useState<number>(0);

React.useLayoutEffect(() => {
if (containerElement.current == null) {
return;
}

// measure width of text as rendered inside our container element.
const monthTextWidth = DatePickerUtils.measureTextWidth(
displayedMonthText,
Classes.DATEPICKER_CAPTION_MEASURE,
containerElement.current,
);
const monthSelectWidth = monthSelectElement.current?.clientWidth ?? 0;
const rightOffset = Math.max(2, monthSelectWidth - monthTextWidth - IconSize.STANDARD - 2);
setMonthRightOffset(rightOffset);
}, [containerElement, displayedMonthText, monthSelectElement]);

return monthRightOffset;
}
Loading