diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx index 15e65ffe738..68cfe803ac5 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { dateTime } from '@grafana/data'; @@ -38,4 +39,32 @@ describe('Date time picker', () => { expect(dateTimeInput).toHaveDisplayValue('2021-07-31 12:30:30'); }); + + it('should be able to select values in TimeOfDayPicker without blurring the element', async () => { + renderDatetimePicker(); + + // open the calendar + time picker + await userEvent.click(screen.getByLabelText('Time picker')); + + // open the time of day overlay + await userEvent.click(screen.getAllByRole('textbox')[1]); + + // check the hour element is visible + const hourElement = screen.getAllByRole('button', { + name: '00', + })[0]; + expect(hourElement).toBeVisible(); + + // select the hour value and check it's still visible + await userEvent.click(hourElement); + expect(hourElement).toBeVisible(); + + // click outside the overlay and check the hour element is no longer visible + await userEvent.click(document.body); + expect( + screen.queryByRole('button', { + name: '00', + }) + ).not.toBeInTheDocument(); + }); }); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index 8bcd50e8e46..d16b1542619 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -12,7 +12,7 @@ import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@ import { Button, HorizontalGroup, Icon, InlineField, Input, Portal } from '../..'; import { useStyles2, useTheme2 } from '../../../themes'; import { getModalStyles } from '../../Modal/getModalStyles'; -import { TimeOfDayPicker } from '../TimeOfDayPicker'; +import { TimeOfDayPicker, POPUP_CLASS_NAME } from '../TimeOfDayPicker'; import { getBodyStyles } from '../TimeRangePicker/CalendarBody'; import { isValid } from '../utils'; @@ -32,7 +32,15 @@ export const DateTimePicker = ({ date, maxDate, label, onChange }: Props) => { const ref = useRef(null); const { overlayProps, underlayProps } = useOverlay( - { onClose: () => setOpen(false), isDismissable: true, isOpen }, + { + onClose: () => setOpen(false), + isDismissable: true, + isOpen, + shouldCloseOnInteractOutside: (element) => { + const popupElement = document.getElementsByClassName(POPUP_CLASS_NAME)[0]; + return !(popupElement && popupElement.contains(element)); + }, + }, ref ); const { dialogProps } = useDialog({}, ref); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx index f9297501cde..7f8015ca00e 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx @@ -19,6 +19,8 @@ export interface Props { disabled?: boolean; } +export const POPUP_CLASS_NAME = 'time-of-day-picker-panel'; + export const TimeOfDayPicker = ({ minuteStep = 1, showHour = true, @@ -33,7 +35,7 @@ export const TimeOfDayPicker = ({ return ( onChange(dateTime(value))} allowEmpty={false}