Skip to content

Commit

Permalink
[datetime2] feat(DateInput3): simpler formatting & parsing API (#6398)
Browse files Browse the repository at this point in the history
  • Loading branch information
adidahiya authored Sep 21, 2023
1 parent a97db05 commit a4e517f
Show file tree
Hide file tree
Showing 21 changed files with 545 additions and 187 deletions.
3 changes: 2 additions & 1 deletion packages/datetime/src/common/datePickerBaseProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

import { DayPickerProps, LocaleUtils } from "react-day-picker";

import type { TimePickerProps, TimePrecision } from "./timePickerProps";
import type { TimePickerProps } from "./timePickerProps";
import type { TimePrecision } from "./timePrecision";

// DatePicker supports a simpler set of modifiers (for now).
// also we need an interface for the dictionary without `today` and `outside` injected by r-d-p.
Expand Down
3 changes: 2 additions & 1 deletion packages/datetime/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export { DateFormatProps } from "./dateFormatProps";
export { DateRange, NonNullDateRange } from "./dateRange";
export { Months } from "./months";
export { TimeUnit } from "./timeUnit";
export { TimePickerProps, TimePrecision } from "./timePickerProps";
export { TimePickerProps } from "./timePickerProps";
export { TimePrecision } from "./timePrecision";
export { TimezoneDisplayFormat } from "./timezoneDisplayFormat";
export { getTimezoneMetadata } from "./timezoneMetadata";
9 changes: 1 addition & 8 deletions packages/datetime/src/common/timePickerProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,9 @@

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

import { TimePrecision } from "./timePrecision";
import { TimeUnit } from "./timeUnit";

export const TimePrecision = {
MILLISECOND: "millisecond" as "millisecond",
MINUTE: "minute" as "minute",
SECOND: "second" as "second",
};
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type TimePrecision = (typeof TimePrecision)[keyof typeof TimePrecision];

export interface TimePickerProps extends Props {
/**
* Whether to focus the first input when it opens initially.
Expand Down
23 changes: 23 additions & 0 deletions packages/datetime/src/common/timePrecision.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 const TimePrecision = {
MILLISECOND: "millisecond" as "millisecond",
MINUTE: "minute" as "minute",
SECOND: "second" as "second",
};
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type TimePrecision = (typeof TimePrecision)[keyof typeof TimePrecision];
2 changes: 1 addition & 1 deletion packages/datetime/src/common/timezoneUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { formatInTimeZone, utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
import isEmpty from "lodash/isEmpty";

import { getCurrentTimezone } from "./getTimezone";
import { TimePrecision } from "./timePickerProps";
import { TimePrecision } from "./timePrecision";
import { UTC_TIME } from "./timezoneItems";

export { getCurrentTimezone, UTC_TIME };
Expand Down
3 changes: 2 additions & 1 deletion packages/datetime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ 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 { TimePickerProps } from "./common/timePickerProps";
export { TimePrecision } from "./common/timePrecision";

export { DateInput, DateInputProps } from "./components/date-input/dateInput";
export { DatePicker, DatePickerProps } from "./components/date-picker/datePicker";
Expand Down
54 changes: 54 additions & 0 deletions packages/datetime2/src/common/dateFnsFormatUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 { format, Locale, parse } from "date-fns";

import { DatePickerBaseProps, TimePrecision } from "@blueprintjs/datetime";

export const DefaultDateFnsFormats = {
DATE_ONLY: "yyyy-MM-dd",
DATE_TIME_MILLISECONDS: "yyyy-MM-dd HH:mm:ss.SSS",
DATE_TIME_MINUTES: "yyyy-MM-dd HH:mm",
DATE_TIME_SECONDS: "yyyy-MM-dd HH:mm:ss",
};

export function getDefaultDateFnsFormat(props: Pick<DatePickerBaseProps, "timePickerProps" | "timePrecision">): string {
const hasTimePickerProps = props.timePickerProps !== undefined && Object.keys(props.timePickerProps).length > 0;
const precision =
props.timePrecision ??
props.timePickerProps?.precision ??
// if timePickerProps is non-empty but has no precision defined, use the default value of "minute"
(hasTimePickerProps ? TimePrecision.MINUTE : undefined);

switch (precision) {
case TimePrecision.MILLISECOND:
return DefaultDateFnsFormats.DATE_TIME_MILLISECONDS;
case TimePrecision.MINUTE:
return DefaultDateFnsFormats.DATE_TIME_MINUTES;
case TimePrecision.SECOND:
return DefaultDateFnsFormats.DATE_TIME_SECONDS;
default:
return DefaultDateFnsFormats.DATE_ONLY;
}
}

export function getDateFnsFormatter(formatStr: string, locale: Locale | undefined) {
return (date: Date) => format(date, formatStr, { locale });
}

export function getDateFnsParser(formatStr: string, locale: Locale | undefined) {
return (str: string) => parse(str, formatStr, new Date(), { locale });
}
14 changes: 14 additions & 0 deletions packages/datetime2/src/common/dateFnsLocaleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import type { Locale } from "date-fns";
import * as React from "react";

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

Expand All @@ -31,3 +32,16 @@ export async function loadDateFnsLocale(localeCode: string): Promise<Locale | un
return undefined;
}
}

export function useDateFnsLocale(localeCode: string | undefined) {
const [locale, setLocale] = React.useState<Locale | undefined>(undefined);
React.useEffect(() => {
if (localeCode === undefined) {
return;
} else if (locale?.code === localeCode) {
return;
}
loadDateFnsLocale(localeCode).then(setLocale);
}, [locale?.code, localeCode]);
return locale;
}
62 changes: 18 additions & 44 deletions packages/datetime2/src/components/date-input3/date-input3.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,25 @@ Migrating from [DateInput](#datetime/date-input)?

</h5>

__DateInput3__ is a replacement for DateInput and will replace it in Blueprint v6.
**DateInput3** is a replacement for DateInput and will replace it in Blueprint v6.
You are encouraged to use this new API now to ease the transition to the next major version of Blueprint.
See the [react-day-picker v8 migration guide](https://github.com/palantir/blueprint/wiki/react-day-picker-8-migration)
on the wiki.

</div>

__DateInput3__ has the same functionality as [DateInput](#datetime/date-input) but uses
**DateInput3** has the same functionality as [DateInput](#datetime/date-input) but uses
[react-day-picker v8](https://react-day-picker.js.org/) instead of [v7](https://react-day-picker-v7.netlify.app/)
to render its calendar. It renders an interactive [__InputGroup__](#core/components/input-group)
which, when focussed, displays a [__DatePicker3__](#datetime2/date-picker3) inside a
[__Popover__](#core/components/popover). It optionally renders a [__TimezoneSelect__](#datetime/timezone-select)
to render its calendar. It renders an interactive [**InputGroup**](#core/components/input-group)
which, when focussed, displays a [**DatePicker3**](#datetime2/date-picker3) inside a
[**Popover**](#core/components/popover). It optionally renders a [**TimezoneSelect**](#datetime/timezone-select)
on the right side of the InputGroup which allows users to change the timezone of the selected date.

@reactExample DateInput3Example

@## Usage

__DateInput3__ supports both controlled and uncontrolled usage. You can control
**DateInput3** supports both controlled and uncontrolled usage. You can control
the selected date by setting the `value` prop, or use the component in
uncontrolled mode and specify an initial date by setting `defaultValue`.
Use the `onChange` prop callback to listen for changes to the selected day and
Expand All @@ -40,31 +40,33 @@ and the `onChange` callback.

@## Props interface

In addition to top-level __DateInput3__ props, you may forward some props to `<DayPicker mode="single">` to customize
In addition to top-level **DateInput3** props, you may forward some props to `<DayPicker mode="single">` to customize
react-day-picker's behavior via `dayPickerProps` (the full list is
[documented here](https://react-day-picker.js.org/api/interfaces/DayPickerSingleProps)).

Shortcuts and modifiers are also configurable via the same API as [__DatePicker3__](#datetime2/date-picker3); see those
Shortcuts and modifiers are also configurable via the same API as [**DatePicker3**](#datetime2/date-picker3); see those
docs for more info.

@interface DateInput3Props

@## Date formatting

__DateInput3__ requires two important props for parsing and formatting dates. These are essentially the plumbing
between the text input and __DatePicker3__.
By default, **DateInput3** utilizes [date-fns](https://date-fns.org/docs/) to format & parse date strings. You may
specify which [date-fns format](https://date-fns.org/docs/format) to use with the `dateFnsFormat` prop.

If you do not specify this prop, the component will use one of its default formats corresponding to the time precision
specified by the `timePrecision` and `timePickerProps` props.

Finally, you have the option to specify a custom formatter & parser with the `formatDate` and `parseDate` props:

- `formatDate(date: Date, localeCode?: string)` receives the current `Date` and returns a string representation of it.
The result of this function becomes the input value when it is not being edited.
- `parseDate(str: string, localeCode?: string)` receives text inputted by the user and converts it to a `Date` object.
The returned `Date` becomes the next value of the component.

The optional `locale` argument to these functions is the value of the `locale` prop set on the component.

Note that we still use JS `Date` here instead of ISO strings &mdash; this makes it easy to delegate to
third party libraries like __date-fns__.
The optional `localeCode` argument to these functions is the value of the `locale` prop set on the component.

A simple implementation using built-in browser methods could look like this:
A simple implementation of a custom formatter & parser using built-in browser methods could look like this:

```tsx
import { DateInput3 } from "@blueprintjs/datetime2";
Expand All @@ -88,34 +90,6 @@ function Example() {
}
```

An implementation using __date-fns__ could look like this:

```tsx
import { DateInput3 } from "@blueprintjs/datetime2";
import { format, parse } from "date-fns";
import { useCallback, useState } from "react";

const today = new Date();
const dateFnsFormat = "yyyy-MM-dd HH:mm:ss";

function Example() {
const [dateValue, setDateValue] = useState<string>(null);
const handleChange = useCallback(setDateValue, []);
const formatDate = useCallback((date: Date) => format(date, dateFnsFormat), []);
const parseDate = useCallback((str: string) => parse(str, dateFnsFormat, today), []);

return (
<DateInput3
formatDate={formatDate}
onChange={handleChange}
parseDate={parseDate}
placeholder={dateFnsFormat}
value={dateValue}
/>
);
}
```

@## Localization

See the [__DatePicker3__ localization docs](#datetime2/date-picker3.localization).
See the [**DatePicker3** localization docs](#datetime2/date-picker3.localization).
Loading

1 comment on commit a4e517f

@adidahiya
Copy link
Contributor Author

Choose a reason for hiding this comment

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

[datetime2] feat(DateInput3): simpler formatting & parsing API (#6398)

Build artifact links for this commit: documentation | landing | table | demo

This is an automated comment from the deploy-preview CircleCI job.

Please sign in to comment.