Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/upcoming/7705.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Bug fixes**

- Fixed `EuiSuperDatePicker` to validate date string with respect of locale on `EuiAbsoluteTab`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This docs example is AMAZING. ✨ 😮 Thank you SO much for going the extra mile to document this!!! ❤️‍🔥

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../components';

import {
EuiCallOut,
EuiCode,
EuiCodeBlock,
EuiIcon,
Expand Down Expand Up @@ -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 = `<EuiSuperDatePicker
onTimeChange={onTimeChange}
start="now-30m"
Expand All @@ -59,6 +63,14 @@ const superDatePickerCustomQuickSelectSnippet = `<EuiSuperDatePicker
/>
`;

const superDatePickerLocaleSnippet = `<EuiSuperDatePicker
start="now-1h"
end="now-15m"
locale="zh-CN"
dateFormat="MMM D, YYYY @ HH:mm:ss.SSS"
onTimeChange={onTimeChange}
/>`;

export const SuperDatePickerExample = {
title: 'Super date picker',
intro: (
Expand Down Expand Up @@ -346,5 +358,38 @@ if (!endMoment || !endMoment.isValid()) {
demo: <SuperDatePickerPattern />,
dempPanelProps: { color: 'subdued' },
},
{
title: 'Locale',
source: [
{
type: GuideSectionTypes.TSX,
code: superDatePickerLocaleSource,
},
],
text: (
<>
<p>
Locale formatting is achieved by using the <EuiCode>locale</EuiCode>
,<EuiCode>timeFormat</EuiCode>, and <EuiCode>dateFormat</EuiCode>{' '}
props. The latter will take any <EuiCode>moment()</EuiCode>{' '}
notation. Check{' '}
<a href="https://en.wikipedia.org/wiki/Date_format_by_country">
Date format by country
</a>{' '}
for formatting examples.
</p>
<EuiCallOut color="warning">
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.{' '}
<EuiCode>{"import 'moment/locale/zh-cn'"}</EuiCode>. See the below
demo TSX for examples.
</EuiCallOut>
</>
),
props: { EuiSuperDatePicker },
snippet: superDatePickerLocaleSnippet,
demo: <SuperDatePickerLocale />,
},
],
};
100 changes: 100 additions & 0 deletions src-docs/src/views/super_date_picker/super_date_picker_locale.tsx
Original file line number Diff line number Diff line change
@@ -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<string | undefined>();
const [localeSelected, setLocaleSelected] = useState(locales[0].id);
const onLocaleChange = (optionId: React.SetStateAction<string>) => {
setLocale(locales.find(({ id }) => id === optionId)!.label);
setLocaleSelected(optionId);
};

const [dateFormat, setDateFormat] = useState<string | undefined>();
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 (
<>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend={'Locale'}
options={locales}
idSelected={localeSelected}
onChange={onLocaleChange}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiComboBox
prepend="dateFormat"
placeholder="Select a dateFormat"
customOptionText="Add {searchValue} as a dateFormat"
singleSelection={{ asPlainText: true }}
options={dateFormats}
selectedOptions={dateFormatsSelected}
onChange={onDateFormatChange}
onCreateOption={onDateFormatCreate}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<EuiSuperDatePicker
showUpdateButton={false}
start={start}
end={end}
locale={locale}
dateFormat={dateFormat}
onTimeChange={onTimeChange}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => ({
Expand Down Expand Up @@ -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(
<EuiAbsoluteTab {...props} dateFormat="ddd MMM Do" locale={locale} />
);
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 : undefined,
Comment thread
sakurai-youhei marked this conversation as resolved.
Outdated
true
);
let dateIsValid = valueAsMoment.isValid();

// If not valid, try a few other other standardized formats
Expand Down