UNPKG

@grafana/ui

Version:
255 lines (252 loc) • 9.52 kB
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