diff --git a/changelogs/upcoming/7705.md b/changelogs/upcoming/7705.md new file mode 100644 index 00000000000..55c3d3c6678 --- /dev/null +++ b/changelogs/upcoming/7705.md @@ -0,0 +1,3 @@ +**Bug fixes** + +- Fixed `EuiSuperDatePicker` to validate date string with respect of locale on `EuiAbsoluteTab`. diff --git a/src-docs/src/views/super_date_picker/super_date_picker_example.js b/src-docs/src/views/super_date_picker/super_date_picker_example.js index 1607e80d973..4fea50c1a86 100644 --- a/src-docs/src/views/super_date_picker/super_date_picker_example.js +++ b/src-docs/src/views/super_date_picker/super_date_picker_example.js @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom'; import { GuideSectionTypes } from '../../components'; import { + EuiCallOut, EuiCode, EuiCodeBlock, EuiIcon, @@ -37,6 +38,9 @@ const autoRefreshOnlySource = require('!!raw-loader!./auto_refresh_only'); import SuperDatePickerPattern from './super_date_picker_pattern'; const superDatePickerPatternSource = require('!!raw-loader!./super_date_picker_pattern'); +import SuperDatePickerLocale from './super_date_picker_locale'; +const superDatePickerLocaleSource = require('!!raw-loader!./super_date_picker_locale'); + const superDatePickerSnippet = ` `; +const superDatePickerLocaleSnippet = ``; + export const SuperDatePickerExample = { title: 'Super date picker', intro: ( @@ -346,5 +358,38 @@ if (!endMoment || !endMoment.isValid()) { demo: , dempPanelProps: { color: 'subdued' }, }, + { + title: 'Locale', + source: [ + { + type: GuideSectionTypes.TSX, + code: superDatePickerLocaleSource, + }, + ], + text: ( + <> +

+ Locale formatting is achieved by using the locale + ,timeFormat, and dateFormat{' '} + props. The latter will take any moment(){' '} + notation. Check{' '} + + Date format by country + {' '} + for formatting examples. +

+ + Moment will try to load the locale on demand when it is used. + Bundlers that do not support dynamic require statements will need to + explicitly import the locale, e.g.{' '} + {"import 'moment/locale/zh-cn'"}. See the below + demo TSX for examples. + + + ), + props: { EuiSuperDatePicker }, + snippet: superDatePickerLocaleSnippet, + demo: , + }, ], }; diff --git a/src-docs/src/views/super_date_picker/super_date_picker_locale.tsx b/src-docs/src/views/super_date_picker/super_date_picker_locale.tsx new file mode 100644 index 00000000000..d19a304ac4a --- /dev/null +++ b/src-docs/src/views/super_date_picker/super_date_picker_locale.tsx @@ -0,0 +1,100 @@ +import React, { useState } from 'react'; + +// NOTE: These explicit imports are required for CodeSandbox and any +// bundler that does not support Moment dynamically loading locales +import 'moment/locale/zh-cn'; +import 'moment/locale/ja'; +import 'moment/locale/fr'; + +import { + EuiButtonGroup, + EuiComboBox, + EuiComboBoxOptionOption, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiSuperDatePicker, + OnTimeChangeProps, +} from '../../../../src/components'; +import { htmlIdGenerator } from '../../../../src/services'; + +const localeId = htmlIdGenerator('locale'); +const locales = [ + { id: localeId(), label: 'en' }, + { id: localeId(), label: 'zh-CN' }, + { id: localeId(), label: 'ja-JP' }, + { id: localeId(), label: 'fr-FR' }, +]; +const dateFormats = [ + { label: 'MMM D, YYYY @ HH:mm:ss.SSS' }, + { label: 'dddd, MMMM Do YYYY, h:mm:ss a' }, + { label: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }, +]; + +export default () => { + const [start, setStart] = useState('now-1h'); + const [end, setEnd] = useState('now-15m'); + const onTimeChange = ({ start, end }: OnTimeChangeProps) => { + setStart(start); + setEnd(end); + }; + + const [locale, setLocale] = useState(); + const [localeSelected, setLocaleSelected] = useState(locales[0].id); + const onLocaleChange = (optionId: React.SetStateAction) => { + setLocale(locales.find(({ id }) => id === optionId)!.label); + setLocaleSelected(optionId); + }; + + const [dateFormat, setDateFormat] = useState(); + const [dateFormatsSelected, setDateFormatsSelected] = useState([ + dateFormats[0], + ]); + const onDateFormatChange = (selectedOptions: EuiComboBoxOptionOption[]) => { + setDateFormat(selectedOptions.length ? selectedOptions[0].label : ''); + setDateFormatsSelected(selectedOptions); + }; + const onDateFormatCreate = (searchValue: string) => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + if (!normalizedSearchValue) return; + + setDateFormat(searchValue); + setDateFormatsSelected([{ label: searchValue }]); + }; + + return ( + <> + + + + + + + + + + + + ); +}; diff --git a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.test.tsx b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.test.tsx index 517212930a6..0a6ef20758f 100644 --- a/src/components/date_picker/super_date_picker/date_popover/absolute_tab.test.tsx +++ b/src/components/date_picker/super_date_picker/date_popover/absolute_tab.test.tsx @@ -11,6 +11,7 @@ import { fireEvent } from '@testing-library/react'; import { render } from '../../../../test/rtl'; import { EuiAbsoluteTab } from './absolute_tab'; +import { LocaleSpecifier } from 'moment'; // Mock EuiDatePicker - 3rd party datepicker lib causes render issues jest.mock('../../date_picker', () => ({ @@ -105,6 +106,27 @@ describe('EuiAbsoluteTab', () => { expect(input).toHaveValue('Jan 31st 01'); }); + describe('parses date string in locale', () => { + test.each<{ + locale: LocaleSpecifier; + dateString: string; + }>([ + { locale: 'en', dateString: 'Mon Jan 1st' }, + { locale: 'zh-CN', dateString: '周一 1月 1日' }, + { locale: 'ja-JP', dateString: '月 1月 1日' }, + { locale: 'fr-FR', dateString: 'lun. janv. 1er' }, + ])('%p', ({ locale, dateString }) => { + const { getByTestSubject } = render( + + ); + const input = getByTestSubject('superDatePickerAbsoluteDateInput'); + + changeInput(input, dateString); + expect(input).not.toBeInvalid(); + expect(input).toHaveValue(dateString); + }); + }); + describe('allows several other common date formats, and autoformats them to the `dateFormat` prop', () => { const assertOutput = (input: HTMLInputElement) => { // Exclude hours from assertion, because moment uses local machine timezone 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 index 8eae41674e0..a0cd792a165 100644 --- 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 @@ -116,10 +116,15 @@ export class EuiAbsoluteTab extends Component< return this.setState(invalidDateState); } - const { onChange, dateFormat } = this.props; + const { onChange, dateFormat, locale } = this.props; - // Attempt to parse with passed `dateFormat` - let valueAsMoment = moment(textInputValue, dateFormat, true); + // Attempt to parse with passed `dateFormat` and `locale` + let valueAsMoment = moment( + textInputValue, + dateFormat, + typeof locale === 'string' ? locale : 'en', // Narrow the union type to string + true + ); let dateIsValid = valueAsMoment.isValid(); // If not valid, try a few other other standardized formats