[v9.3.x] Alerting: Fix RelativeTimeRangePicker (#60149)
Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
This commit is contained in:
co-authored by
Konrad Lalik
parent
5a0a148dcb
commit
2bbab19dd4
+59
-44
@@ -1,5 +1,6 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React, { FormEvent, useCallback, useState } from 'react';
|
import React, { FormEvent, useCallback, useState } from 'react';
|
||||||
|
import { usePopper } from 'react-popper';
|
||||||
|
|
||||||
import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data';
|
import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar';
|
|||||||
import { Field } from '../../Forms/Field';
|
import { Field } from '../../Forms/Field';
|
||||||
import { Icon } from '../../Icon/Icon';
|
import { Icon } from '../../Icon/Icon';
|
||||||
import { getInputStyles, Input } from '../../Input/Input';
|
import { getInputStyles, Input } from '../../Input/Input';
|
||||||
|
import { Portal } from '../../Portal/Portal';
|
||||||
import { Tooltip } from '../../Tooltip/Tooltip';
|
import { Tooltip } from '../../Tooltip/Tooltip';
|
||||||
import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle';
|
import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle';
|
||||||
import { TimeRangeList } from '../TimeRangePicker/TimeRangeList';
|
import { TimeRangeList } from '../TimeRangePicker/TimeRangeList';
|
||||||
@@ -50,6 +52,12 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
|
|||||||
const [from, setFrom] = useState<InputState>({ value: timeOption.from, validation: isRangeValid(timeOption.from) });
|
const [from, setFrom] = useState<InputState>({ value: timeOption.from, validation: isRangeValid(timeOption.from) });
|
||||||
const [to, setTo] = useState<InputState>({ value: timeOption.to, validation: isRangeValid(timeOption.to) });
|
const [to, setTo] = useState<InputState>({ value: timeOption.to, validation: isRangeValid(timeOption.to) });
|
||||||
|
|
||||||
|
const [markerElement, setMarkerElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
const [selectorElement, setSelectorElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
const popper = usePopper(markerElement, selectorElement, {
|
||||||
|
placement: 'auto-start',
|
||||||
|
});
|
||||||
|
|
||||||
const styles = useStyles2(getStyles(from.validation.errorMessage, to.validation.errorMessage));
|
const styles = useStyles2(getStyles(from.validation.errorMessage, to.validation.errorMessage));
|
||||||
|
|
||||||
const onChangeTimeOption = (option: TimeOption) => {
|
const onChangeTimeOption = (option: TimeOption) => {
|
||||||
@@ -94,7 +102,7 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container} ref={setMarkerElement}>
|
||||||
<button className={styles.pickerInput} onClick={onOpen}>
|
<button className={styles.pickerInput} onClick={onOpen}>
|
||||||
<span className={styles.clockIcon}>
|
<span className={styles.clockIcon}>
|
||||||
<Icon name="clock-nine" />
|
<Icon name="clock-nine" />
|
||||||
@@ -107,52 +115,59 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<ClickOutsideWrapper includeButtonPress={false} onClick={onClose}>
|
<Portal>
|
||||||
<div className={styles.content}>
|
<ClickOutsideWrapper includeButtonPress={false} onClick={onClose}>
|
||||||
<div className={styles.body}>
|
<div
|
||||||
<CustomScrollbar className={styles.leftSide} hideHorizontalTrack>
|
className={styles.content}
|
||||||
<TimeRangeList
|
ref={setSelectorElement}
|
||||||
title={t('time-picker.time-range.example-title', 'Example time ranges')}
|
style={popper.styles.popper}
|
||||||
options={validOptions}
|
{...popper.attributes}
|
||||||
onChange={onChangeTimeOption}
|
>
|
||||||
value={timeOption}
|
<div className={styles.body}>
|
||||||
/>
|
<CustomScrollbar className={styles.leftSide} hideHorizontalTrack>
|
||||||
</CustomScrollbar>
|
<TimeRangeList
|
||||||
<div className={styles.rightSide}>
|
title={t('time-picker.time-range.example-title', 'Example time ranges')}
|
||||||
<div className={styles.title}>
|
options={validOptions}
|
||||||
<TimePickerTitle>
|
onChange={onChangeTimeOption}
|
||||||
<Tooltip content={<TooltipContent />} placement="bottom" theme="info">
|
value={timeOption}
|
||||||
<div>
|
/>
|
||||||
<Trans i18nKey="time-picker.time-range.specify">
|
</CustomScrollbar>
|
||||||
Specify time range <Icon name="info-circle" />
|
<div className={styles.rightSide}>
|
||||||
</Trans>
|
<div className={styles.title}>
|
||||||
</div>
|
<TimePickerTitle>
|
||||||
</Tooltip>
|
<Tooltip content={<TooltipContent />} placement="bottom" theme="info">
|
||||||
</TimePickerTitle>
|
<div>
|
||||||
|
<Trans i18nKey="time-picker.time-range.specify">
|
||||||
|
Specify time range <Icon name="info-circle" />
|
||||||
|
</Trans>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</TimePickerTitle>
|
||||||
|
</div>
|
||||||
|
<Field label="From" invalid={!from.validation.isValid} error={from.validation.errorMessage}>
|
||||||
|
<Input
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
onBlur={() => setFrom({ ...from, validation: isRangeValid(from.value) })}
|
||||||
|
onChange={(event) => setFrom({ ...from, value: event.currentTarget.value })}
|
||||||
|
value={from.value}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="To" invalid={!to.validation.isValid} error={to.validation.errorMessage}>
|
||||||
|
<Input
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
onBlur={() => setTo({ ...to, validation: isRangeValid(to.value) })}
|
||||||
|
onChange={(event) => setTo({ ...to, value: event.currentTarget.value })}
|
||||||
|
value={to.value}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Button aria-label="TimePicker submit button" onClick={onApply}>
|
||||||
|
Apply time range
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Field label="From" invalid={!from.validation.isValid} error={from.validation.errorMessage}>
|
|
||||||
<Input
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onBlur={() => setFrom({ ...from, validation: isRangeValid(from.value) })}
|
|
||||||
onChange={(event) => setFrom({ ...from, value: event.currentTarget.value })}
|
|
||||||
value={from.value}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field label="To" invalid={!to.validation.isValid} error={to.validation.errorMessage}>
|
|
||||||
<Input
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onBlur={() => setTo({ ...to, validation: isRangeValid(to.value) })}
|
|
||||||
onChange={(event) => setTo({ ...to, value: event.currentTarget.value })}
|
|
||||||
value={to.value}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Button aria-label="TimePicker submit button" onClick={onApply}>
|
|
||||||
Apply time range
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ClickOutsideWrapper>
|
||||||
</ClickOutsideWrapper>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user