@grafana/ui
Version:
Grafana Components Library
255 lines (252 loc) • 9.52 kB
JavaScript
import { jsxs, jsx } from 'react/jsx-runtime';
import { css, cx } from '@emotion/css';
import { useFloating, autoUpdate, useClick, useDismiss, useInteractions } from '@floating-ui/react';
import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { useOverlay } from '@react-aria/overlays';
import { useState, useCallback, useRef } from 'react';
import { Trans, t } from '@grafana/i18n';
import { useStyles2 } from '../../../themes/ThemeContext.mjs';
import { getPositioningMiddleware } from '../../../utils/floating.mjs';
import { Button } from '../../Button/Button.mjs';
import { Field } from '../../Forms/Field.mjs';
import { Icon } from '../../Icon/Icon.mjs';
import { Input, getInputStyles } from '../../Input/Input.mjs';
import { ScrollContainer } from '../../ScrollContainer/ScrollContainer.mjs';
import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle.mjs';
import { TimeRangeList } from '../TimeRangePicker/TimeRangeList.mjs';
import { getQuickOptions } from '../options.mjs';
import { mapRelativeTimeRangeToOption, isRangeValid, isRelativeFormat, mapOptionToRelativeTimeRange } from './utils.mjs';
"use strict";
function RelativeTimeRangePicker(props) {
const { timeRange, onChange } = props;
const [isOpen, setIsOpen] = useState(false);
const onClose = useCallback(() => setIsOpen(false), []);
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 validOptions = getQuickOptions().filter((o) => isRelativeFormat(o.from));
const placement = "bottom-start";
const middleware = getPositioningMiddleware(placement);
const { context, refs, floatingStyles } = useFloating({
open: isOpen,
placement,
onOpenChange: setIsOpen,
middleware,
whileElementsMounted: autoUpdate,
strategy: "fixed"
});
const click = useClick(context);
const dismiss = useDismiss(context);
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
const styles = useStyles2(getStyles(from.validation.errorMessage, to.validation.errorMessage));
const onChangeTimeOption = (option) => {
const relativeTimeRange = mapOptionToRelativeTimeRange(option);
if (!relativeTimeRange) {
return;
}
onClose();
setFrom({ ...from, value: option.from });
setTo({ ...to, value: option.to });
onChange(relativeTimeRange);
};
const onOpen = useCallback(
(event) => {
event.stopPropagation();
event.preventDefault();
setIsOpen(!isOpen);
},
[isOpen]
);
const onApply = (event) => {
event.preventDefault();
if (!to.validation.isValid || !from.validation.isValid) {
return;
}
const timeRange2 = mapOptionToRelativeTimeRange({
from: from.value,
to: to.value,
display: ""
});
if (!timeRange2) {
return;
}
onChange(timeRange2);
setIsOpen(false);
};
const { from: timeOptionFrom, to: timeOptionTo } = timeOption;
return /* @__PURE__ */ jsxs("div", { className: styles.container, children: [
/* @__PURE__ */ jsxs(
"button",
{
ref: refs.setReference,
className: styles.pickerInput,
type: "button",
onClick: onOpen,
...getReferenceProps(),
children: [
/* @__PURE__ */ jsx("span", { className: styles.clockIcon, children: /* @__PURE__ */ jsx(Icon, { name: "clock-nine" }) }),
/* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsxs(Trans, { i18nKey: "time-picker.time-range.from-to", children: [
{ timeOptionFrom },
" to ",
{ timeOptionTo }
] }) }),
/* @__PURE__ */ jsx("span", { className: styles.caretIcon, children: /* @__PURE__ */ jsx(Icon, { name: isOpen ? "angle-up" : "angle-down", size: "lg" }) })
]
}
),
isOpen && /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("div", { role: "presentation", className: styles.backdrop, ...underlayProps }),
/* @__PURE__ */ jsx(FocusScope, { contain: true, autoFocus: true, restoreFocus: true, children: /* @__PURE__ */ jsx("div", { ref, ...overlayProps, ...dialogProps, children: /* @__PURE__ */ jsx("div", { className: styles.content, ref: refs.setFloating, style: floatingStyles, ...getFloatingProps(), children: /* @__PURE__ */ jsxs("div", { className: styles.body, children: [
/* @__PURE__ */ jsx("div", { className: styles.leftSide, children: /* @__PURE__ */ jsx(ScrollContainer, { showScrollIndicators: true, children: /* @__PURE__ */ jsx(
TimeRangeList,
{
title: t("time-picker.time-range.example-title", "Example time ranges"),
options: validOptions,
onChange: onChangeTimeOption,
value: timeOption
}
) }) }),
/* @__PURE__ */ jsxs("div", { className: styles.rightSide, children: [
/* @__PURE__ */ jsx("div", { className: styles.title, children: /* @__PURE__ */ jsx(TimePickerTitle, { children: /* @__PURE__ */ jsx(Trans, { i18nKey: "time-picker.time-range.specify", children: "Specify time range" }) }) }),
/* @__PURE__ */ jsx(
Field,
{
label: t("time-picker.time-range.from-label", "From"),
invalid: !from.validation.isValid,
error: from.validation.errorMessage,
children: /* @__PURE__ */ jsx(
Input,
{
onClick: (event) => event.stopPropagation(),
onBlur: () => setFrom({ ...from, validation: isRangeValid(from.value) }),
onChange: (event) => setFrom({ ...from, value: event.currentTarget.value }),
value: from.value
}
)
}
),
/* @__PURE__ */ jsx(
Field,
{
label: t("time-picker.time-range.to-label", "To"),
invalid: !to.validation.isValid,
error: to.validation.errorMessage,
children: /* @__PURE__ */ jsx(
Input,
{
onClick: (event) => event.stopPropagation(),
onBlur: () => setTo({ ...to, validation: isRangeValid(to.value) }),
onChange: (event) => setTo({ ...to, value: event.currentTarget.value }),
value: to.value
}
)
}
),
/* @__PURE__ */ jsx(
Button,
{
"aria-label": t("time-picker.time-range.submit-button-label", "TimePicker submit button"),
onClick: onApply,
children: /* @__PURE__ */ jsx(Trans, { i18nKey: "time-picker.time-range.apply", children: "Apply time range" })
}
)
] })
] }) }) }) })
] })
] });
}
const getStyles = (fromError, toError) => (theme) => {
const inputStyles = getInputStyles({ theme, invalid: false });
const bodyMinimumHeight = 250;
const bodyHeight = bodyMinimumHeight + calculateErrorHeight(theme, fromError) + calculateErrorHeight(theme, toError);
return {
backdrop: css({
position: "fixed",
zIndex: theme.zIndex.modalBackdrop,
top: 0,
right: 0,
bottom: 0,
left: 0
}),
container: css({
display: "flex",
position: "relative"
}),
pickerInput: cx(
inputStyles.input,
inputStyles.wrapper,
css({
display: "flex",
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer",
paddingRight: 0,
paddingLeft: 0,
lineHeight: `${theme.spacing.gridSize * theme.components.height.md - 2}px`
})
),
caretIcon: cx(
inputStyles.suffix,
css({
position: "relative",
marginLeft: theme.spacing(0.5)
})
),
clockIcon: cx(
inputStyles.prefix,
css({
position: "relative",
marginRight: theme.spacing(0.5)
})
),
content: css({
background: theme.colors.background.primary,
boxShadow: theme.shadows.z3,
position: "absolute",
zIndex: theme.zIndex.modal,
width: "500px",
top: "100%",
borderRadius: theme.shape.radius.default,
border: `1px solid ${theme.colors.border.weak}`,
left: 0,
whiteSpace: "normal"
}),
body: css({
display: "flex",
height: `${bodyHeight}px`
}),
description: css({
color: theme.colors.text.secondary,
fontSize: theme.typography.size.sm
}),
leftSide: css({
width: "50% !important",
borderRight: `1px solid ${theme.colors.border.medium}`
}),
rightSide: css({
width: "50%",
padding: theme.spacing(1)
}),
title: css({
marginBottom: theme.spacing(1)
})
};
};
function calculateErrorHeight(theme, errorMessage) {
if (!errorMessage) {
return 0;
}
if (errorMessage.length > 34) {
return theme.spacing.gridSize * 6.5;
}
return theme.spacing.gridSize * 4;
}
export { RelativeTimeRangePicker };
//# sourceMappingURL=RelativeTimeRangePicker.mjs.map