Skip to content

Commit

Permalink
[datetime2] feat: DateRangeInput2 component (#5390)
Browse files Browse the repository at this point in the history
  • Loading branch information
adidahiya authored Jul 11, 2022
1 parent f1d59ea commit 90b51cf
Showing 34 changed files with 4,357 additions and 81 deletions.
6 changes: 3 additions & 3 deletions packages/datetime/src/dateFormat.tsx
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
*/

import { isDateValid, isDayInRange } from "./common/dateUtils";
import { IDatePickerBaseProps } from "./datePickerCore";
import { DatePickerBaseProps } from "./datePickerCore";

// eslint-disable-next-line deprecation/deprecation
export type DateFormatProps = IDateFormatProps;
@@ -63,8 +63,8 @@ export interface IDateFormatProps {
}

export function getFormattedDateString(
date: Date | false | null,
props: DateFormatProps & IDatePickerBaseProps,
date: Date | false | null | undefined,
props: DateFormatProps & DatePickerBaseProps,
ignoreRange = false,
) {
if (date == null) {
4 changes: 2 additions & 2 deletions packages/datetime/src/dateInput.tsx
Original file line number Diff line number Diff line change
@@ -37,13 +37,13 @@ import * as Classes from "./common/classes";
import { isDateValid, isDayInRange } from "./common/dateUtils";
import { DateFormatProps, getFormattedDateString } from "./dateFormat";
import { DatePicker } from "./datePicker";
import { getDefaultMaxDate, getDefaultMinDate, IDatePickerBaseProps } from "./datePickerCore";
import { DatePickerBaseProps, getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";
import { DatePickerShortcut } from "./shortcuts";

// eslint-disable-next-line deprecation/deprecation
export type DateInputProps = IDateInputProps;
/** @deprecated use DateInputProps */
export interface IDateInputProps extends IDatePickerBaseProps, DateFormatProps, Props {
export interface IDateInputProps extends DatePickerBaseProps, DateFormatProps, Props {
/**
* Allows the user to clear the selection by clicking the currently selected day.
* Passed to `DatePicker` component.
4 changes: 2 additions & 2 deletions packages/datetime/src/datePicker.tsx
Original file line number Diff line number Diff line change
@@ -24,15 +24,15 @@ import * as Classes from "./common/classes";
import * as DateUtils from "./common/dateUtils";
import * as Errors from "./common/errors";
import { DatePickerCaption } from "./datePickerCaption";
import { getDefaultMaxDate, getDefaultMinDate, IDatePickerBaseProps } from "./datePickerCore";
import { DatePickerBaseProps, getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";
import { DatePickerNavbar } from "./datePickerNavbar";
import { DatePickerShortcut, DateRangeShortcut, Shortcuts } from "./shortcuts";
import { TimePicker } from "./timePicker";

// eslint-disable-next-line deprecation/deprecation
export type DatePickerProps = IDatePickerProps;
/** @deprecated use DatePickerProps */
export interface IDatePickerProps extends IDatePickerBaseProps, Props {
export interface IDatePickerProps extends DatePickerBaseProps, Props {
/**
* Allows the user to clear the selection by clicking the currently selected day.
* If disabled, the "Clear" Button in the Actions Bar will also be disabled.
2 changes: 1 addition & 1 deletion packages/datetime/src/datePickerCore.tsx
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ export interface IDatePickerModifiers {
// eslint-disable-next-line deprecation/deprecation
export type DatePickerModifiers = IDatePickerModifiers;

export interface IDatePickerBaseProps {
export interface DatePickerBaseProps {
/**
* Props to pass to ReactDayPicker. See API documentation
* [here](https://react-day-picker-v7.netlify.app/api/DayPicker).
28 changes: 28 additions & 0 deletions packages/datetime/src/datePickerUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 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 { getFormattedDateString } from "./dateFormat";
import { getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";

/**
* DatePicker-related utility functions which may be useful outside this package to
* build date/time components. Initially created for use in @blueprintjs/datetime2.
*/
export const DatePickerUtils = {
getDefaultMaxDate,
getDefaultMinDate,
getFormattedDateString,
};
16 changes: 12 additions & 4 deletions packages/datetime/src/dateRangeInput.tsx
Original file line number Diff line number Diff line change
@@ -14,6 +14,12 @@
* limitations under the License.
*/

/**
* @fileoverview This component is DEPRECATED, and the code is frozen.
* All changes & bugfixes should be made to DateRangeInput2 in the datetime2
* package instead.
*/

import classNames from "classnames";
import * as React from "react";
import DayPicker from "react-day-picker";
@@ -39,7 +45,7 @@ import { DateRange } from "./common/dateRange";
import { areSameTime, isDateValid, isDayInRange } from "./common/dateUtils";
import * as Errors from "./common/errors";
import { DateFormatProps, getFormattedDateString } from "./dateFormat";
import { getDefaultMaxDate, getDefaultMinDate, IDatePickerBaseProps } from "./datePickerCore";
import { DatePickerBaseProps, getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";
import { DateRangePicker } from "./dateRangePicker";
import { DateRangeShortcut } from "./shortcuts";

@@ -53,7 +59,7 @@ type InputEvent =
// eslint-disable-next-line deprecation/deprecation
export type DateRangeInputProps = IDateRangeInputProps;
/** @deprecated use DateRangeInputProps */
export interface IDateRangeInputProps extends IDatePickerBaseProps, DateFormatProps, Props {
export interface IDateRangeInputProps extends DatePickerBaseProps, DateFormatProps, Props {
/**
* Whether the start and end dates of the range can be the same day.
* If `true`, clicking a selected date will create a one-day range.
@@ -213,6 +219,7 @@ interface IStateKeysAndValuesObject {
};
}

/** @deprecated use { DateRangeInput2 } from "@blueprintjs/datetime2" */
export class DateRangeInput extends AbstractPureComponent2<DateRangeInputProps, IDateRangeInputState> {
public static defaultProps: Partial<DateRangeInputProps> = {
allowSingleDayRange: false,
@@ -251,8 +258,8 @@ export class DateRangeInput extends AbstractPureComponent2<DateRangeInputProps,
this.props.endInputProps?.inputRef,
);

public constructor(props: DateRangeInputProps, context?: any) {
super(props, context);
public constructor(props: DateRangeInputProps) {
super(props);
this.reset(props);
}

@@ -983,6 +990,7 @@ export class DateRangeInput extends AbstractPureComponent2<DateRangeInputProps,
// the constructor and componentDidUpdate.
private getFormattedMinMaxDateString(props: IDateRangeInputProps, propName: "minDate" | "maxDate") {
const date = props[propName];
// eslint-disable-next-line deprecation/deprecation
const defaultDate = DateRangeInput.defaultProps[propName];
// default values are applied only if a prop is strictly `undefined`
// See: https://facebook.github.io/react/docs/react-component.html#defaultprops
4 changes: 2 additions & 2 deletions packages/datetime/src/dateRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -28,11 +28,11 @@ import { MonthAndYear } from "./common/monthAndYear";
import { DatePickerCaption } from "./datePickerCaption";
import {
combineModifiers,
DatePickerBaseProps,
DatePickerModifiers,
getDefaultMaxDate,
getDefaultMinDate,
HOVERED_RANGE_MODIFIER,
IDatePickerBaseProps,
SELECTED_RANGE_MODIFIER,
} from "./datePickerCore";
import { DatePickerNavbar } from "./datePickerNavbar";
@@ -43,7 +43,7 @@ import { TimePicker } from "./timePicker";
// eslint-disable-next-line deprecation/deprecation
export type DateRangePickerProps = IDateRangePickerProps;
/** @deprecated use DateRangePickerProps */
export interface IDateRangePickerProps extends IDatePickerBaseProps, Props {
export interface IDateRangePickerProps extends DatePickerBaseProps, Props {
/**
* Whether the start and end dates of the range can be the same day.
* If `true`, clicking a selected date will create a one-day range.
13 changes: 12 additions & 1 deletion packages/datetime/src/daterangeinput.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
@# Date range input

<div class="@ns-callout @ns-intent-success @ns-icon-star">
<h4 class="@ns-heading">Newer API available</h4>

There is an updated version of this component available in the new
[__@blueprintjs/datetime2__ package](#datetime2) called
[DateRangeInput2](#datetime2/date-range-input2). Its API is currently in development,
but you are encouraged to try it out and provide feedback for the next
version of the Blueprint date input.

</div>

The DateRangeInput component is a [ControlGroup](#core/components/control-group) composed
of two [InputGroups](#core/components/text-inputs.input-group). It shows a
[DateRangePicker](#datetime/daterangepicker) in a [Popover](#core/components/popover) on focus.
@@ -28,4 +39,4 @@ information on these props.

@## Localization

See the [Date picker localization docs](#datetime/datepicker.localization).
See the [DatePicker localization docs](#datetime/datepicker.localization).
4 changes: 2 additions & 2 deletions packages/datetime/src/index.md
Original file line number Diff line number Diff line change
@@ -28,9 +28,9 @@ Make sure to review the [getting started docs for installation info](#blueprint/
npm install --save @blueprintjs/datetime
```

Import CSS with a JS bundler like webpack:
Import the package stylesheet in Sass:

```js
```scss
@import "~@blueprintjs/datetime/lib/css/blueprint-datetime.css";
```

3 changes: 2 additions & 1 deletion packages/datetime/src/index.ts
Original file line number Diff line number Diff line change
@@ -38,7 +38,8 @@ export { TimeUnit } from "./common/timeUnit";
export { DateFormatProps, IDateFormatProps } from "./dateFormat";
export { DateInput, DateInputProps, IDateInputProps } from "./dateInput";
export { DatePicker, DatePickerProps, IDatePickerProps } from "./datePicker";
export { DatePickerModifiers, IDatePickerModifiers } from "./datePickerCore";
export { DatePickerUtils } from "./datePickerUtils";
export { DatePickerBaseProps, DatePickerModifiers, IDatePickerModifiers } from "./datePickerCore";
export { DateTimePicker, IDateTimePickerProps } from "./dateTimePicker";
export { DateRangeInput, DateRangeInputProps, IDateRangeInputProps } from "./dateRangeInput";
export { DateRangePicker, DateRangePickerProps, IDateRangePickerProps } from "./dateRangePicker";
2 changes: 2 additions & 0 deletions packages/datetime/test/dateRangeInputTests.tsx
Original file line number Diff line number Diff line change
@@ -56,6 +56,8 @@ type InvalidDateTestFunction = (
otherInputGetterFn: (root: WrappedComponentRoot) => WrappedComponentInput,
) => void;

/* eslint-disable deprecation/deprecation */

// Change the default for testability
DateRangeInput.defaultProps.popoverProps = { usePortal: false };

4 changes: 4 additions & 0 deletions packages/datetime2/karma.conf.js
Original file line number Diff line number Diff line change
@@ -7,6 +7,10 @@ const { createKarmaConfig } = require("@blueprintjs/karma-build-scripts");
module.exports = function (config) {
const baseConfig = createKarmaConfig({
dirname: __dirname,
coverageExcludes: [
// HACKHACK: needs coverage
"src/components/date-range-input2/*",
],
});
config.set(baseConfig);
config.set({
23 changes: 23 additions & 0 deletions packages/datetime2/src/common/dateRange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2022 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 type DateRange = [Date | null, Date | null];
export type NonNullDateRange = [Date, Date];

/* istanbul ignore next */
export function isNonNullRange(range: DateRange): range is NonNullDateRange {
return range[0] != null && range[1] != null;
}
76 changes: 27 additions & 49 deletions packages/datetime2/src/common/dateUtils.ts
Original file line number Diff line number Diff line change
@@ -14,60 +14,38 @@
* limitations under the License.
*/

import { formatInTimeZone } from "date-fns-tz";
import { isEmpty } from "lodash-es";
import { isSameDay, isWithinInterval } from "date-fns";

import { TimePrecision } from "@blueprintjs/datetime";
import { DateRange, isNonNullRange } from "./dateRange";

import { convertDateToLocalEquivalentOfTimezoneTime, convertLocalDateToTimezoneTime } from "./timezoneUtils";

const NO_TIME_PRECISION = "date";
const UTC_IANA_LABEL = "Etc/UTC";

const TIME_FORMAT_TO_ISO_FORMAT: Record<TimePrecision | "date", string> = {
[TimePrecision.MILLISECOND]: "yyyy-MM-dd'T'HH:mm:ss.SSSxxx",
[TimePrecision.SECOND]: "yyyy-MM-dd'T'HH:mm:ssxxx",
[TimePrecision.MINUTE]: "yyyy-MM-dd'T'HH:mmxxx",
[NO_TIME_PRECISION]: "yyyy-MM-dd",
};

/**
* @see https://github.com/marnusw/date-fns-tz#formatintimezone
* @returns a string of tokens which tell date-fns-tz's formatInTimeZone how to render a datetime
*/
function getFormatStr(timePrecision: TimePrecision | undefined): string {
return TIME_FORMAT_TO_ISO_FORMAT[timePrecision ?? NO_TIME_PRECISION];
export function clone(d: Date) {
return new Date(d.getTime());
}

export function getIsoEquivalentWithUpdatedTimezone(
date: Date,
timezone: string,
timePrecision: TimePrecision | undefined,
): string {
const convertedDate = convertDateToLocalEquivalentOfTimezoneTime(date, timezone);
return formatInTimeZone(convertedDate, timezone, getFormatStr(timePrecision));
export function isSameTime(d1: Date | null, d2: Date | null) {
// N.B. do not use date-fns helper fns here, since we don't want to return false when the month/day/year is different
return (
d1 != null &&
d2 != null &&
d1.getHours() === d2.getHours() &&
d1.getMinutes() === d2.getMinutes() &&
d1.getSeconds() === d2.getSeconds() &&
d1.getMilliseconds() === d2.getMilliseconds()
);
}

/**
* HACKHACK: this method relies on parsing strings with the `Date()` constructor, which is discouraged
* by the MDN documentation and the Moment.js status page. If we continue to use this approach, we need
* to validate that input strings conform to the ISO 8601 format.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#parameters
* @see https://momentjs.com/docs/#/-project-status/
*
* @param value ISO string representation of a date
* @param timezone target timezone IANA code
*/
export function getDateObjectFromIsoString(value: string | null | undefined, timezone: string): Date | null {
if (value == null || isEmpty(value)) {
return null;
}
const date = new Date(value);
// If the value is just a date format then we convert it to midnight in local time to avoid weird things happening
if (value.length === 10) {
// If it's just a date, we know it's interpreted as midnight UTC so we convert it to local time of that UTC time
return convertLocalDateToTimezoneTime(date, UTC_IANA_LABEL);
export function isDayInRange(date: Date | null, dateRange: DateRange, exclusive = false) {
if (date == null || !isNonNullRange(dateRange)) {
return false;
}
return convertLocalDateToTimezoneTime(date, timezone);

const day = clone(date);
const start = clone(dateRange[0]);
const end = clone(dateRange[1]);

day.setHours(0, 0, 0, 0);
start.setHours(0, 0, 0, 0);
end.setHours(0, 0, 0, 0);

return isWithinInterval(date, { start, end }) && (!exclusive || (!isSameDay(start, day) && !isSameDay(day, end)));
}
21 changes: 21 additions & 0 deletions packages/datetime2/src/common/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2022 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.
*/

const ns = "[Blueprint]";

export const DATERANGEINPUT_NULL_VALUE =
`${ns} <DateRangeInput2> value cannot be null. Pass undefined to clear the value and operate in` +
" uncontrolled mode, or pass [null, null] to clear the value and continue operating in controlled mode.";
6 changes: 4 additions & 2 deletions packages/datetime2/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@
* limitations under the License.
*/

import * as classes from "../common/classes";
import * as Classes from "./classes";
import * as DateUtils from "./dateUtils";

export const Classes = classes;
export { Classes, DateUtils };
export { DateRange, NonNullDateRange } from "./dateRange";
Loading

1 comment on commit 90b51cf

@blueprint-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[datetime2] feat: DateRangeInput2 component (#5390)

Previews: documentation | landing | table | demo

Please sign in to comment.