diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png new file mode 100644 index 000000000000..0d26fd8fd525 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png new file mode 100644 index 000000000000..f0d40151d96d Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSuperDatePicker_EuiSuperDatePicker_Quick_Select_Only.png differ diff --git a/packages/eui/changelogs/upcoming/8810.md b/packages/eui/changelogs/upcoming/8810.md new file mode 100644 index 000000000000..eda3005b083a --- /dev/null +++ b/packages/eui/changelogs/upcoming/8810.md @@ -0,0 +1,6 @@ +- Added prop `delimiter` on `EuiDatePickerRange` + +**Bug fixes** + +- Fixed an issue with `EuiSuperDatePicker` where toggling `isQuickSelectOnly` would cause a full re-render when the selected range uses absolute dates + diff --git a/packages/eui/src/components/date_picker/date_picker.tsx b/packages/eui/src/components/date_picker/date_picker.tsx index 01b58569c86e..03b3bd12098a 100644 --- a/packages/eui/src/components/date_picker/date_picker.tsx +++ b/packages/eui/src/components/date_picker/date_picker.tsx @@ -213,7 +213,7 @@ export const EuiDatePicker: FunctionComponent = ({ }) => { // Check for whether the passed `selected` moment date is valid const isInvalid = - _isInvalid || (selected?.isValid() === false ? true : undefined); + _isInvalid || (selected?.isValid?.() === false ? true : undefined); const styles = useEuiMemoizedStyles(euiDatePickerStyles); const cssStyles = [ diff --git a/packages/eui/src/components/date_picker/date_picker_range.stories.tsx b/packages/eui/src/components/date_picker/date_picker_range.stories.tsx index 612e49e58db0..06f371800a25 100644 --- a/packages/eui/src/components/date_picker/date_picker_range.stories.tsx +++ b/packages/eui/src/components/date_picker/date_picker_range.stories.tsx @@ -134,25 +134,28 @@ const StatefulPlayground = ({ ...rest }: EuiDatePickerRangeProps) => { const [selectedStartDate, setSelectedStartDate] = useState( - startDateControl.props.selected + startDateControl?.props.selected ?? moment('01/01/1970') ); const [selectedEndDate, setSelectedEndDate] = useState( - endDateControl.props.selected + endDateControl?.props.selected ?? moment('01/07/1970') ); - const startControl = React.cloneElement(startDateControl, { - selected: selectedStartDate, - onChange: setSelectedStartDate, - startDate: selectedStartDate, - endDate: selectedEndDate, - }); - - const endControl = React.cloneElement(endDateControl, { - selected: selectedEndDate, - onChange: setSelectedEndDate, - startDate: selectedStartDate, - endDate: selectedEndDate, - }); + const startControl = + startDateControl && + React.cloneElement(startDateControl, { + selected: selectedStartDate, + onChange: setSelectedStartDate, + startDate: selectedStartDate, + endDate: selectedEndDate, + }); + const endControl = + endDateControl && + React.cloneElement(endDateControl, { + selected: selectedEndDate, + onChange: setSelectedEndDate, + startDate: selectedStartDate, + endDate: selectedEndDate, + }); return ( & { /** * Including any children will replace all innards with the provided children @@ -51,12 +52,12 @@ export type EuiDatePickerRangeProps = CommonProps & /** * The end date `EuiDatePicker` element */ - endDateControl: ReactElement; + endDateControl?: ReactElement; /** * The start date `EuiDatePicker` element */ - startDateControl: ReactElement; + startDateControl?: ReactElement; /** * Pass either an icon type or set to `false` to remove icon entirely @@ -116,6 +117,7 @@ export const EuiDatePickerRange: FunctionComponent = ({ onBlur, append, prepend, + delimiter, ...rest }) => { // `fullWidth` and `compressed` should not affect inline datepickers (matches non-range behavior) @@ -130,8 +132,8 @@ export const EuiDatePickerRange: FunctionComponent = ({ : [ inlineStyles.euiDatePickerRangeInline, // Determine the inline container query to use based on the width of the react-datepicker - startDateControl.props.showTimeSelect || - endDateControl.props.showTimeSelect + startDateControl?.props.showTimeSelect || + endDateControl?.props.showTimeSelect ? inlineStyles.responsiveWithTimeSelect : inlineStyles.responsive, shadow && inlineStyles.shadow, @@ -141,58 +143,56 @@ export const EuiDatePickerRange: FunctionComponent = ({ let endControl = endDateControl; if (!isCustom) { - startControl = cloneElement( - startDateControl as ReactElement, - { + startControl = + startControl && + cloneElement(startDateControl as ReactElement, { controlOnly: true, showIcon: false, inline, compressed, fullWidth, readOnly, - disabled: disabled || startDateControl.props.disabled, - isInvalid: isInvalid || startDateControl.props.isInvalid, + disabled: disabled || startDateControl?.props.disabled, + isInvalid: isInvalid || startDateControl?.props.isInvalid, className: classNames( 'euiDatePickerRange__start', - startDateControl.props.className + startDateControl?.props.className ), onBlur: (event: FocusEvent) => { - startDateControl.props?.onBlur?.(event); + startDateControl?.props?.onBlur?.(event); onBlur?.(event); }, onFocus: (event: FocusEvent) => { - startDateControl.props?.onFocus?.(event); + startDateControl?.props?.onFocus?.(event); onFocus?.(event); }, - } - ); + }); - endControl = cloneElement( - endDateControl as ReactElement, - { + endControl = + endControl && + cloneElement(endDateControl as ReactElement, { controlOnly: true, showIcon: false, inline, compressed, fullWidth, readOnly, - disabled: disabled || endDateControl.props.disabled, - isInvalid: isInvalid || endDateControl.props.isInvalid, + disabled: disabled || endDateControl?.props.disabled, + isInvalid: isInvalid || endDateControl?.props.isInvalid, popoverPlacement: 'downRight', className: classNames( 'euiDatePickerRange__end', - endDateControl.props.className + endDateControl?.props.className ), onBlur: (event: FocusEvent) => { - endDateControl.props?.onBlur?.(event); + endDateControl?.props?.onBlur?.(event); onBlur?.(event); }, onFocus: (event: FocusEvent) => { - endDateControl.props?.onFocus?.(event); + endDateControl?.props?.onFocus?.(event); onFocus?.(event); }, - } - ); + }); } const icon = useMemo(() => { @@ -205,6 +205,7 @@ export const EuiDatePickerRange: FunctionComponent = ({ return ( = { title: 'Forms/EuiSuperDatePicker/EuiSuperDatePicker', @@ -124,6 +127,57 @@ function CustomPanel({ applyTime }: { applyTime?: ApplyTime }) { ); } +export const QuickSelectOnly: Story = { + parameters: { + controls: { + include: ['isQuickSelectOnly'], + }, + loki: { chromeSelector: LOKI_SELECTORS.portal }, + }, + args: { + start: '2025-01-01T00:00:00', + end: 'now', + isQuickSelectOnly: false, + }, + render: function Render(args) { + const [isCollapsed, setCollapsed] = useState( + args.isQuickSelectOnly ?? false + ); + + useEffect(() => { + if (args.isQuickSelectOnly == null) return; + + setCollapsed(args.isQuickSelectOnly); + }, [args.isQuickSelectOnly]); + + return ( + + setCollapsed(true)} /> + setCollapsed(false), + }} + /> + + ); + }, + play: async ({ canvasElement }: PlayFunctionContext) => { + const canvas = within(canvasElement); + + await waitFor(async () => { + expect( + canvas.getByTestSubject('superDatePickerToggleQuickMenuButton') + ).toBeInTheDocument(); + }); + + await fireEvent.click( + canvas.getByTestSubject('superDatePickerToggleQuickMenuButton') + ); + }, +}; + /** * VRT only */ diff --git a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx index ffe3d07895e3..afed819a614b 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx +++ b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.test.tsx @@ -6,17 +6,19 @@ * Side Public License, v 1. */ -import React from 'react'; -import { fireEvent } from '@testing-library/react'; -import { render } from '../../../test/rtl'; +import React, { useState } from 'react'; +import moment from 'moment'; +import { fireEvent, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { render, waitForEuiPopoverOpen } from '../../../test/rtl'; import { requiredProps } from '../../../test'; import { shouldRenderCustomStyles } from '../../../test/internal'; - +import { EuiFieldText } from '../../form'; import { EuiSuperDatePicker, EuiSuperDatePickerProps, } from './super_date_picker'; -import moment from 'moment'; const noop = () => {}; @@ -254,6 +256,59 @@ describe('EuiSuperDatePicker', () => { ); expect(container.firstChild).toMatchSnapshot(); }); + + it('should open the quick select panel', async () => { + const Component = () => { + const [isCollapsed, setCollapsed] = useState(false); + + return ( + <> + setCollapsed(true)} + data-test-subj="euiFieldText" + /> + setCollapsed(false), + 'data-test-subj': 'euiSuperDatePickerQuickSelectButton', + }} + /> + + ); + }; + const { getByTestSubject } = render(); + + const input = getByTestSubject('euiFieldText'); + const quickSelectButton = getByTestSubject( + 'euiSuperDatePickerQuickSelectButton' + ); + const startDateButton = getByTestSubject( + 'superDatePickerstartDatePopoverButton' + ); + + expect(startDateButton).toBeInTheDocument(); + + act(() => { + userEvent.click(input); + }); + + expect(input).toHaveFocus(); + expect(startDateButton).not.toBeInTheDocument(); + + fireEvent.click(quickSelectButton); + + await waitForEuiPopoverOpen(); + + expect( + getByTestSubject('superDatePickerQuickMenu') + ).toBeInTheDocument(); + + expect(document.querySelector('.euiPanel')).toHaveFocus(); + }); }); describe('isDisabled', () => { diff --git a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx index 0317f29b5bb9..cb3652cbcba3 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx +++ b/packages/eui/src/components/date_picker/super_date_picker/super_date_picker.tsx @@ -595,15 +595,6 @@ export class EuiSuperDatePickerInternal extends Component< ], }; - if (isQuickSelectOnly) { - return ( - - ); - } - const isDisabledDisplay = isObject(isDisabled) && isDisabled?.display; if ( @@ -612,28 +603,30 @@ export class EuiSuperDatePickerInternal extends Component< ) { return ( - + {!isQuickSelectOnly && ( + + )} ); } @@ -658,55 +651,60 @@ export class EuiSuperDatePickerInternal extends Component< css={rangeCssStyles} isCustom={true} iconType={false} + delimiter={isQuickSelectOnly ? '' : undefined} startDateControl={ - + isQuickSelectOnly ? undefined : ( + + ) } endDateControl={ - + isQuickSelectOnly ? undefined : ( + + ) } /> )} diff --git a/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx b/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx index 6497f16ad33d..c7d392cefe58 100644 --- a/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx +++ b/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.stories.tsx @@ -122,8 +122,10 @@ export const Playground: Story = { return ( ); }, diff --git a/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.tsx b/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.tsx index d4d2ba2180e5..a7078f2c74e8 100644 --- a/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.tsx +++ b/packages/eui/src/components/form/form_control_layout/form_control_layout_delimited.tsx @@ -30,11 +30,11 @@ export type EuiFormControlLayoutDelimitedProps = /** * Left side control */ - startControl: ReactElement; + startControl?: ReactElement; /** * Right side control */ - endControl: ReactElement; + endControl?: ReactElement; /** * The center content. Accepts a string to be wrapped in a subdued EuiText * or a single ReactElement @@ -86,12 +86,12 @@ export const EuiFormControlLayoutDelimited: FunctionComponent< wrapperProps={{ ...rest.wrapperProps, css: wrapperStyles }} > - {addClassesToControl(startControl)} + {startControl && addClassesToControl(startControl)} - {addClassesToControl(endControl)} + {endControl && addClassesToControl(endControl)} );