From 15cba559b97e3e2bd756645d06d6b188b8c0979e Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 20 Feb 2020 21:10:54 -0500 Subject: [PATCH] converts all DatePicker family of components to TypeScript --- ...te_picker.test.js => date_picker.test.tsx} | 0 .../{date_picker.js => date_picker.tsx} | 225 +++++-------- ...nge.test.js => date_picker_range.test.tsx} | 0 ..._picker_range.js => date_picker_range.tsx} | 66 ++-- src/components/date_picker/index.d.ts | 104 ------ src/components/date_picker/index.js | 10 - src/components/date_picker/index.ts | 25 ++ .../date_picker/react-datepicker.d.ts | 75 ++++- .../super_date_picker/async_interval.js | 22 -- ...nterval.test.js => async_interval.test.ts} | 13 +- .../super_date_picker/async_interval.ts | 25 ++ .../{date_modes.js => date_modes.ts} | 18 +- .../date_popover/absolute_tab.js | 101 ------ .../date_popover/absolute_tab.tsx | 125 +++++++ ...over_button.js => date_popover_button.tsx} | 48 +-- ...er_content.js => date_popover_content.tsx} | 47 +-- .../super_date_picker/date_popover/index.ts | 10 + .../{relative_tab.js => relative_tab.tsx} | 105 +++--- .../date_picker/super_date_picker/index.js | 5 - .../date_picker/super_date_picker/index.ts | 15 + ...ration.test.js => pretty_duration.test.ts} | 0 ...{pretty_duration.js => pretty_duration.ts} | 34 +- ...anges.js => commonly_used_time_ranges.tsx} | 32 +- .../quick_select_popover/index.ts | 14 + ...k_select.test.js => quick_select.test.tsx} | 0 .../{quick_select.js => quick_select.tsx} | 99 +++--- ....test.js => quick_select_popover.test.tsx} | 7 +- ...ct_popover.js => quick_select_popover.tsx} | 62 ++-- .../{recently_used.js => recently_used.tsx} | 29 +- ...fresh_interval.js => refresh_interval.tsx} | 123 +++---- .../super_date_picker/relative_options.ts | 13 +- ...e_utils.test.js => relative_utils.test.ts} | 0 .../{relative_utils.js => relative_utils.ts} | 21 +- ...ker.test.js => super_date_picker.test.tsx} | 0 ...r_date_picker.js => super_date_picker.tsx} | 315 ++++++++++-------- ...n.test.js => super_update_button.test.tsx} | 0 ...date_button.js => super_update_button.tsx} | 39 ++- .../super_date_picker/time_units.js | 19 -- .../super_date_picker/time_units.ts | 21 ++ .../date_picker/super_date_picker/types.js | 17 - src/components/date_picker/types.ts | 70 ++++ src/components/index.d.ts | 1 - 42 files changed, 1060 insertions(+), 895 deletions(-) rename src/components/date_picker/{date_picker.test.js => date_picker.test.tsx} (100%) rename src/components/date_picker/{date_picker.js => date_picker.tsx} (69%) rename src/components/date_picker/{date_picker_range.test.js => date_picker_range.test.tsx} (100%) rename src/components/date_picker/{date_picker_range.js => date_picker_range.tsx} (72%) delete mode 100644 src/components/date_picker/index.d.ts delete mode 100644 src/components/date_picker/index.js create mode 100644 src/components/date_picker/index.ts delete mode 100644 src/components/date_picker/super_date_picker/async_interval.js rename src/components/date_picker/super_date_picker/{async_interval.test.js => async_interval.test.ts} (91%) create mode 100644 src/components/date_picker/super_date_picker/async_interval.ts rename src/components/date_picker/super_date_picker/{date_modes.js => date_modes.ts} (60%) delete mode 100644 src/components/date_picker/super_date_picker/date_popover/absolute_tab.js create mode 100644 src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx rename src/components/date_picker/super_date_picker/date_popover/{date_popover_button.js => date_popover_button.tsx} (63%) rename src/components/date_picker/super_date_picker/date_popover/{date_popover_content.js => date_popover_content.tsx} (74%) create mode 100644 src/components/date_picker/super_date_picker/date_popover/index.ts rename src/components/date_picker/super_date_picker/date_popover/{relative_tab.js => relative_tab.tsx} (62%) delete mode 100644 src/components/date_picker/super_date_picker/index.js create mode 100644 src/components/date_picker/super_date_picker/index.ts rename src/components/date_picker/super_date_picker/{pretty_duration.test.js => pretty_duration.test.ts} (100%) rename src/components/date_picker/super_date_picker/{pretty_duration.js => pretty_duration.ts} (75%) rename src/components/date_picker/super_date_picker/quick_select_popover/{commonly_used_time_ranges.js => commonly_used_time_ranges.tsx} (69%) create mode 100644 src/components/date_picker/super_date_picker/quick_select_popover/index.ts rename src/components/date_picker/super_date_picker/quick_select_popover/{quick_select.test.js => quick_select.test.tsx} (100%) rename src/components/date_picker/super_date_picker/quick_select_popover/{quick_select.js => quick_select.tsx} (77%) rename src/components/date_picker/super_date_picker/quick_select_popover/{quick_select_popover.test.js => quick_select_popover.test.tsx} (85%) rename src/components/date_picker/super_date_picker/quick_select_popover/{quick_select_popover.js => quick_select_popover.tsx} (80%) rename src/components/date_picker/super_date_picker/quick_select_popover/{recently_used.js => recently_used.tsx} (70%) rename src/components/date_picker/super_date_picker/quick_select_popover/{refresh_interval.js => refresh_interval.tsx} (59%) rename src/components/date_picker/super_date_picker/{relative_utils.test.js => relative_utils.test.ts} (100%) rename src/components/date_picker/super_date_picker/{relative_utils.js => relative_utils.ts} (74%) rename src/components/date_picker/super_date_picker/{super_date_picker.test.js => super_date_picker.test.tsx} (100%) rename src/components/date_picker/super_date_picker/{super_date_picker.js => super_date_picker.tsx} (66%) rename src/components/date_picker/super_date_picker/{super_update_button.test.js => super_update_button.test.tsx} (100%) rename src/components/date_picker/super_date_picker/{super_update_button.js => super_update_button.tsx} (79%) delete mode 100644 src/components/date_picker/super_date_picker/time_units.js create mode 100644 src/components/date_picker/super_date_picker/time_units.ts delete mode 100644 src/components/date_picker/super_date_picker/types.js create mode 100644 src/components/date_picker/types.ts diff --git a/src/components/date_picker/date_picker.test.js b/src/components/date_picker/date_picker.test.tsx similarity index 100% rename from src/components/date_picker/date_picker.test.js rename to src/components/date_picker/date_picker.test.tsx diff --git a/src/components/date_picker/date_picker.js b/src/components/date_picker/date_picker.tsx similarity index 69% rename from src/components/date_picker/date_picker.js rename to src/components/date_picker/date_picker.tsx index 6d97c77acf55..b2fb9843da0b 100644 --- a/src/components/date_picker/date_picker.js +++ b/src/components/date_picker/date_picker.tsx @@ -1,8 +1,7 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, MouseEventHandler } from 'react'; import classNames from 'classnames'; -import moment from 'moment'; +import { Moment } from 'moment'; // eslint-disable-line import/named import { ReactDatePicker as DatePicker } from '../../../packages'; import { EuiFormControlLayout } from '../form/form_control_layout'; @@ -12,11 +11,86 @@ import { EuiValidatableControl } from '../form/validatable_control'; import { EuiErrorBoundary } from '../error_boundary'; import { EuiI18nConsumer } from '../context'; +import { CommonProps } from '../common'; + +import ReactDatePicker, { ReactDatePickerProps } from './react-datepicker'; // eslint-disable-line import/no-unresolved +import { EuiFormControlLayoutIconsProps } from '../form/form_control_layout/form_control_layout_icons'; export const euiDatePickerDefaultDateFormat = 'MM/DD/YYYY'; export const euiDatePickerDefaultTimeFormat = 'hh:mm A'; -export class EuiDatePicker extends Component { +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; + + /** + * Adds additional times to the time selector other then :30 increments + */ + injectTimes?: Moment[]; // added here because the type is missing in @types/react-datepicker@1.8.0 + + /** + * Applies ref to the input + */ + inputRef?: React.Ref; + + /** + * 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; + + /** + * 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; +} + +export type EuiDatePickerProps = CommonProps & EuiExtendedDatePickerProps; + +export class EuiDatePicker extends Component { + defaultProps = { + adjustDateOnChange: true, + dateFormat: euiDatePickerDefaultDateFormat, + fullWidth: false, + isLoading: false, + shadow: true, + shouldCloseOnSelect: true, + showIcon: true, + showTimeSelect: false, + timeFormat: euiDatePickerDefaultTimeFormat, + }; + render() { const { adjustDateOnChange, @@ -27,7 +101,7 @@ export class EuiDatePicker extends Component { dayClassName, disabled, excludeDates, - filterDates, + filterDate, fullWidth, injectTimes, inline, @@ -72,9 +146,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 { @@ -130,7 +204,7 @@ export class EuiDatePicker extends Component { @@ -145,7 +219,7 @@ export class EuiDatePicker extends Component { dayClassName={dayClassName} disabled={disabled} excludeDates={excludeDates} - filterDates={filterDates} + filterDate={filterDate} injectTimes={injectTimes} inline={inline} locale={locale || contextLocale} @@ -180,136 +254,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, -}; diff --git a/src/components/date_picker/date_picker_range.test.js b/src/components/date_picker/date_picker_range.test.tsx similarity index 100% rename from src/components/date_picker/date_picker_range.test.js rename to src/components/date_picker/date_picker_range.test.tsx diff --git a/src/components/date_picker/date_picker_range.js b/src/components/date_picker/date_picker_range.tsx similarity index 72% rename from src/components/date_picker/date_picker_range.js rename to src/components/date_picker/date_picker_range.tsx index ac8997fb7220..9a45ad1f78f8 100644 --- a/src/components/date_picker/date_picker_range.js +++ b/src/components/date_picker/date_picker_range.tsx @@ -1,16 +1,41 @@ -import React, { cloneElement, Fragment } from 'react'; -import PropTypes from 'prop-types'; +import React, { cloneElement, Fragment, FC, ReactElement } from 'react'; import classNames from 'classnames'; import { EuiText } from '../text'; -import { EuiIcon } from '../icon'; +import { EuiIcon, IconType } from '../icon'; +import { CommonProps } from '../common'; +import { EuiDatePickerProps } from './date_picker'; -export const EuiDatePickerRange = ({ +export type EuiDatePickerRangeProps = CommonProps & { + /** + * The end date `EuiDatePicker` element + */ + endDateControl: ReactElement; + fullWidth?: boolean; + + /** + * Pass either an icon type or set to `false` to remove icon entirely + */ + iconType?: boolean | IconType; + + /** + * Won't apply any additional props to start and end date components + */ + isCustom?: boolean; + readOnly?: boolean; + + /** + * The start date `EuiDatePicker` element + */ + startDateControl: ReactElement; +}; + +export const EuiDatePickerRange: FC = ({ children, className, startDateControl, endDateControl, - iconType, + iconType = true, fullWidth, isCustom, readOnly, @@ -75,34 +100,3 @@ export const EuiDatePickerRange = ({ ); }; - -EuiDatePickerRange.propTypes = { - /** - * The start date `EuiDatePicker` element - */ - startDateControl: PropTypes.node.isRequired, - /** - * The end date `EuiDatePicker` element - */ - endDateControl: PropTypes.node.isRequired, - /** - * Pass either an icon type or set to `false` to remove icon entirely - */ - iconType: PropTypes.oneOfType([ - PropTypes.bool, - PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - ]), - fullWidth: PropTypes.bool, - /** - * Won't apply any additional props to start and end date components - */ - isCustom: PropTypes.bool, - /** - * Including any children will replace all innerds with the provided children - */ - children: PropTypes.node, -}; - -EuiDatePickerRange.defaultProps = { - iconType: true, -}; diff --git a/src/components/date_picker/index.d.ts b/src/components/date_picker/index.d.ts deleted file mode 100644 index ff810cec0beb..000000000000 --- a/src/components/date_picker/index.d.ts +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import { CommonProps } from '../common'; -import { IconType } from '../icon'; -import _ReactDatePicker, { - ReactDatePickerProps as _ReactDatePickerProps, -} from './react-datepicker'; // eslint-disable-line import/no-unresolved -import { Moment } from 'moment'; // eslint-disable-line import/named - -declare module '@elastic/eui' { - interface OnTimeChangeProps { - start: string; - end: string; - isInvalid: boolean; - isQuickSelection: boolean; - } - - interface OnRefreshProps { - start: string; - end: string; - refreshInterval: number; - } - - interface OnRefreshChangeProps { - isPaused: boolean; - refreshInterval: number; - } - - interface EuiExtendedDatePickerProps extends _ReactDatePickerProps { - fullWidth?: boolean; - isInvalid?: boolean; - isLoading?: boolean; - injectTimes?: Moment[]; // added here because the type is missing in @types/react-datepicker@1.8.0 - inputRef?: React.Ref; - placeholder?: string; - shadow?: boolean; - showIcon?: boolean; - } - - export type EuiDatePickerProps = CommonProps & EuiExtendedDatePickerProps; - export const EuiDatePicker: React.SFC; - - export type EuiDatePickerRangeProps = CommonProps & { - startDateControl: React.ReactElement; - endDateControl: React.ReactElement; - iconType?: boolean | IconType; - fullWidth?: boolean; - isCustom?: boolean; - }; - - export const EuiDatePickerRange: React.SFC; - - export interface EuiSuperDatePickerCommonRange { - start: string; - end: string; - label: string; - } - - export interface EuiSuperDatePickerRecentRange { - start: string; - end: string; - } - - export interface EuiSuperDatePickerQuickSelectPanel { - title: string; - content: React.ReactNode; - } - - export type EuiSuperDatePickerProps = CommonProps & { - isLoading?: boolean; - start?: string; - end?: string; - isPaused?: boolean; - refreshInterval?: number; - onTimeChange: (props: OnTimeChangeProps) => void; - onRefresh?: (props: OnRefreshProps) => void; - onRefreshChange?: (props: OnRefreshChangeProps) => void; - commonlyUsedRanges?: EuiSuperDatePickerCommonRange[]; - dateFormat?: string; - recentlyUsedRanges?: EuiSuperDatePickerRecentRange[]; - showUpdateButton?: boolean; - isAutoRefreshOnly?: boolean; - customQuickSelectPanels?: EuiSuperDatePickerQuickSelectPanel[]; - }; - - export const EuiSuperDatePicker: React.SFC; - - export const ReactDatePicker: typeof _ReactDatePicker; - export const ReactDatePickerProps: _ReactDatePickerProps; - - interface DurationRange { - start: string; - end: string; - label: string; - } - - export const commonDurationRanges: DurationRange[]; - - export function prettyDuration( - timeFrom: string, - timeTo: string, - quickRanges: DurationRange[], - dateFormat: string - ): string; -} diff --git a/src/components/date_picker/index.js b/src/components/date_picker/index.js deleted file mode 100644 index 69a0ec433e7d..000000000000 --- a/src/components/date_picker/index.js +++ /dev/null @@ -1,10 +0,0 @@ -export { EuiDatePicker } from './date_picker'; - -export { EuiDatePickerRange } from './date_picker_range'; - -export { - EuiSuperDatePicker, - EuiSuperUpdateButton, - prettyDuration, - commonDurationRanges, -} from './super_date_picker'; diff --git a/src/components/date_picker/index.ts b/src/components/date_picker/index.ts new file mode 100644 index 000000000000..dae3008b8aec --- /dev/null +++ b/src/components/date_picker/index.ts @@ -0,0 +1,25 @@ +export * from './super_date_picker'; + +export { EuiDatePicker, EuiDatePickerProps } from './date_picker'; + +export { + EuiDatePickerRange, + EuiDatePickerRangeProps, +} from './date_picker_range'; + +export { + DurationRange, + TimeUnitId, + TimeUnitFromNowId, + TimeUnitLabel, + TimeUnitLabelPlural, + AbsoluteDateMode, + RelativeDateMode, + NowDateMode, + DateMode, + ShortDate, + RelativeParts, + RelativeOption, + QuickSelect, + QuickSelectPanel, +} from './types'; diff --git a/src/components/date_picker/react-datepicker.d.ts b/src/components/date_picker/react-datepicker.d.ts index 81f19ce3cb1a..d8afa119ef99 100644 --- a/src/components/date_picker/react-datepicker.d.ts +++ b/src/components/date_picker/react-datepicker.d.ts @@ -15,15 +15,34 @@ import * as React from 'react'; import * as moment from 'moment'; export interface ReactDatePickerProps { + /** + * Whether changes to Year and Month (via dropdowns) should trigger `onChange` + */ adjustDateOnChange?: boolean; allowSameDay?: boolean; autoComplete?: string; autoFocus?: boolean; + + /** + * Optional class added to the calendar portion of datepicker + */ calendarClassName?: string; children?: React.ReactNode; + + /** + * Added to the actual input of the calendar + */ className?: string; + + /** + * Replaces the input with any node, like a button + */ customInput?: React.ReactNode; customInputRef?: string; + + /** + * Accepts any moment format string + */ dateFormat?: string | string[]; dateFormatCalendar?: string; dayClassName?(date: moment.Moment): string | null; @@ -43,17 +62,41 @@ export interface ReactDatePickerProps { includeTimes?: moment.Moment[]; inline?: boolean; isClearable?: boolean; - locale?: string; + + /** + * Switches the locale / display. "en-us", "zn-ch"...etc + */ + locale?: moment.LocaleSpecifier; + + /** + * The max date accepted (in moment format) as a selection + */ maxDate?: moment.Moment; + + /** + * The max time accepted (in moment format) as a selection + */ maxTime?: moment.Moment; + + /** + * The min date accepted (in moment format) as a selection + */ minDate?: moment.Moment; + + /** + * The min time accepted (in moment format) as a selection + */ minTime?: moment.Moment; monthsShown?: number; name?: string; onBlur?(event: React.FocusEvent): void; - onChange( - date: moment.Moment | null, - event: React.SyntheticEvent | undefined + + /** + * What to do when the input changes + */ + onChange?( + date: moment.Moment | string | null, + event?: React.SyntheticEvent | undefined ): void; onChangeRaw?(event: React.FocusEvent): void; onClickOutside?(event: React.MouseEvent): void; @@ -73,6 +116,10 @@ export interface ReactDatePickerProps { openToDate?: moment.Moment; peekNextMonth?: boolean; placeholderText?: string; + + /** + * Class applied to the popup, when inline is false + */ popperClassName?: string; popperContainer?(props: { children: React.ReactNode[] }): React.ReactNode; popperPlacement?: string; @@ -81,14 +128,30 @@ export interface ReactDatePickerProps { required?: boolean; scrollableMonthYearDropdown?: boolean; scrollableYearDropdown?: boolean; + + /** + * The selected datetime (in moment format) + */ selected?: moment.Moment | null; selectsEnd?: boolean; selectsStart?: boolean; + + /** + * Will close the popup on selection + */ shouldCloseOnSelect?: boolean; showDisabledMonthNavigation?: boolean; showMonthDropdown?: boolean; showMonthYearDropdown?: boolean; + + /** + * Show the time selection alongside the calendar + */ showTimeSelect?: boolean; + + /** + * Only show the time selector, not the calendar + */ showTimeSelectOnly?: boolean; showWeekNumbers?: boolean; showYearDropdown?: boolean; @@ -96,6 +159,10 @@ export interface ReactDatePickerProps { startOpen?: boolean; tabIndex?: number; timeCaption?: string; + + /** + * The format of the time within the selector, in moment notation + */ timeFormat?: string; timeIntervals?: number; title?: string; diff --git a/src/components/date_picker/super_date_picker/async_interval.js b/src/components/date_picker/super_date_picker/async_interval.js deleted file mode 100644 index 952add571ab7..000000000000 --- a/src/components/date_picker/super_date_picker/async_interval.js +++ /dev/null @@ -1,22 +0,0 @@ -export class AsyncInterval { - timeoutId = null; - isStopped = false; - - constructor(fn, refreshInterval) { - this.setAsyncInterval(fn, refreshInterval); - } - - setAsyncInterval = (fn, ms) => { - if (!this.isStopped) { - this.timeoutId = window.setTimeout(async () => { - this.__pendingFn = await fn(); - this.setAsyncInterval(fn, ms); - }, ms); - } - }; - - stop = () => { - this.isStopped = true; - window.clearTimeout(this.timeoutId); - }; -} diff --git a/src/components/date_picker/super_date_picker/async_interval.test.js b/src/components/date_picker/super_date_picker/async_interval.test.ts similarity index 91% rename from src/components/date_picker/super_date_picker/async_interval.test.js rename to src/components/date_picker/super_date_picker/async_interval.test.ts index 327267a61389..ab377540ab38 100644 --- a/src/components/date_picker/super_date_picker/async_interval.test.js +++ b/src/components/date_picker/super_date_picker/async_interval.test.ts @@ -14,9 +14,12 @@ describe('AsyncInterval', () => { // Advances time and awaits any pending promises after every 100ms // This helper makes it easier to advance time without worrying // whether tasks are still lingering on the event loop - async function andvanceTimerAndAwaitFn(instance, ms) { - const iterations = times(Math.floor(ms / 100)); - const remainder = ms % 100; + async function andvanceTimerAndAwaitFn( + instance: AsyncInterval, + milliseconds: number + ) { + const iterations = times(Math.floor(milliseconds / 100)); + const remainder = milliseconds % 100; // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const item of iterations) { await instance.__pendingFn; @@ -28,7 +31,7 @@ describe('AsyncInterval', () => { } describe('when creating a 1000ms interval', async () => { - let instance; + let instance: AsyncInterval; let spy; beforeEach(() => { spy = jest.fn(); @@ -65,7 +68,7 @@ describe('AsyncInterval', () => { }); describe('when creating a 1000ms interval that calls a fn that takes 2000ms to complete', async () => { - let instance; + let instance: AsyncInterval; let spy; beforeEach(() => { spy = jest.fn(async () => await sleep(2000)); diff --git a/src/components/date_picker/super_date_picker/async_interval.ts b/src/components/date_picker/super_date_picker/async_interval.ts new file mode 100644 index 000000000000..00a3f78d1d7c --- /dev/null +++ b/src/components/date_picker/super_date_picker/async_interval.ts @@ -0,0 +1,25 @@ +export class AsyncInterval { + timeoutId: number | null = null; + isStopped = false; + __pendingFn: Function = () => {}; + + constructor(fn: Function, refreshInterval: number) { + this.setAsyncInterval(fn, refreshInterval); + } + + setAsyncInterval = (fn: Function, milliseconds: number) => { + if (!this.isStopped) { + this.timeoutId = window.setTimeout(async () => { + this.__pendingFn = await fn(); + this.setAsyncInterval(fn, milliseconds); + }, milliseconds); + } + }; + + stop = () => { + this.isStopped = true; + if (this.timeoutId !== null) { + window.clearTimeout(this.timeoutId); + } + }; +} diff --git a/src/components/date_picker/super_date_picker/date_modes.js b/src/components/date_picker/super_date_picker/date_modes.ts similarity index 60% rename from src/components/date_picker/super_date_picker/date_modes.js rename to src/components/date_picker/super_date_picker/date_modes.ts index 16f9924bf0a5..c8a1a1ba5e94 100644 --- a/src/components/date_picker/super_date_picker/date_modes.js +++ b/src/components/date_picker/super_date_picker/date_modes.ts @@ -3,14 +3,24 @@ import { parseRelativeParts, toRelativeStringFromParts, } from './relative_utils'; +import { + AbsoluteDateMode, + RelativeDateMode, + NowDateMode, + ShortDate, +} from '../types'; -export const DATE_MODES = { +export const DATE_MODES: { + ABSOLUTE: AbsoluteDateMode; + RELATIVE: RelativeDateMode; + NOW: NowDateMode; +} = { ABSOLUTE: 'absolute', RELATIVE: 'relative', NOW: 'now', }; -export function getDateMode(value) { +export function getDateMode(value: ShortDate) { if (value === 'now') { return DATE_MODES.NOW; } @@ -22,7 +32,7 @@ export function getDateMode(value) { return DATE_MODES.ABSOLUTE; } -export function toAbsoluteString(value, roundUp) { +export function toAbsoluteString(value: string, roundUp: boolean) { const valueAsMoment = dateMath.parse(value, { roundUp }); if (!valueAsMoment) { return value; @@ -30,6 +40,6 @@ export function toAbsoluteString(value, roundUp) { return valueAsMoment.toISOString(); } -export function toRelativeString(value) { +export function toRelativeString(value: string) { return toRelativeStringFromParts(parseRelativeParts(value)); } diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js deleted file mode 100644 index ee106752024c..000000000000 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.js +++ /dev/null @@ -1,101 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; - -import moment from 'moment'; - -import dateMath from '@elastic/datemath'; - -import { EuiDatePicker } from '../../date_picker'; -import { EuiFormRow, EuiFieldText, EuiFormLabel } from '../../../form'; -import { toSentenceCase } from '../../../../services/string/to_case'; - -export class EuiAbsoluteTab extends Component { - constructor(props) { - super(props); - - const parsedValue = dateMath.parse(props.value, { roundUp: props.roundUp }); - const valueAsMoment = - parsedValue && parsedValue.isValid() ? parsedValue : moment(); - const sentenceCasedPosition = toSentenceCase(props.position); - - this.state = { - valueAsMoment, - textInputValue: valueAsMoment - .locale(this.props.locale || 'en') - .format(this.props.dateFormat), - isTextInvalid: false, - sentenceCasedPosition, - }; - } - - handleChange = date => { - this.props.onChange(date.toISOString()); - this.setState({ - valueAsMoment: date, - textInputValue: date.format(this.props.dateFormat), - isTextInvalid: false, - }); - }; - - handleTextChange = evt => { - const date = moment(evt.target.value, this.props.dateFormat, true); - const updatedState = { - textInputValue: evt.target.value, - isTextInvalid: !date.isValid(), - }; - if (date.isValid()) { - this.props.onChange(date.toISOString()); - updatedState.valueAsMoment = date; - } - - this.setState(updatedState); - }; - - render() { - return ( -
- - - - {this.state.sentenceCasedPosition} date - - } - /> - -
- ); - } -} - -EuiAbsoluteTab.propTypes = { - dateFormat: PropTypes.string.isRequired, - timeFormat: PropTypes.string.isRequired, - locale: PropTypes.string, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - roundUp: PropTypes.bool.isRequired, - position: PropTypes.oneOf(['start', 'end']), -}; diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx new file mode 100644 index 000000000000..ccd36bf0d80b --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.tsx @@ -0,0 +1,125 @@ +import React, { Component, ChangeEventHandler } from 'react'; + +import moment, { Moment, LocaleSpecifier } from 'moment'; // eslint-disable-line import/named + +import dateMath from '@elastic/datemath'; + +import { EuiDatePicker } from '../../date_picker'; +import { EuiFormRow } from '../../../form/form_row'; +import { EuiFieldText } from '../../../form/field_text'; +import { EuiFormLabel } from '../../../form/form_label'; +import { toSentenceCase } from '../../../../services/string/to_case'; +import { ReactDatePickerProps } from '../../react-datepicker'; // eslint-disable-line import/no-unresolved + +export interface EuiAbsoluteTabProps { + dateFormat: string; + timeFormat: string; + locale?: LocaleSpecifier; + value: string; + onChange: ReactDatePickerProps['onChange']; + roundUp: boolean; + position: 'start' | 'end'; +} + +interface EuiAbsoluteTabState { + isTextInvalid: boolean; + sentenceCasedPosition: string; + textInputValue: string; + valueAsMoment: Moment | null; +} + +export class EuiAbsoluteTab extends Component< + EuiAbsoluteTabProps, + EuiAbsoluteTabState +> { + state: EuiAbsoluteTabState; + + constructor(props: EuiAbsoluteTabProps) { + super(props); + + const sentenceCasedPosition = toSentenceCase(props.position); + + const parsedValue = dateMath.parse(props.value, { roundUp: props.roundUp }); + const valueAsMoment = + parsedValue && parsedValue.isValid() ? parsedValue : moment(); + + const textInputValue = valueAsMoment + .locale(this.props.locale || 'en') + .format(this.props.dateFormat); + + this.state = { + isTextInvalid: false, + sentenceCasedPosition, + textInputValue, + valueAsMoment, + }; + } + + handleChange: ReactDatePickerProps['onChange'] = (date, event) => { + if (date === null) { + return; + } + const dateMoment = moment(date); + this.props.onChange(date, event); + this.setState({ + valueAsMoment: dateMoment, + textInputValue: dateMoment.format(this.props.dateFormat), + isTextInvalid: false, + }); + }; + + handleTextChange: ChangeEventHandler = event => { + const valueAsMoment = moment( + event.target.value, + this.props.dateFormat, + true + ); + const dateIsValid = valueAsMoment.isValid(); + if (dateIsValid) { + this.props.onChange(valueAsMoment, event); + } + this.setState({ + textInputValue: event.target.value as string, + isTextInvalid: !dateIsValid, + valueAsMoment: dateIsValid ? valueAsMoment : null, + }); + }; + + render() { + return ( +
+ + + + {this.state.sentenceCasedPosition} date + + } + /> + +
+ ); + } +} diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx similarity index 63% rename from src/components/date_picker/super_date_picker/date_popover/date_popover_button.js rename to src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx index 74868cb75854..5b17806cc1d1 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_button.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_button.tsx @@ -1,13 +1,34 @@ -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { FC, ButtonHTMLAttributes, MouseEventHandler } from 'react'; import classNames from 'classnames'; -import { EuiPopover } from '../../../popover'; +import { EuiPopover, EuiPopoverProps } from '../../../popover'; import { formatTimeString } from '../pretty_duration'; -import { EuiDatePopoverContent } from './date_popover_content'; +import { + EuiDatePopoverContent, + EuiDatePopoverContentProps, +} from './date_popover_content'; +import { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named -export function EuiDatePopoverButton(props) { +export interface EuiDatePopoverButtonProps { + className?: string; + buttonProps?: ButtonHTMLAttributes; + dateFormat: string; + isDisabled?: boolean; + isInvalid?: boolean; + isOpen: boolean; + needsUpdating?: boolean; + locale?: LocaleSpecifier; + onChange: EuiDatePopoverContentProps['onChange']; + onPopoverClose: EuiPopoverProps['closePopover']; + onPopoverToggle: MouseEventHandler; + position: 'start' | 'end'; + roundUp?: boolean; + timeFormat: string; + value: string; +} + +export const EuiDatePopoverButton: FC = props => { const { position, isDisabled, @@ -77,19 +98,6 @@ export function EuiDatePopoverButton(props) { /> ); -} - -EuiDatePopoverButton.propTypes = { - position: PropTypes.oneOf(['start', 'end']), - isInvalid: PropTypes.bool, - isDisabled: PropTypes.bool, - needsUpdating: PropTypes.bool, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - dateFormat: PropTypes.string.isRequired, - timeFormat: PropTypes.string.isRequired, - roundUp: PropTypes.bool, - isOpen: PropTypes.bool.isRequired, - onPopoverToggle: PropTypes.func.isRequired, - onPopoverClose: PropTypes.func.isRequired, }; + +EuiDatePopoverButton.displayName = 'EuiDatePopoverButton'; diff --git a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx similarity index 74% rename from src/components/date_picker/super_date_picker/date_popover/date_popover_content.js rename to src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx index c9a101204ce4..9fe14c451e40 100644 --- a/src/components/date_picker/super_date_picker/date_popover/date_popover_content.js +++ b/src/components/date_picker/super_date_picker/date_popover/date_popover_content.tsx @@ -1,7 +1,6 @@ -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { FC } from 'react'; -import { EuiTabbedContent } from '../../../tabs'; +import { EuiTabbedContent, EuiTabbedContentProps } from '../../../tabs'; import { EuiText } from '../../../text'; import { EuiButton } from '../../../button'; @@ -14,17 +13,29 @@ import { toAbsoluteString, toRelativeString, } from '../date_modes'; +import { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named +import { ReactDatePickerProps } from '../../react-datepicker'; -export function EuiDatePopoverContent({ +export interface EuiDatePopoverContentProps { + value: string; + onChange: ReactDatePickerProps['onChange']; + roundUp?: boolean; + dateFormat: string; + timeFormat: string; + locale?: LocaleSpecifier; + position: 'start' | 'end'; +} + +export const EuiDatePopoverContent: FC = ({ value, - roundUp, + roundUp = false, onChange, dateFormat, timeFormat, locale, position, -}) { - const onTabClick = selectedTab => { +}) => { + const onTabClick: EuiTabbedContentProps['onTabClick'] = selectedTab => { switch (selectedTab.id) { case DATE_MODES.ABSOLUTE: onChange(toAbsoluteString(value, roundUp)); @@ -105,24 +116,16 @@ export function EuiDatePopoverContent({ className="euiDatePopoverContent" tabs={renderTabs()} autoFocus="selected" - initialSelectedTab={{ id: getDateMode(value) }} + initialSelectedTab={{ + // NOTE_TO_SELF(dimitri): behavior change? + content: , + id: getDateMode(value), + // NOTE_TO_SELF(dimitri): behavior change? + name: '', + }} onTabClick={onTabClick} size="s" expand /> ); -} - -EuiDatePopoverContent.propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - roundUp: PropTypes.bool, - dateFormat: PropTypes.string.isRequired, - timeFormat: PropTypes.string.isRequired, - locale: PropTypes.string, - position: PropTypes.oneOf(['start', 'end']), -}; - -EuiDatePopoverContent.defaultProps = { - roundUp: false, }; diff --git a/src/components/date_picker/super_date_picker/date_popover/index.ts b/src/components/date_picker/super_date_picker/date_popover/index.ts new file mode 100644 index 000000000000..4f86a5e3e623 --- /dev/null +++ b/src/components/date_picker/super_date_picker/date_popover/index.ts @@ -0,0 +1,10 @@ +export { EuiAbsoluteTab, EuiAbsoluteTabProps } from './absolute_tab'; +export { + EuiDatePopoverButton, + EuiDatePopoverButtonProps, +} from './date_popover_button'; +export { + EuiDatePopoverContent, + EuiDatePopoverContentProps, +} from './date_popover_content'; +export { EuiRelativeTab, EuiRelativeTabProps } from './relative_tab'; diff --git a/src/components/date_picker/super_date_picker/date_popover/relative_tab.js b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx similarity index 62% rename from src/components/date_picker/super_date_picker/date_popover/relative_tab.js rename to src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx index 2fe03b16a7b6..375b456ea7b9 100644 --- a/src/components/date_picker/super_date_picker/date_popover/relative_tab.js +++ b/src/components/date_picker/super_date_picker/date_popover/relative_tab.tsx @@ -1,18 +1,16 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { Component, ChangeEventHandler } from 'react'; import dateMath from '@elastic/datemath'; import { toSentenceCase } from '../../../../services/string/to_case'; import { htmlIdGenerator } from '../../../../services'; import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; -import { - EuiForm, - EuiFormRow, - EuiSelect, - EuiFieldNumber, - EuiFieldText, - EuiSwitch, - EuiFormLabel, -} from '../../../form'; +// @ts-ignore +import { EuiForm } from '../../../form/form'; +import { EuiFormRow } from '../../../form/form_row'; +import { EuiSelect } from '../../../form/select'; +import { EuiFieldNumber } from '../../../form/field_number'; +import { EuiFieldText } from '../../../form/field_text'; +import { EuiSwitch, EuiSwitchEvent } from '../../../form/switch'; +import { EuiFormLabel } from '../../../form/form_label'; import { EuiSpacer } from '../../../spacer'; import { timeUnits } from '../time_units'; @@ -23,58 +21,82 @@ import { } from '../relative_utils'; import { EuiScreenReaderOnly } from '../../../accessibility'; import { EuiI18n } from '../../../i18n'; +import { RelativeParts, TimeUnitId } from '../../types'; +import { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named +import { ReactDatePickerProps } from '../../react-datepicker'; // eslint-disable-line import/no-unresolved -export class EuiRelativeTab extends Component { - constructor(props) { - super(props); - const sentenceCasedPosition = toSentenceCase(props.position); +export interface EuiRelativeTabProps { + dateFormat: string; + locale?: LocaleSpecifier; + value: string; + onChange: ReactDatePickerProps['onChange']; + roundUp?: boolean; + position: 'start' | 'end'; +} - this.state = { - ...parseRelativeParts(this.props.value), - sentenceCasedPosition, - }; - } +interface EuiRelativeTabState + extends Pick { + count: number | null; + sentenceCasedPosition: string; +} + +export class EuiRelativeTab extends Component< + EuiRelativeTabProps, + EuiRelativeTabState +> { + state: EuiRelativeTabState = { + ...parseRelativeParts(this.props.value), + sentenceCasedPosition: toSentenceCase(this.props.position), + }; generateId = htmlIdGenerator(); - onCountChange = evt => { - const sanitizedValue = parseInt(evt.target.value, 10); + onCountChange: ChangeEventHandler = event => { + const sanitizedValue = parseInt(event.target.value, 10); this.setState( { - count: isNaN(sanitizedValue) ? '' : sanitizedValue, + count: isNaN(sanitizedValue) ? null : sanitizedValue, }, this.handleChange ); }; - onUnitChange = evt => { + onUnitChange: ChangeEventHandler = event => { this.setState( { - unit: evt.target.value, + unit: event.target.value, }, this.handleChange ); }; - onRoundChange = evt => { + onRoundChange = (event: EuiSwitchEvent) => { this.setState( { - round: evt.target.checked, + round: event.target.checked, }, this.handleChange ); }; handleChange = () => { - if (this.state.count === '' || this.state.count < 0) { + const { count, round, roundUnit, unit } = this.state; + if (count === null || count < 0) { return; } - this.props.onChange(toRelativeStringFromParts(this.state)); + const date = toRelativeStringFromParts({ + count, + round, + roundUnit, + unit, + }); + this.props.onChange(date); }; render() { + const { count, unit } = this.state; const relativeDateInputNumberDescriptionId = this.generateId(); - const isInvalid = this.state.count < 0; + const isInvalid = count !== null && count < 0; const parsedValue = dateMath.parse(this.props.value, { roundUp: this.props.roundUp, }); @@ -94,7 +116,7 @@ export class EuiRelativeTab extends Component { 'euiRelativeTab.numberInputLabel', ]} defaults={['Must be >= 0', 'Time span amount']}> - {([numberInputError, numberInputLabel]) => ( + {([numberInputError, numberInputLabel]: string[]) => ( @@ -103,7 +125,7 @@ export class EuiRelativeTab extends Component { aria-label={numberInputLabel} aria-describedby={relativeDateInputNumberDescriptionId} data-test-subj={'superDatePickerRelativeDateInputNumber'} - value={this.state.count} + value={count || undefined} onChange={this.onCountChange} isInvalid={isInvalid} /> @@ -115,14 +137,14 @@ export class EuiRelativeTab extends Component { - {unitInputLabel => ( + {(unitInputLabel: string) => ( @@ -134,8 +156,8 @@ export class EuiRelativeTab extends Component { - {roundingLabel => ( + values={{ unit: timeUnits[unit.substring(0, 1) as TimeUnitId] }}> + {(roundingLabel: string) => (

@@ -173,12 +195,3 @@ export class EuiRelativeTab extends Component { ); } } - -EuiRelativeTab.propTypes = { - dateFormat: PropTypes.string.isRequired, - locale: PropTypes.string, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - roundUp: PropTypes.bool, - position: PropTypes.oneOf(['start', 'end']), -}; diff --git a/src/components/date_picker/super_date_picker/index.js b/src/components/date_picker/super_date_picker/index.js deleted file mode 100644 index 978ec874cc60..000000000000 --- a/src/components/date_picker/super_date_picker/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { EuiSuperDatePicker } from './super_date_picker'; - -export { EuiSuperUpdateButton } from './super_update_button'; - -export { prettyDuration, commonDurationRanges } from './pretty_duration'; diff --git a/src/components/date_picker/super_date_picker/index.ts b/src/components/date_picker/super_date_picker/index.ts new file mode 100644 index 000000000000..b7143ade4e9c --- /dev/null +++ b/src/components/date_picker/super_date_picker/index.ts @@ -0,0 +1,15 @@ +export * from './date_popover'; +export * from './quick_select_popover'; +export { AsyncInterval } from './async_interval'; + +export { + EuiSuperDatePicker, + EuiSuperDatePickerProps, +} from './super_date_picker'; + +export { + EuiSuperUpdateButton, + EuiSuperUpdateButtonProps, +} from './super_update_button'; + +export { prettyDuration, commonDurationRanges } from './pretty_duration'; diff --git a/src/components/date_picker/super_date_picker/pretty_duration.test.js b/src/components/date_picker/super_date_picker/pretty_duration.test.ts similarity index 100% rename from src/components/date_picker/super_date_picker/pretty_duration.test.js rename to src/components/date_picker/super_date_picker/pretty_duration.test.ts diff --git a/src/components/date_picker/super_date_picker/pretty_duration.js b/src/components/date_picker/super_date_picker/pretty_duration.ts similarity index 75% rename from src/components/date_picker/super_date_picker/pretty_duration.js rename to src/components/date_picker/super_date_picker/pretty_duration.ts index 21a49ce872ff..246c11453b53 100644 --- a/src/components/date_picker/super_date_picker/pretty_duration.js +++ b/src/components/date_picker/super_date_picker/pretty_duration.ts @@ -1,12 +1,13 @@ import dateMath from '@elastic/datemath'; -import moment from 'moment'; +import moment, { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named import { timeUnits, timeUnitsPlural } from './time_units'; import { getDateMode, DATE_MODES } from './date_modes'; import { parseRelativeParts } from './relative_utils'; +import { DurationRange, TimeUnitId, ShortDate } from '../types'; const ISO_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; -export const commonDurationRanges = [ +export const commonDurationRanges: DurationRange[] = [ { start: 'now/d', end: 'now/d', label: 'Today' }, { start: 'now/w', end: 'now/w', label: 'This week' }, { start: 'now/M', end: 'now/M', label: 'This month' }, @@ -17,13 +18,13 @@ export const commonDurationRanges = [ { start: 'now/y', end: 'now', label: 'Year to date' }, ]; -function cantLookup(timeFrom, timeTo, dateFormat) { +function cantLookup(timeFrom: string, timeTo: string, dateFormat: string) { const displayFrom = formatTimeString(timeFrom, dateFormat); const displayTo = formatTimeString(timeTo, dateFormat, true); return `${displayFrom} to ${displayTo}`; } -function isRelativeToNow(timeFrom, timeTo) { +function isRelativeToNow(timeFrom: ShortDate, timeTo: ShortDate) { const fromDateMode = getDateMode(timeFrom); const toDateMode = getDateMode(timeTo); const isLast = @@ -34,10 +35,10 @@ function isRelativeToNow(timeFrom, timeTo) { } export function formatTimeString( - timeString, - dateFormat, + timeString: string, + dateFormat: string, roundUp = false, - locale = 'en' + locale: LocaleSpecifier = 'en' ) { const timeAsMoment = moment(timeString, ISO_FORMAT, true); if (timeAsMoment.isValid()) { @@ -56,7 +57,12 @@ export function formatTimeString( return timeString; } -export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { +export function prettyDuration( + timeFrom: ShortDate, + timeTo: ShortDate, + quickRanges: DurationRange[] = [], + dateFormat: string +) { const matchingQuickRange = quickRanges.find( ({ start: quickFrom, end: quickTo }) => { return timeFrom === quickFrom && timeTo === quickTo; @@ -76,14 +82,16 @@ export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { timeTense = 'Next'; relativeParts = parseRelativeParts(timeTo); } - const countTimeUnit = relativeParts.unit.substring(0, 1); + const countTimeUnit = relativeParts.unit.substring(0, 1) as TimeUnitId; const countTimeUnitFullName = relativeParts.count > 1 ? timeUnitsPlural[countTimeUnit] : timeUnits[countTimeUnit]; let text = `${timeTense} ${relativeParts.count} ${countTimeUnitFullName}`; if (relativeParts.round) { - text += ` rounded to the ${timeUnits[relativeParts.roundUnit]}`; + if (relativeParts.roundUnit) { + text += ` rounded to the ${timeUnits[relativeParts.roundUnit]}`; + } } return text; } @@ -91,7 +99,11 @@ export function prettyDuration(timeFrom, timeTo, quickRanges = [], dateFormat) { return cantLookup(timeFrom, timeTo, dateFormat); } -export function showPrettyDuration(timeFrom, timeTo, quickRanges = []) { +export function showPrettyDuration( + timeFrom: ShortDate, + timeTo: ShortDate, + quickRanges: DurationRange[] = [] +) { const matchingQuickRange = quickRanges.find( ({ start: quickFrom, end: quickTo }) => { return timeFrom === quickFrom && timeTo === quickTo; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.js b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.tsx similarity index 69% rename from src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.js rename to src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.tsx index 3331ab150234..4d58aa2aa8c2 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/commonly_used_time_ranges.tsx @@ -1,32 +1,37 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { commonlyUsedRangeShape } from '../types'; +import React, { FC } from 'react'; import { EuiI18n } from '../../../i18n'; import { EuiFlexGrid, EuiFlexItem } from '../../../flex'; import { EuiTitle } from '../../../title'; import { EuiLink } from '../../../link'; import { EuiHorizontalRule } from '../../../horizontal_rule'; import { htmlIdGenerator } from '../../../../services'; +import { DurationRange, ApplyTime } from '../../types'; const generateId = htmlIdGenerator(); -export function EuiCommonlyUsedTimeRanges({ applyTime, commonlyUsedRanges }) { +export interface EuiCommonlyUsedTimeRangesProps { + applyTime: ApplyTime; + commonlyUsedRanges: DurationRange[]; +} + +export const EuiCommonlyUsedTimeRanges: FC = ({ + applyTime, + commonlyUsedRanges, +}) => { const legendId = generateId(); const links = commonlyUsedRanges.map(({ start, end, label }) => { const applyCommonlyUsed = () => { applyTime({ start, end }); }; + const dataTestSubj = label + ? `superDatePickerCommonlyUsed_${label.replace(' ', '_')}` + : undefined; return ( - + {label} @@ -57,9 +62,6 @@ export function EuiCommonlyUsedTimeRanges({ applyTime, commonlyUsedRanges }) { ); -} - -EuiCommonlyUsedTimeRanges.propTypes = { - applyTime: PropTypes.func.isRequired, - commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, }; + +EuiCommonlyUsedTimeRanges.displayName = 'EuiCommonlyUsedTimeRanges'; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/index.ts b/src/components/date_picker/super_date_picker/quick_select_popover/index.ts new file mode 100644 index 000000000000..d5bacaf91638 --- /dev/null +++ b/src/components/date_picker/super_date_picker/quick_select_popover/index.ts @@ -0,0 +1,14 @@ +export { + EuiCommonlyUsedTimeRanges, + EuiCommonlyUsedTimeRangesProps, +} from './commonly_used_time_ranges'; +export { + EuiQuickSelectPopover, + EuiQuickSelectPopoverProps, +} from './quick_select_popover'; +export { EuiQuickSelect, EuiQuickSelectProps } from './quick_select'; +export { EuiRecentlyUsed, EuiRecentlyUsedProps } from './recently_used'; +export { + EuiRefreshInterval, + EuiRefreshIntervalProps, +} from './refresh_interval'; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.tsx similarity index 100% rename from src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.js rename to src/components/date_picker/super_date_picker/quick_select_popover/quick_select.test.tsx diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx similarity index 77% rename from src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js rename to src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx index 082760bd7bcd..d9437d85e1c6 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select.tsx @@ -1,17 +1,19 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { Component, ChangeEventHandler } from 'react'; import moment from 'moment'; import dateMath from '@elastic/datemath'; import { htmlIdGenerator } from '../../../../services'; import { EuiButton, EuiButtonIcon } from '../../../button'; import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; import { EuiSpacer } from '../../../spacer'; -import { EuiSelect, EuiFieldNumber } from '../../../form'; +import { EuiSelect } from '../../../form/select'; +import { EuiFieldNumber } from '../../../form/field_number'; import { EuiToolTip } from '../../../tool_tip'; import { EuiHorizontalRule } from '../../../horizontal_rule'; import { EuiI18n } from '../../../i18n'; import { timeUnits } from '../time_units'; import { EuiScreenReaderOnly } from '../../../accessibility'; +import { ApplyTime, QuickSelect, TimeUnitId } from '../../types'; +import { keysOf } from '../../../common'; const LAST = 'last'; const NEXT = 'next'; @@ -20,40 +22,60 @@ const timeTenseOptions = [ { value: LAST, text: 'Last' }, { value: NEXT, text: 'Next' }, ]; -const timeUnitsOptions = Object.keys(timeUnits).map(key => { +const timeUnitsOptions = keysOf(timeUnits).map(key => { return { value: key, text: `${timeUnits[key]}s` }; }); -export class EuiQuickSelect extends Component { - constructor(props) { - super(props); +type EuiQuickSelectState = QuickSelect; - const { timeTense, timeValue, timeUnits } = this.props.prevQuickSelect; - this.state = { - timeTense: timeTense ? timeTense : LAST, - timeValue: timeValue ? timeValue : 15, - timeUnits: timeUnits ? timeUnits : 'm', - }; - } +export interface EuiQuickSelectProps { + applyTime: ApplyTime; + start: string; + end: string; + prevQuickSelect?: EuiQuickSelectState; +} + +export class EuiQuickSelect extends Component< + EuiQuickSelectProps, + EuiQuickSelectState +> { + defaultProps = { + prevQuickSelect: {}, + }; + + state: EuiQuickSelectState = { + timeTense: + this.props.prevQuickSelect && this.props.prevQuickSelect.timeTense + ? this.props.prevQuickSelect.timeTense + : LAST, + timeValue: + this.props.prevQuickSelect && this.props.prevQuickSelect.timeValue + ? this.props.prevQuickSelect.timeValue + : 15, + timeUnits: + this.props.prevQuickSelect && this.props.prevQuickSelect.timeUnits + ? this.props.prevQuickSelect.timeUnits + : 'm', + }; generateId = htmlIdGenerator(); - onTimeTenseChange = evt => { + onTimeTenseChange: ChangeEventHandler = event => { this.setState({ - timeTense: evt.target.value, + timeTense: event.target.value, }); }; - onTimeValueChange = evt => { - const sanitizedValue = parseInt(evt.target.value, 10); + onTimeValueChange: ChangeEventHandler = event => { + const sanitizedValue = parseInt(event.target.value, 10); this.setState({ - timeValue: isNaN(sanitizedValue) ? '' : sanitizedValue, + timeValue: isNaN(sanitizedValue) ? 0 : sanitizedValue, }); }; - onTimeUnitsChange = evt => { + onTimeUnitsChange: ChangeEventHandler = event => { this.setState({ - timeUnits: evt.target.value, + timeUnits: event.target.value as TimeUnitId, }); }; @@ -120,13 +142,17 @@ export class EuiQuickSelect extends Component { const { timeTense, timeValue, timeUnits } = this.state; const timeSelectionId = this.generateId(); const legendId = this.generateId(); + const matchedTimeUnit = timeUnitsOptions.find( + ({ value }) => value === timeUnits + ); + const timeUnit = matchedTimeUnit ? matchedTimeUnit.text : ''; return (
- {legendText => ( + {(legendText: string) => ( // Legend needs to be the first thing in a fieldset, but we want the visible title within the flex. // So we hide it, but allow screen readers to see it @@ -145,7 +171,7 @@ export class EuiQuickSelect extends Component { - {quickSelectTitle => ( + {(quickSelectTitle: string) => (
{quickSelectTitle}
@@ -158,7 +184,7 @@ export class EuiQuickSelect extends Component { - {previousLabel => ( + {(previousLabel: string) => ( - {nextLabel => ( + {(nextLabel: string) => ( - {tenseLabel => ( + {(tenseLabel: string) => ( - {valueLabel => ( + {(valueLabel: string) => ( - {unitLabel => ( + {(unitLabel: string) => ( + disabled={timeValue <= 0}> @@ -250,9 +276,7 @@ export class EuiQuickSelect extends Component { values={{ timeTense, timeValue, - timeUnit: timeUnitsOptions.find( - option => option.value === timeUnits - ).text, + timeUnit, }} />

@@ -261,14 +285,3 @@ export class EuiQuickSelect extends Component { ); } } - -EuiQuickSelect.propTypes = { - applyTime: PropTypes.func.isRequired, - start: PropTypes.string.isRequired, - end: PropTypes.string.isRequired, - prevQuickSelect: PropTypes.object, -}; - -EuiQuickSelect.defaultProps = { - prevQuickSelect: {}, -}; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.tsx similarity index 85% rename from src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.js rename to src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.tsx index 20a5f4952f2f..dd0275db6d01 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.test.tsx @@ -1,11 +1,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiQuickSelectPopover } from './quick_select_popover'; +import { + EuiQuickSelectPopover, + EuiQuickSelectPopoverProps, +} from './quick_select_popover'; const noop = () => {}; -const defaultProps = { +const defaultProps: EuiQuickSelectPopoverProps = { applyTime: noop, applyRefreshInterval: noop, start: 'now-15m', diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx similarity index 80% rename from src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js rename to src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx index 1b26436902fe..4c1ed339c2b8 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/quick_select_popover.tsx @@ -1,10 +1,4 @@ -import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; -import { - commonlyUsedRangeShape, - recentlyUsedRangeShape, - quickSelectPanelShape, -} from '../types'; import { EuiButtonEmpty } from '../../../button'; import { EuiIcon } from '../../../icon'; @@ -18,9 +12,39 @@ import { EuiQuickSelect } from './quick_select'; import { EuiCommonlyUsedTimeRanges } from './commonly_used_time_ranges'; import { EuiRecentlyUsed } from './recently_used'; import { EuiRefreshInterval } from './refresh_interval'; +import { + DurationRange, + ApplyRefreshInterval, + ApplyTime, + QuickSelect, + QuickSelectPanel, +} from '../../types'; + +export interface EuiQuickSelectPopoverProps { + applyRefreshInterval?: ApplyRefreshInterval; + applyTime: ApplyTime; + commonlyUsedRanges: DurationRange[]; + customQuickSelectPanels?: QuickSelectPanel[]; + dateFormat: string; + end: string; + isAutoRefreshOnly?: boolean; + isDisabled?: boolean; + isPaused?: boolean; + recentlyUsedRanges?: DurationRange[]; + refreshInterval: number; + start: string; +} -export class EuiQuickSelectPopover extends Component { - state = { +interface EuiQuickSelectPopoverState { + isOpen: boolean; + prevQuickSelect?: QuickSelect; +} + +export class EuiQuickSelectPopover extends Component< + EuiQuickSelectPopoverProps, + EuiQuickSelectPopoverState +> { + state: EuiQuickSelectPopoverState = { isOpen: false, }; @@ -34,7 +58,12 @@ export class EuiQuickSelectPopover extends Component { })); }; - applyTime = ({ start, end, quickSelect, keepPopoverOpen = false }) => { + applyTime: ApplyTime = ({ + start, + end, + quickSelect, + keepPopoverOpen = false, + }) => { this.props.applyTime({ start, end, @@ -141,18 +170,3 @@ export class EuiQuickSelectPopover extends Component { ); } } - -EuiQuickSelectPopover.propTypes = { - applyTime: PropTypes.func.isRequired, - start: PropTypes.string.isRequired, - end: PropTypes.string.isRequired, - applyRefreshInterval: PropTypes.func, - isDisabled: PropTypes.bool.isRequired, - isPaused: PropTypes.bool.isRequired, - refreshInterval: PropTypes.number.isRequired, - commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, - dateFormat: PropTypes.string.isRequired, - recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape).isRequired, - isAutoRefreshOnly: PropTypes.bool.isRequired, - customQuickSelectPanels: PropTypes.arrayOf(quickSelectPanelShape), -}; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.tsx similarity index 70% rename from src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js rename to src/components/date_picker/super_date_picker/quick_select_popover/recently_used.tsx index b949f596bf05..d68c6dfe7490 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/recently_used.tsx @@ -1,6 +1,4 @@ -import PropTypes from 'prop-types'; -import React, { Fragment } from 'react'; -import { commonlyUsedRangeShape, recentlyUsedRangeShape } from '../types'; +import React, { Fragment, FC } from 'react'; import { prettyDuration } from '../pretty_duration'; import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; @@ -9,13 +7,21 @@ import { EuiSpacer } from '../../../spacer'; import { EuiLink } from '../../../link'; import { EuiText } from '../../../text'; import { EuiHorizontalRule } from '../../../horizontal_rule'; +import { DurationRange, ApplyTime } from '../../types'; -export function EuiRecentlyUsed({ +export interface EuiRecentlyUsedProps { + applyTime: ApplyTime; + commonlyUsedRanges: DurationRange[]; + dateFormat: string; + recentlyUsedRanges?: DurationRange[]; +} + +export const EuiRecentlyUsed: FC = ({ applyTime, commonlyUsedRanges, dateFormat, - recentlyUsedRanges, -}) { + recentlyUsedRanges = [], +}) => { if (recentlyUsedRanges.length === 0) { return null; } @@ -47,15 +53,6 @@ export function EuiRecentlyUsed({ ); -} - -EuiRecentlyUsed.propTypes = { - applyTime: PropTypes.func.isRequired, - commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape).isRequired, - dateFormat: PropTypes.string.isRequired, - recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape), }; -EuiRecentlyUsed.defaultProps = { - recentlyUsedRanges: [], -}; +EuiRecentlyUsed.displayName = 'EuiRecentlyUsed'; diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.tsx similarity index 59% rename from src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js rename to src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.tsx index 469433436b11..e9d81e01920b 100644 --- a/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.js +++ b/src/components/date_picker/super_date_picker/quick_select_popover/refresh_interval.tsx @@ -1,31 +1,32 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { Component, ChangeEventHandler } from 'react'; import { timeUnits, timeUnitsPlural } from '../time_units'; import { EuiI18n } from '../../../i18n'; import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; import { EuiTitle } from '../../../title'; import { EuiSpacer } from '../../../spacer'; -import { EuiSelect, EuiFieldNumber } from '../../../form'; +import { EuiSelect } from '../../../form/select'; +import { EuiFieldNumber } from '../../../form/field_number'; import { EuiButton } from '../../../button'; import { htmlIdGenerator } from '../../../../services'; import { EuiScreenReaderOnly } from '../../../accessibility'; - -const refreshUnitsOptions = Object.keys(timeUnits) - .filter(timeUnit => { - return timeUnit === 'h' || timeUnit === 'm' || timeUnit === 's'; - }) - .map(timeUnit => { - return { value: timeUnit, text: timeUnitsPlural[timeUnit] }; - }); +import { + Milliseconds, + TimeUnitId, + RelativeOption, + ApplyRefreshInterval, +} from '../../types'; +import { keysOf } from '../../../common'; + +const refreshUnitsOptions: RelativeOption[] = keysOf(timeUnits) + .filter(timeUnit => timeUnit === 'h' || timeUnit === 'm' || timeUnit === 's') + .map(timeUnit => ({ value: timeUnit, text: timeUnitsPlural[timeUnit] })); const MILLISECONDS_IN_SECOND = 1000; const MILLISECONDS_IN_MINUTE = MILLISECONDS_IN_SECOND * 60; const MILLISECONDS_IN_HOUR = MILLISECONDS_IN_MINUTE * 60; -function fromMilliseconds(milliseconds) { - function round(value) { - return parseFloat(value.toFixed(2)); - } +function fromMilliseconds(milliseconds: Milliseconds): EuiRefreshIntervalState { + const round = (value: number) => parseFloat(value.toFixed(2)); if (milliseconds > MILLISECONDS_IN_HOUR) { return { units: 'h', @@ -46,7 +47,7 @@ function fromMilliseconds(milliseconds) { }; } -function toMilliseconds(units, value) { +function toMilliseconds(units: TimeUnitId, value: Milliseconds) { switch (units) { case 'h': return Math.round(value * MILLISECONDS_IN_HOUR); @@ -58,70 +59,88 @@ function toMilliseconds(units, value) { } } -export class EuiRefreshInterval extends Component { - constructor(props) { - super(props); +export interface EuiRefreshIntervalProps { + applyRefreshInterval?: ApplyRefreshInterval; + isPaused?: boolean; + refreshInterval: Milliseconds; +} - const { value, units } = fromMilliseconds(props.refreshInterval); - this.state = { - value, - units, - }; - } +interface EuiRefreshIntervalState { + value: number | null; + units: TimeUnitId; +} + +export class EuiRefreshInterval extends Component< + EuiRefreshIntervalProps, + EuiRefreshIntervalState +> { + state: EuiRefreshIntervalState = fromMilliseconds(this.props.refreshInterval); generateId = htmlIdGenerator(); - onValueChange = evt => { - const sanitizedValue = parseFloat(evt.target.value); + onValueChange: ChangeEventHandler = event => { + const sanitizedValue = parseFloat(event.target.value); this.setState( { - value: isNaN(sanitizedValue) ? '' : sanitizedValue, + value: isNaN(sanitizedValue) ? null : sanitizedValue, }, this.applyRefreshInterval ); }; - onUnitsChange = evt => { + onUnitsChange: ChangeEventHandler = event => { this.setState( { - units: evt.target.value, + units: event.target.value as TimeUnitId, }, this.applyRefreshInterval ); }; applyRefreshInterval = () => { - if (this.state.value === '') { + const { applyRefreshInterval, isPaused } = this.props; + const { units, value } = this.state; + if (value === null) { + return; + } + if (!applyRefreshInterval) { return; } - const valueInMilliSeconds = toMilliseconds( - this.state.units, - this.state.value - ); + const refreshInterval = toMilliseconds(units, value); - this.props.applyRefreshInterval({ - refreshInterval: valueInMilliSeconds, - isPaused: valueInMilliSeconds <= 0 ? true : this.props.isPaused, + applyRefreshInterval({ + refreshInterval, + isPaused: refreshInterval <= 0 ? true : isPaused, }); }; toogleRefresh = () => { - this.props.applyRefreshInterval({ - refreshInterval: toMilliseconds(this.state.units, this.state.value), - isPaused: !this.props.isPaused, + const { applyRefreshInterval, isPaused } = this.props; + const { units, value } = this.state; + + if (!applyRefreshInterval || value === null) { + return; + } + applyRefreshInterval({ + refreshInterval: toMilliseconds(units, value), + isPaused: !isPaused, }); }; render() { + const { applyRefreshInterval, isPaused } = this.props; + const { value, units } = this.state; const legendId = this.generateId(); const refreshSelectionId = this.generateId(); - const { value, units } = this.state; - if (!this.props.applyRefreshInterval) { + if (!applyRefreshInterval) { return null; } + const options = refreshUnitsOptions.find(({ value }) => value === units); + const optionText = options ? options.text : ''; + return (
@@ -137,7 +156,7 @@ export class EuiRefreshInterval extends Component { - {this.props.isPaused ? ( + {isPaused ? ( ) : ( @@ -179,9 +198,7 @@ export class EuiRefreshInterval extends Component { default="Currently set to {optionValue} {optionText}." values={{ optionValue: value, - optionText: refreshUnitsOptions.find( - option => option.value === units - ).text, + optionText, }} />

@@ -190,9 +207,3 @@ export class EuiRefreshInterval extends Component { ); } } - -EuiRefreshInterval.propTypes = { - applyRefreshInterval: PropTypes.func, - isPaused: PropTypes.bool.isRequired, - refreshInterval: PropTypes.number.isRequired, -}; diff --git a/src/components/date_picker/super_date_picker/relative_options.ts b/src/components/date_picker/super_date_picker/relative_options.ts index 435ff6d5434d..db5c2e83d0e2 100644 --- a/src/components/date_picker/super_date_picker/relative_options.ts +++ b/src/components/date_picker/super_date_picker/relative_options.ts @@ -1,4 +1,6 @@ -export const relativeOptions = [ +import { TimeUnitId, RelativeOption } from '../types'; + +export const relativeOptions: RelativeOption[] = [ { text: 'Seconds ago', value: 's' }, { text: 'Minutes ago', value: 'm' }, { text: 'Hours ago', value: 'h' }, @@ -17,10 +19,11 @@ export const relativeOptions = [ ]; export const relativeUnitsFromLargestToSmallest = relativeOptions - .filter(({ value }) => { - return !value.includes('+'); - }) .map(({ value }) => { return value; }) - .reverse(); + .filter(value => { + // NOTE_TO_SELF(dimitri): a TypeScript assertion couldn't hurt here, instead of a cast + return !value.includes('+'); + }) + .reverse() as TimeUnitId[]; diff --git a/src/components/date_picker/super_date_picker/relative_utils.test.js b/src/components/date_picker/super_date_picker/relative_utils.test.ts similarity index 100% rename from src/components/date_picker/super_date_picker/relative_utils.test.js rename to src/components/date_picker/super_date_picker/relative_utils.test.ts diff --git a/src/components/date_picker/super_date_picker/relative_utils.js b/src/components/date_picker/super_date_picker/relative_utils.ts similarity index 74% rename from src/components/date_picker/super_date_picker/relative_utils.js rename to src/components/date_picker/super_date_picker/relative_utils.ts index eede3f94f772..235088531128 100644 --- a/src/components/date_picker/super_date_picker/relative_utils.js +++ b/src/components/date_picker/super_date_picker/relative_utils.ts @@ -4,10 +4,11 @@ import moment from 'moment'; import { get } from '../../../services/objects'; import { isString } from '../../../services/predicate'; import { relativeUnitsFromLargestToSmallest } from './relative_options'; +import { TimeUnitId, RelativeParts } from '../types'; const ROUND_DELIMETER = '/'; -export function parseRelativeParts(value) { +export function parseRelativeParts(value: string): RelativeParts { const matches = isString(value) && value.match(/now(([\-\+])([0-9]+)([smhdwMy])(\/[smhdwMy])?)?/); @@ -24,11 +25,15 @@ export function parseRelativeParts(value) { if (count && unit) { const isRounded = roundBy ? true : false; + const roundUnit = + isRounded && roundBy + ? (roundBy.replace(ROUND_DELIMETER, '') as TimeUnitId) + : undefined; return { count: parseInt(count, 10), unit: operator === '+' ? `${unit}+` : unit, round: isRounded, - roundUnit: isRounded ? roundBy.replace(ROUND_DELIMETER, '') : undefined, + ...(roundUnit ? { roundUnit } : {}), }; } @@ -36,10 +41,10 @@ export function parseRelativeParts(value) { const duration = moment.duration(moment().diff(dateMath.parse(value))); let unitOp = ''; for (let i = 0; i < relativeUnitsFromLargestToSmallest.length; i++) { - const as = duration.as(relativeUnitsFromLargestToSmallest[i]); - if (as < 0) unitOp = '+'; - if (Math.abs(as) > 1) { - results.count = Math.round(Math.abs(as)); + const asRelative = duration.as(relativeUnitsFromLargestToSmallest[i]); + if (asRelative < 0) unitOp = '+'; + if (Math.abs(asRelative) > 1) { + results.count = Math.round(Math.abs(asRelative)); results.unit = relativeUnitsFromLargestToSmallest[i] + unitOp; results.round = false; break; @@ -48,7 +53,7 @@ export function parseRelativeParts(value) { return results; } -export function toRelativeStringFromParts(relativeParts) { +export const toRelativeStringFromParts = (relativeParts: RelativeParts) => { const count = get(relativeParts, 'count', 0); const isRounded = get(relativeParts, 'round', false); @@ -62,4 +67,4 @@ export function toRelativeStringFromParts(relativeParts) { const round = isRounded ? `${ROUND_DELIMETER}${unit}` : ''; return `now${operator}${count}${unit}${round}`; -} +}; diff --git a/src/components/date_picker/super_date_picker/super_date_picker.test.js b/src/components/date_picker/super_date_picker/super_date_picker.test.tsx similarity index 100% rename from src/components/date_picker/super_date_picker/super_date_picker.test.js rename to src/components/date_picker/super_date_picker/super_date_picker.test.tsx diff --git a/src/components/date_picker/super_date_picker/super_date_picker.js b/src/components/date_picker/super_date_picker/super_date_picker.tsx similarity index 66% rename from src/components/date_picker/super_date_picker/super_date_picker.js rename to src/components/date_picker/super_date_picker/super_date_picker.tsx index 5ad5c5fe5c65..4c41f8acc532 100644 --- a/src/components/date_picker/super_date_picker/super_date_picker.js +++ b/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -1,11 +1,5 @@ -import PropTypes from 'prop-types'; import React, { Component } from 'react'; import classNames from 'classnames'; -import { - commonlyUsedRangeShape, - recentlyUsedRangeShape, - quickSelectPanelShape, -} from './types'; import { prettyDuration, showPrettyDuration, @@ -20,15 +14,109 @@ import { EuiQuickSelectPopover } from './quick_select_popover/quick_select_popov import { EuiDatePopoverButton } from './date_popover/date_popover_button'; import { EuiDatePickerRange } from '../date_picker_range'; -import { EuiFormControlLayout } from '../../form'; +import { EuiFormControlLayout } from '../../form/form_control_layout'; import { EuiFlexGroup, EuiFlexItem } from '../../flex'; import { AsyncInterval } from './async_interval'; import { EuiI18n } from '../../i18n'; import { EuiI18nConsumer } from '../../context'; +import { CommonProps } from '../../common'; +import { + ShortDate, + Milliseconds, + DurationRange, + ApplyTime, + ApplyRefreshInterval, + QuickSelectPanel, +} from '../types'; +import { EuiDatePopoverContentProps } from './date_popover/date_popover_content'; +import { LocaleSpecifier } from 'moment'; // eslint-disable-line import/named export { prettyDuration, commonDurationRanges }; -function isRangeInvalid(start, end) { +interface OnTimeChangeProps extends DurationRange { + isInvalid: boolean; + isQuickSelection: boolean; +} + +interface OnRefreshProps extends DurationRange { + refreshInterval: number; +} + +export type EuiSuperDatePickerProps = CommonProps & { + commonlyUsedRanges: DurationRange[]; + customQuickSelectPanels?: QuickSelectPanel[]; + + /** + * Specifies the formatted used when displaying dates and/or datetimes + */ + dateFormat: string; + end: ShortDate; + + /** + * Set isAutoRefreshOnly to true to limit the component to only display auto refresh content. + */ + isAutoRefreshOnly?: boolean; + isDisabled?: boolean; + isLoading?: boolean; + isPaused?: boolean; + + /** + * Used to localize e.g. month names, passed to `moment` + */ + locale?: LocaleSpecifier; + + /** + * Callback for when the refresh interval is fired. + * EuiSuperDatePicker will only manage a refresh interval timer when onRefresh callback is supplied + * If a promise is returned, the next refresh interval will not start until the promise has resolved. + * If the promise rejects the refresh interval will stop and the error thrown + */ + onRefresh?: (props: OnRefreshProps) => void; + + /** + * Callback for when the refresh interval changes. + * Supply onRefreshChange to show refresh interval inputs in quick select popover + */ + onRefreshChange?: ApplyRefreshInterval; + + /** + * Callback for when the time changes. + */ + onTimeChange: (props: OnTimeChangeProps) => void; + recentlyUsedRanges?: DurationRange[]; + + /** + * Refresh interval in milliseconds + */ + refreshInterval: Milliseconds; + + /** + * Set showUpdateButton to false to immediately invoke onTimeChange for all start and end changes. + */ + showUpdateButton?: boolean; + start: ShortDate; + + /** + * Specifies the formatted used when displaying times + */ + timeFormat: string; +}; + +interface EuiSuperDatePickerState { + end: ShortDate; + hasChanged: boolean; + isEndDatePopoverOpen: boolean; + isInvalid: boolean; + isStartDatePopoverOpen: boolean; + prevProps: { + end: ShortDate; + start: ShortDate; + }; + showPrettyDuration: boolean; + start: ShortDate; +} + +function isRangeInvalid(start: ShortDate, end: ShortDate) { if (start === 'now' && end === 'now') { return true; } @@ -50,76 +138,10 @@ function isRangeInvalid(start, end) { return false; } -export class EuiSuperDatePicker extends Component { - static propTypes = { - isLoading: PropTypes.bool, - isDisabled: PropTypes.bool, - /** - * String as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.SSSZ' - */ - start: PropTypes.string, - /** - * String as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.SSSZ' - */ - end: PropTypes.string, - /** - * Callback for when the time changes. Called with { start, end, isQuickSelection, isInvalid } - */ - onTimeChange: PropTypes.func.isRequired, - isPaused: PropTypes.bool, - /** - * Refresh interval in milliseconds - */ - refreshInterval: PropTypes.number, - /** - * Callback for when the refresh interval changes. Called with { isPaused, refreshInterval } - * Supply onRefreshChange to show refresh interval inputs in quick select popover - */ - onRefreshChange: PropTypes.func, - - /** - * Callback for when the refresh interval is fired. Called with { start, end, refreshInterval } - * EuiSuperDatePicker will only manage a refresh interval timer when onRefresh callback is supplied - * If a promise is returned, the next refresh interval will not start until the promise has resolved. - * If the promise rejects the refresh interval will stop and the error thrown - */ - onRefresh: PropTypes.func, - - /** - * 'start' and 'end' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.SSSZ' - */ - commonlyUsedRanges: PropTypes.arrayOf(commonlyUsedRangeShape), - /** - * Used to localize e.g. month names, passed to `moment` - */ - locale: PropTypes.string, - /** - * Specifies the formatted used when displaying dates and/or datetimes - */ - dateFormat: PropTypes.string, - /** - * Specifies the formatted used when displaying times - */ - timeFormat: PropTypes.string, - /** - * 'start' and 'end' must be string as either datemath (e.g.: now, now-15m, now-15m/m) or - * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.SSSZ' - */ - recentlyUsedRanges: PropTypes.arrayOf(recentlyUsedRangeShape), - /** - * Set showUpdateButton to false to immediately invoke onTimeChange for all start and end changes. - */ - showUpdateButton: PropTypes.bool, - /** - * Set isAutoRefreshOnly to true to limit the component to only display auto refresh content. - */ - isAutoRefreshOnly: PropTypes.bool, - customQuickSelectPanels: PropTypes.arrayOf(quickSelectPanelShape), - }; - +export class EuiSuperDatePicker extends Component< + EuiSuperDatePickerProps, + EuiSuperDatePickerState +> { static defaultProps = { start: 'now-15m', end: 'now', @@ -134,7 +156,30 @@ export class EuiSuperDatePicker extends Component { isAutoRefreshOnly: false, }; - static getDerivedStateFromProps(nextProps, prevState) { + asyncInterval?: AsyncInterval; + + state: EuiSuperDatePickerState = { + prevProps: { + start: this.props.start, + end: this.props.end, + }, + start: this.props.start, + end: this.props.end, + isInvalid: isRangeInvalid(this.props.start, this.props.end), + hasChanged: false, + showPrettyDuration: showPrettyDuration( + this.props.start, + this.props.end, + this.props.commonlyUsedRanges + ), + isStartDatePopoverOpen: false, + isEndDatePopoverOpen: false, + }; + + static getDerivedStateFromProps( + nextProps: EuiSuperDatePickerProps, + prevState: EuiSuperDatePickerState + ) { if ( nextProps.start !== prevState.prevProps.start || nextProps.end !== prevState.prevProps.end @@ -159,27 +204,7 @@ export class EuiSuperDatePicker extends Component { return null; } - constructor(props) { - super(props); - - const { start, end, commonlyUsedRanges } = this.props; - - this.state = { - prevProps: { - start: props.start, - end: props.end, - }, - start, - end, - isInvalid: isRangeInvalid(start, end), - hasChanged: false, - showPrettyDuration: showPrettyDuration(start, end, commonlyUsedRanges), - isStartDatePopoverOpen: false, - isEndDatePopoverOpen: false, - }; - } - - setTime = ({ start, end }) => { + setTime = ({ end, start }: DurationRange) => { const isInvalid = isRangeInvalid(start, end); this.setState({ @@ -216,11 +241,11 @@ export class EuiSuperDatePicker extends Component { this.stopInterval(); }; - setStart = start => { + setStart: EuiDatePopoverContentProps['onChange'] = (start: ShortDate) => { this.setTime({ start, end: this.state.end }); }; - setEnd = end => { + setEnd: EuiDatePopoverContentProps['onChange'] = (end: ShortDate) => { this.setTime({ start: this.state.start, end }); }; @@ -233,14 +258,10 @@ export class EuiSuperDatePicker extends Component { }); }; - applyQuickTime = ({ start, end }) => { - this.setState(prevState => ({ - showPrettyDuration: showPrettyDuration( - start, - end, - prevState.commonlyUsedRanges - ), - })); + applyQuickTime: ApplyTime = ({ start, end }) => { + this.setState({ + showPrettyDuration: showPrettyDuration(start, end, commonDurationRanges), + }); this.props.onTimeChange({ start, end, @@ -273,7 +294,7 @@ export class EuiSuperDatePicker extends Component { this.setState({ isEndDatePopoverOpen: false }); }; - onRefreshChange = ({ refreshInterval, isPaused }) => { + onRefreshChange: ApplyRefreshInterval = ({ refreshInterval, isPaused }) => { this.stopInterval(); if (!isPaused) { this.startInterval(refreshInterval); @@ -289,7 +310,7 @@ export class EuiSuperDatePicker extends Component { } }; - startInterval = refreshInterval => { + startInterval = (refreshInterval: number) => { const { onRefresh } = this.props; if (onRefresh) { const handler = () => { @@ -301,10 +322,27 @@ export class EuiSuperDatePicker extends Component { }; renderDatePickerRange = () => { - const { start, end, hasChanged, isInvalid } = this.state; - const { isDisabled } = this.props; - - if (this.props.isAutoRefreshOnly) { + const { + end, + hasChanged, + isEndDatePopoverOpen, + isInvalid, + isStartDatePopoverOpen, + showPrettyDuration, + start, + } = this.state; + const { + commonlyUsedRanges, + dateFormat, + isAutoRefreshOnly, + isDisabled, + isPaused, + locale, + refreshInterval, + timeFormat, + } = this.props; + + if (isAutoRefreshOnly) { return ( } readOnly> - {prettyInterval(this.props.isPaused, this.props.refreshInterval)} + {prettyInterval(Boolean(isPaused), refreshInterval)} ); } if ( - this.state.showPrettyDuration && - !this.state.isStartDatePopoverOpen && - !this.state.isEndDatePopoverOpen + showPrettyDuration && + !isStartDatePopoverOpen && + !isEndDatePopoverOpen ) { return ( - {prettyDuration( - start, - end, - this.props.commonlyUsedRanges, - this.props.dateFormat - )} + {prettyDuration(start, end, commonlyUsedRanges, dateFormat)} ); diff --git a/src/components/date_picker/super_date_picker/super_update_button.test.js b/src/components/date_picker/super_date_picker/super_update_button.test.tsx similarity index 100% rename from src/components/date_picker/super_date_picker/super_update_button.test.js rename to src/components/date_picker/super_date_picker/super_update_button.test.tsx diff --git a/src/components/date_picker/super_date_picker/super_update_button.js b/src/components/date_picker/super_date_picker/super_update_button.tsx similarity index 79% rename from src/components/date_picker/super_date_picker/super_update_button.js rename to src/components/date_picker/super_date_picker/super_update_button.tsx index 54e5c3af4600..761615053337 100644 --- a/src/components/date_picker/super_date_picker/super_update_button.js +++ b/src/components/date_picker/super_date_picker/super_update_button.tsx @@ -1,29 +1,34 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { Component, MouseEventHandler, Ref } from 'react'; import classNames from 'classnames'; import { EuiButton } from '../../button'; import { EuiI18n } from '../../i18n'; -import { EuiToolTip } from '../../tool_tip'; - -export class EuiSuperUpdateButton extends Component { - static propTypes = { - needsUpdate: PropTypes.bool, - isLoading: PropTypes.bool, - isDisabled: PropTypes.bool, - onClick: PropTypes.func.isRequired, - /** - * Passes props to `EuiToolTip` - */ - toolTipProps: PropTypes.object, - }; +import { EuiToolTip, EuiToolTipProps } from '../../tool_tip'; + +export interface EuiSuperUpdateButtonProps { + className?: string; + isDisabled: boolean; + isLoading: boolean; + needsUpdate: boolean; + onClick: MouseEventHandler; + + /** + * Passes props to `EuiToolTip` + */ + toolTipProps?: EuiToolTipProps; +} +export class EuiSuperUpdateButton extends Component { static defaultProps = { needsUpdate: false, isLoading: false, isDisabled: false, }; + _isMounted = false; + tooltipTimeout: NodeJS.Timer | undefined; + tooltip: EuiToolTip | null = null; + componentWillUnmount() { this._isMounted = false; } @@ -45,7 +50,9 @@ export class EuiSuperUpdateButton extends Component { } } - setTootipRef = node => (this.tooltip = node); + setTootipRef: Ref = node => { + this.tooltip = node; + }; showTooltip = () => { if (!this._isMounted || !this.tooltip) { diff --git a/src/components/date_picker/super_date_picker/time_units.js b/src/components/date_picker/super_date_picker/time_units.js deleted file mode 100644 index 9e058dcc5004..000000000000 --- a/src/components/date_picker/super_date_picker/time_units.js +++ /dev/null @@ -1,19 +0,0 @@ -export const timeUnits = { - s: 'second', - m: 'minute', - h: 'hour', - d: 'day', - w: 'week', - M: 'month', - y: 'year', -}; - -export const timeUnitsPlural = { - s: 'seconds', - m: 'minutes', - h: 'hours', - d: 'days', - w: 'weeks', - M: 'months', - y: 'years', -}; diff --git a/src/components/date_picker/super_date_picker/time_units.ts b/src/components/date_picker/super_date_picker/time_units.ts new file mode 100644 index 000000000000..e606cb926fd0 --- /dev/null +++ b/src/components/date_picker/super_date_picker/time_units.ts @@ -0,0 +1,21 @@ +import { TimeUnitId, TimeUnitLabel, TimeUnitLabelPlural } from '../types'; + +export const timeUnits: { [id in TimeUnitId]: TimeUnitLabel } = { + s: 'second', + m: 'minute', + h: 'hour', + d: 'day', + w: 'week', + M: 'month', + y: 'year', +}; + +export const timeUnitsPlural: { [id in TimeUnitId]: TimeUnitLabelPlural } = { + s: 'seconds', + m: 'minutes', + h: 'hours', + d: 'days', + w: 'weeks', + M: 'months', + y: 'years', +}; diff --git a/src/components/date_picker/super_date_picker/types.js b/src/components/date_picker/super_date_picker/types.js deleted file mode 100644 index 2f334117777d..000000000000 --- a/src/components/date_picker/super_date_picker/types.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types'; - -export const commonlyUsedRangeShape = PropTypes.shape({ - start: PropTypes.string.isRequired, - end: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, -}); - -export const recentlyUsedRangeShape = PropTypes.shape({ - start: PropTypes.string.isRequired, - end: PropTypes.string.isRequired, -}); - -export const quickSelectPanelShape = PropTypes.shape({ - title: PropTypes.string.isRequired, - content: PropTypes.node.isRequired, -}); diff --git a/src/components/date_picker/types.ts b/src/components/date_picker/types.ts new file mode 100644 index 000000000000..816c32fece12 --- /dev/null +++ b/src/components/date_picker/types.ts @@ -0,0 +1,70 @@ +export interface DurationRange { + end: ShortDate; + label?: string; + start: ShortDate; +} + +export type TimeUnitId = 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y'; +export type TimeUnitFromNowId = 's+' | 'm+' | 'h+' | 'd+' | 'w+' | 'M+' | 'y+'; +export type TimeUnitLabel = + | 'second' + | 'minute' + | 'hour' + | 'day' + | 'week' + | 'month' + | 'year'; +export type TimeUnitLabelPlural = + | 'seconds' + | 'minutes' + | 'hours' + | 'days' + | 'weeks' + | 'months' + | 'years'; +export type AbsoluteDateMode = 'absolute'; +export type RelativeDateMode = 'relative'; +export type NowDateMode = 'now'; +export type DateMode = AbsoluteDateMode | RelativeDateMode | NowDateMode; + +/** + * String as either datemath (e.g.: now, now-15m, now-15m/m) or + * absolute date in the format 'YYYY-MM-DDTHH:mm:ss.SSSZ' + */ +export type ShortDate = NowDateMode | string; + +export type Milliseconds = number; + +export interface RelativeParts { + count: number; + round: boolean; + roundUnit?: TimeUnitId; + unit: string; +} + +export interface RelativeOption { + text: string; + value: TimeUnitId | TimeUnitFromNowId; +} + +export type ApplyRefreshInterval = (args: { + isPaused?: boolean; + refreshInterval: number; +}) => void; + +export interface QuickSelect { + timeTense: string; + timeValue: number; + timeUnits: TimeUnitId; +} + +interface ApplyTimeArgs extends DurationRange { + keepPopoverOpen?: boolean; + quickSelect?: QuickSelect; +} +export type ApplyTime = (args: ApplyTimeArgs) => void; + +export interface QuickSelectPanel { + title: string; + content: React.ReactNode; +} diff --git a/src/components/index.d.ts b/src/components/index.d.ts index 24be6528a3f8..fc0319457ff0 100644 --- a/src/components/index.d.ts +++ b/src/components/index.d.ts @@ -1,5 +1,4 @@ /// -/// /// declare module '@elastic/eui' {