From a9e39a108cd2cf0ad544e879e8a023994c4f3745 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 5 Jan 2023 11:35:47 +0000 Subject: [PATCH] RelativeTimeRangePicker: correctly trap focus in overlay (#60984) * correctly trap focus in relativetimerangepicker overlay * add underlay Co-authored-by: Michael Mandrus --- .../RelativeTimeRangePicker.tsx | 119 ++++++++++-------- 1 file changed, 69 insertions(+), 50 deletions(-) diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx index 4858ef92fb1..cc888aa713a 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx @@ -1,5 +1,8 @@ import { css, cx } from '@emotion/css'; -import React, { FormEvent, useCallback, useState } from 'react'; +import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; +import { useOverlay } from '@react-aria/overlays'; +import React, { FormEvent, useCallback, useRef, useState } from 'react'; import { usePopper } from 'react-popper'; import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data'; @@ -7,7 +10,6 @@ import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data'; import { useStyles2 } from '../../../themes'; import { Trans, t } from '../../../utils/i18n'; import { Button } from '../../Button'; -import { ClickOutsideWrapper } from '../../ClickOutsideWrapper/ClickOutsideWrapper'; import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar'; import { Field } from '../../Forms/Field'; import { Icon } from '../../Icon/Icon'; @@ -51,6 +53,12 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) { const timeOption = mapRelativeTimeRangeToOption(timeRange); const [from, setFrom] = useState({ value: timeOption.from, validation: isRangeValid(timeOption.from) }); const [to, setTo] = useState({ value: timeOption.to, validation: isRangeValid(timeOption.to) }); + const ref = useRef(null); + const { overlayProps, underlayProps } = useOverlay( + { onClose: () => setIsOpen(false), isDismissable: true, isOpen }, + ref + ); + const { dialogProps } = useDialog({}, ref); const [markerElement, setMarkerElement] = useState(null); const [selectorElement, setSelectorElement] = useState(null); @@ -116,57 +124,60 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) { {isOpen && ( - -
-
- - - -
-
- - } placement="bottom" theme="info"> -
- - Specify time range - -
-
-
+
+ +
+
+
+ + + +
+
+ + } placement="bottom" theme="info"> +
+ + Specify time range + +
+
+
+
+ + event.stopPropagation()} + onBlur={() => setFrom({ ...from, validation: isRangeValid(from.value) })} + onChange={(event) => setFrom({ ...from, value: event.currentTarget.value })} + value={from.value} + /> + + + event.stopPropagation()} + onBlur={() => setTo({ ...to, validation: isRangeValid(to.value) })} + onChange={(event) => setTo({ ...to, value: event.currentTarget.value })} + value={to.value} + /> + +
- - event.stopPropagation()} - onBlur={() => setFrom({ ...from, validation: isRangeValid(from.value) })} - onChange={(event) => setFrom({ ...from, value: event.currentTarget.value })} - value={from.value} - /> - - - event.stopPropagation()} - onBlur={() => setTo({ ...to, validation: isRangeValid(to.value) })} - onChange={(event) => setTo({ ...to, value: event.currentTarget.value })} - value={to.value} - /> - -
- +
)}
@@ -211,6 +222,14 @@ const getStyles = (fromError?: string, toError?: string) => (theme: GrafanaTheme const bodyHeight = bodyMinimumHeight + calculateErrorHeight(theme, fromError) + calculateErrorHeight(theme, toError); return { + backdrop: css` + position: fixed; + z-index: ${theme.zIndex.modalBackdrop}; + top: 0; + right: 0; + bottom: 0; + left: 0; + `, container: css` display: flex; position: relative; @@ -246,7 +265,7 @@ const getStyles = (fromError?: string, toError?: string) => (theme: GrafanaTheme background: ${theme.colors.background.primary}; box-shadow: ${theme.shadows.z3}; position: absolute; - z-index: ${theme.zIndex.dropdown}; + z-index: ${theme.zIndex.modal}; width: 500px; top: 100%; border-radius: 2px;