Skip to content

Commit

Permalink
converts all DatePicker family of components to TypeScript (#2891)
Browse files Browse the repository at this point in the history
* converts all DatePicker family of components to TypeScript

* updates ScreenReaderOnly text per review feedback

* widens RefCallback usage to just Ref per review feedback

* export ReactDatePicker and ReactDatePickerProps from @elastic/eui

* Move date picker onChange string argument definition to super date picker

* Semi-revert EuiAbsoluteTab's onChange call

* Cleaned up aria-describedby in EuiRefreshInteral

* makes valueAsMoment usage more consistent/idiomatic

* makes optional event arguments consistent

* Clean up regressions found in Kibana testing

Co-authored-by: Chandler Prall <[email protected]>
  • Loading branch information
dimitropoulos and chandlerprall authored Apr 13, 2020
1 parent 61ffb8a commit cddaf28
Show file tree
Hide file tree
Showing 52 changed files with 1,252 additions and 984 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## [`master`](https://github.com/elastic/eui/tree/master)

- Converted `NavDrawer`, `NavDrawerGroup`, and `NavDrawerFlyout` to TypeScript ([#3268](https://github.com/elastic/eui/pull/3268))
- Converted `EuiDatePicker`, `EuiDatePickerRange`, `EuiSuperDatePicker`, and `EuiSuperUpdateButton` to TypeScript ([#2891](https://github.com/elastic/eui/pull/2891))

## [`22.5.0`](https://github.com/elastic/eui/tree/v22.5.0)

Expand Down
20 changes: 20 additions & 0 deletions src/components/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ export type PropsOf<C> = C extends SFC<infer SFCProps>
? ComponentProps
: never;

// Utility methods for ApplyClassComponentDefaults
type ExtractDefaultProps<T> = T extends { defaultProps: infer D } ? D : never;
type ExtractProps<
C extends new (...args: any) => any,
IT = InstanceType<C>
> = IT extends Component<infer P> ? P : never;

/**
* Because of how TypeScript's LibraryManagedAttributes is designed to handle defaultProps (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#support-for-defaultprops-in-jsx)
* we can't directly export the props definition as the defaulted values are not made optional,
* because it isn't processed by LibraryManagedAttributes. To get around this, we:
* - remove the props which have default values applied
* - export (Props - Defaults) & Partial<Defaults>
*/
export type ApplyClassComponentDefaults<
C extends new (...args: any) => any,
D = ExtractDefaultProps<C>,
P = ExtractProps<C>
> = Omit<P, keyof D> & Partial<D>;

/*
https://github.com/Microsoft/TypeScript/issues/28339
Problem: Pick and Omit do not distribute over union types, which manifests when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ exports[`EuiDatePicker is rendered 1`] = `
className="euiDatePicker euiDatePicker--shadow"
>
<EuiFormControlLayout
clear={null}
fullWidth={false}
icon="calendar"
isLoading={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
import { requiredProps, takeMountedSnapshot } from '../../test';
import Moment from 'moment';
import moment from 'moment';

import { EuiDatePicker } from './date_picker';
import { EuiContext } from '../context';

describe('EuiDatePicker', () => {
test('is rendered', () => {
const component = shallow(<EuiDatePicker {...requiredProps} />);
const component = shallow<EuiDatePicker>(
<EuiDatePicker {...requiredProps} />
);

expect(component).toMatchSnapshot(); // snapshot of wrapping dom
expect(component.find('ContextConsumer').shallow()).toMatchSnapshot(); // snapshot of DatePicker usage
});

describe('localization', () => {
const selectedDate = new Moment('2019-07-01T00:00:00-0700').locale('fr');
const selectedDate = moment('2019-07-01T00:00:00-0700').locale('fr');

test('accepts the locale prop', () => {
const component = mount(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,97 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React, { Component, MouseEventHandler, Ref } from 'react';
import classNames from 'classnames';

import moment from 'moment';
import { ReactDatePicker as DatePicker } from '../../../packages';
import { Moment } from 'moment'; // eslint-disable-line import/named

import { EuiFormControlLayout } from '../form/form_control_layout';

import { EuiValidatableControl } from '../form/validatable_control';
import { EuiFormControlLayout, EuiValidatableControl } from '../form';
import { EuiFormControlLayoutIconsProps } from '../form/form_control_layout/form_control_layout_icons';

import { EuiErrorBoundary } from '../error_boundary';

import { EuiI18nConsumer } from '../context';
import { ApplyClassComponentDefaults, CommonProps } from '../common';

// @ts-ignore the type is provided by react-datepicker.d.ts
import { ReactDatePicker as _ReactDatePicker } from '../../../packages';
import ReactDatePicker, { ReactDatePickerProps } from './react-datepicker'; // eslint-disable-line import/no-unresolved

export const euiDatePickerDefaultDateFormat = 'MM/DD/YYYY';
export const euiDatePickerDefaultTimeFormat = 'hh:mm A';

export class EuiDatePicker extends Component {
const DatePicker = _ReactDatePicker as typeof ReactDatePicker;

interface EuiExtendedDatePickerProps extends ReactDatePickerProps {
/**
* Applies classes to the numbered days provided. Check docs for example.
*/
dayClassName?: (date: Moment) => string | null;

/**
* Makes the input full width
*/
fullWidth?: boolean;

/**
* ref for the ReactDatePicker instance
*/
inputRef: Ref<Component<ReactDatePickerProps, any, any>>;

/**
* Provides styling to the input when invalid
*/
isInvalid?: boolean;

/**
* Provides styling to the input when loading
*/
isLoading?: boolean;

/**
* What to do when the input is cleared by the x icon
*/
onClear?: MouseEventHandler<HTMLButtonElement>;

/**
* Opens to this date (in moment format) on first press, regardless of selection
*/
openToDate?: Moment;

/**
* Shows only when no date is selected
*/
placeholder?: string;

/**
* Can turn the shadow off if using the inline prop
*/
shadow?: boolean;

/**
* Show the icon in input
*/
showIcon?: boolean;
}

type _EuiDatePickerProps = CommonProps & EuiExtendedDatePickerProps;

export type EuiDatePickerProps = ApplyClassComponentDefaults<
typeof EuiDatePicker
>;

export class EuiDatePicker extends Component<_EuiDatePickerProps> {
static defaultProps = {
adjustDateOnChange: true,
dateFormat: euiDatePickerDefaultDateFormat,
fullWidth: false,
inputRef: () => {},
isLoading: false,
shadow: true,
shouldCloseOnSelect: true,
showIcon: true,
showTimeSelect: false,
timeFormat: euiDatePickerDefaultTimeFormat,
};

render() {
const {
adjustDateOnChange,
Expand All @@ -27,7 +102,7 @@ export class EuiDatePicker extends Component {
dayClassName,
disabled,
excludeDates,
filterDates,
filterDate,
fullWidth,
injectTimes,
inline,
Expand Down Expand Up @@ -72,9 +147,9 @@ export class EuiDatePicker extends Component {
className
);

let optionalIcon;
let optionalIcon: EuiFormControlLayoutIconsProps['icon'];
if (inline || customInput || !showIcon) {
optionalIcon = null;
optionalIcon = undefined;
} else if (showTimeSelectOnly) {
optionalIcon = 'clock';
} else {
Expand Down Expand Up @@ -130,7 +205,7 @@ export class EuiDatePicker extends Component {
<EuiFormControlLayout
icon={optionalIcon}
fullWidth={fullWidth}
clear={selected && onClear ? { onClick: onClear } : null}
clear={selected && onClear ? { onClick: onClear } : undefined}
isLoading={isLoading}>
<EuiValidatableControl isInvalid={isInvalid}>
<EuiI18nConsumer>
Expand All @@ -145,7 +220,7 @@ export class EuiDatePicker extends Component {
dayClassName={dayClassName}
disabled={disabled}
excludeDates={excludeDates}
filterDates={filterDates}
filterDate={filterDate}
injectTimes={injectTimes}
inline={inline}
locale={locale || contextLocale}
Expand All @@ -167,7 +242,7 @@ export class EuiDatePicker extends Component {
timeFormat={timeFormat}
utcOffset={utcOffset}
yearDropdownItemNumber={7}
accessibleMode={true}
accessibleMode
{...rest}
/>
);
Expand All @@ -180,136 +255,3 @@ export class EuiDatePicker extends Component {
);
}
}

EuiDatePicker.propTypes = {
/**
* Whether changes to Year and Month (via dropdowns) should trigger `onChange`
*/
adjustDateOnChange: PropTypes.bool,
/**
* Optional class added to the calendar portion of datepicker
*/
calendarClassName: PropTypes.string,

/**
* Added to the actual input of the calendar
*/
className: PropTypes.string,
/**
* Replaces the input with any node, like a button
*/
customInput: PropTypes.node,
/**
* Accepts any moment format string
*/
dateFormat: PropTypes.string,
/**
* Applies classes to the numbered days provided. Check docs for example.
*/
dayClassName: PropTypes.func,

/**
* Array of dates allowed. Check docs for example.
*/
filterDates: PropTypes.array,
/**
* Makes the input full width
*/
fullWidth: PropTypes.bool,
/**
* Adds additional times to the time selector other then :30 increments
*/
injectTimes: PropTypes.array,
/**
* Applies ref to the input
*/
inputRef: PropTypes.func,
/**
* Provides styling to the input when invalid
*/
isInvalid: PropTypes.bool,
/**
* Provides styling to the input when loading
*/
isLoading: PropTypes.bool,
/**
* Switches the locale / display. "en-us", "zn-ch"...etc
*/
locale: PropTypes.string,
/**
* The max date accepted (in moment format) as a selection
*/
maxDate: PropTypes.instanceOf(moment),
/**
* The max time accepted (in moment format) as a selection
*/
maxTime: PropTypes.instanceOf(moment),
/**
* The min date accepted (in moment format) as a selection
*/
minDate: PropTypes.instanceOf(moment),
/**
* The min time accepted (in moment format) as a selection
*/
minTime: PropTypes.instanceOf(moment),
/**
* What to do when the input changes
*/
onChange: PropTypes.func,
/**
* What to do when the input is cleared by the x icon
*/
onClear: PropTypes.func,
/**
* Opens to this date (in moment format) on first press, regardless of selection
*/
openToDate: PropTypes.instanceOf(moment),
/**
* Shows only when no date is selected
*/
placeholder: PropTypes.string,
/**
* Class applied to the popup, when inline is false
*/
popperClassName: PropTypes.string,
/**
* The selected datetime (in moment format)
*/
selected: PropTypes.instanceOf(moment),
/**
* Can turn the shadow off if using the inline prop
*/
shadow: PropTypes.bool,
/**
* Will close the popup on selection
*/
shouldCloseOnSelect: PropTypes.bool,
/**
* Show the icon in input
*/
showIcon: PropTypes.bool,
/**
* Show the time selection alongside the calendar
*/
showTimeSelect: PropTypes.bool,
/**
* Only show the time selector, not the calendar
*/
showTimeSelectOnly: PropTypes.bool,
/**
* The format of the time within the selector, in moment notation
*/
timeFormat: PropTypes.string,
};

EuiDatePicker.defaultProps = {
adjustDateOnChange: true,
dateFormat: euiDatePickerDefaultDateFormat,
fullWidth: false,
isLoading: false,
shadow: true,
shouldCloseOnSelect: true,
showIcon: true,
showTimeSelect: false,
timeFormat: euiDatePickerDefaultTimeFormat,
};
Loading

0 comments on commit cddaf28

Please sign in to comment.