@grafana/ui
Version:
Grafana Components Library
1 lines • 11.9 kB
Source Map (JSON)
{"version":3,"file":"TimeRangePicker.mjs","sources":["../../../../src/components/DateTimePickers/TimeRangePicker.tsx"],"sourcesContent":["import { css, cx } from '@emotion/css';\nimport { useDialog } from '@react-aria/dialog';\nimport { FocusScope } from '@react-aria/focus';\nimport { useOverlay } from '@react-aria/overlays';\nimport { memo, createRef, useState, useEffect } from 'react';\n\nimport {\n rangeUtil,\n GrafanaTheme2,\n dateTimeFormat,\n timeZoneFormatUserFriendly,\n TimeOption,\n TimeRange,\n TimeZone,\n dateMath,\n} from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\n\nimport { useStyles2 } from '../../themes/ThemeContext';\nimport { t, Trans } from '../../utils/i18n';\nimport { ButtonGroup } from '../Button';\nimport { getModalStyles } from '../Modal/getModalStyles';\nimport { getPortalContainer } from '../Portal/Portal';\nimport { ToolbarButton } from '../ToolbarButton';\nimport { Tooltip } from '../Tooltip/Tooltip';\n\nimport { TimePickerContent } from './TimeRangePicker/TimePickerContent';\nimport { WeekStart } from './WeekStartPicker';\nimport { quickOptions } from './options';\nimport { useTimeSync } from './utils/useTimeSync';\n\n/** @public */\nexport interface TimeRangePickerProps {\n hideText?: boolean;\n value: TimeRange;\n timeZone?: TimeZone;\n fiscalYearStartMonth?: number;\n\n /**\n * If you handle sync state between pickers yourself use this prop to pass the sync button component.\n * Otherwise, a default one will show automatically if sync is possible.\n */\n timeSyncButton?: JSX.Element;\n\n // Use to manually set the synced styles for the time range picker if you need to control the sync state yourself.\n isSynced?: boolean;\n\n // Use to manually set the initial sync state for the time range picker. It will use the current value to sync.\n initialIsSynced?: boolean;\n\n onChange: (timeRange: TimeRange) => void;\n onChangeTimeZone: (timeZone: TimeZone) => void;\n onChangeFiscalYearStartMonth?: (month: number) => void;\n onMoveBackward: () => void;\n onMoveForward: () => void;\n onZoom: () => void;\n onError?: (error?: string) => void;\n history?: TimeRange[];\n quickRanges?: TimeOption[];\n hideQuickRanges?: boolean;\n widthOverride?: number;\n isOnCanvas?: boolean;\n onToolbarTimePickerClick?: () => void;\n /** Which day of the week the calendar should start on. Possible values: \"saturday\", \"sunday\" or \"monday\" */\n weekStart?: WeekStart;\n}\n\nexport interface State {\n isOpen: boolean;\n}\n\nexport function TimeRangePicker(props: TimeRangePickerProps) {\n const [isOpen, setOpen] = useState(false);\n\n const {\n value,\n onMoveBackward,\n onMoveForward,\n onZoom,\n onError,\n timeZone,\n fiscalYearStartMonth,\n history,\n onChangeTimeZone,\n onChangeFiscalYearStartMonth,\n quickRanges,\n hideQuickRanges,\n widthOverride,\n isOnCanvas,\n onToolbarTimePickerClick,\n weekStart,\n initialIsSynced,\n } = props;\n\n const { onChangeWithSync, isSynced, timeSyncButton } = useTimeSync({\n initialIsSynced,\n value,\n onChangeProp: props.onChange,\n isSyncedProp: props.isSynced,\n timeSyncButtonProp: props.timeSyncButton,\n });\n\n const onChange = (timeRange: TimeRange) => {\n onChangeWithSync(timeRange);\n setOpen(false);\n };\n\n useEffect(() => {\n if (isOpen && onToolbarTimePickerClick) {\n onToolbarTimePickerClick();\n }\n }, [isOpen, onToolbarTimePickerClick]);\n\n const onToolbarButtonSwitch = () => {\n setOpen((prevState) => !prevState);\n };\n\n const onClose = () => {\n setOpen(false);\n };\n\n const overlayRef = createRef<HTMLElement>();\n const buttonRef = createRef<HTMLElement>();\n const { overlayProps, underlayProps } = useOverlay(\n {\n onClose,\n isDismissable: true,\n isOpen,\n shouldCloseOnInteractOutside: (element) => {\n const portalContainer = getPortalContainer();\n return !buttonRef.current?.contains(element) && !portalContainer.contains(element);\n },\n },\n overlayRef\n );\n const { dialogProps } = useDialog({}, overlayRef);\n\n const styles = useStyles2(getStyles);\n const { modalBackdrop } = useStyles2(getModalStyles);\n const hasAbsolute = !rangeUtil.isRelativeTime(value.raw.from) || !rangeUtil.isRelativeTime(value.raw.to);\n\n const variant = isSynced ? 'active' : isOnCanvas ? 'canvas' : 'default';\n\n const isFromAfterTo = value?.to?.isBefore(value.from);\n const timePickerIcon = isFromAfterTo ? 'exclamation-triangle' : 'clock-nine';\n\n const currentTimeRange = formattedRange(value, timeZone, quickRanges);\n\n return (\n <ButtonGroup className={styles.container}>\n {hasAbsolute && (\n <ToolbarButton\n aria-label={t('time-picker.range-picker.backwards-time-aria-label', 'Move time range backwards')}\n variant={variant}\n onClick={onMoveBackward}\n icon=\"angle-left\"\n type=\"button\"\n narrow\n />\n )}\n\n <Tooltip\n ref={buttonRef}\n content={<TimePickerTooltip timeRange={value} timeZone={timeZone} />}\n placement=\"bottom\"\n interactive\n >\n <ToolbarButton\n data-testid={selectors.components.TimePicker.openButton}\n aria-label={t('time-picker.range-picker.current-time-selected', 'Time range selected: {{currentTimeRange}}', {\n currentTimeRange,\n })}\n aria-controls=\"TimePickerContent\"\n onClick={onToolbarButtonSwitch}\n icon={timePickerIcon}\n isOpen={isOpen}\n type=\"button\"\n variant={variant}\n >\n <TimePickerButtonLabel {...props} />\n </ToolbarButton>\n </Tooltip>\n {isOpen && (\n <div data-testid={selectors.components.TimePicker.overlayContent}>\n <div role=\"presentation\" className={cx(modalBackdrop, styles.backdrop)} {...underlayProps} />\n <FocusScope contain autoFocus restoreFocus>\n <section className={styles.content} ref={overlayRef} {...overlayProps} {...dialogProps}>\n <TimePickerContent\n timeZone={timeZone}\n fiscalYearStartMonth={fiscalYearStartMonth}\n value={value}\n onChange={onChange}\n quickOptions={quickRanges || quickOptions}\n history={history}\n showHistory\n widthOverride={widthOverride}\n onChangeTimeZone={onChangeTimeZone}\n onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}\n hideQuickRanges={hideQuickRanges}\n onError={onError}\n weekStart={weekStart}\n />\n </section>\n </FocusScope>\n </div>\n )}\n\n {timeSyncButton}\n\n {hasAbsolute && (\n <ToolbarButton\n aria-label={t('time-picker.range-picker.forwards-time-aria-label', 'Move time range forwards')}\n onClick={onMoveForward}\n icon=\"angle-right\"\n narrow\n type=\"button\"\n variant={variant}\n />\n )}\n\n <Tooltip content={ZoomOutTooltip} placement=\"bottom\">\n <ToolbarButton\n aria-label={t('time-picker.range-picker.zoom-out-button', 'Zoom out time range')}\n onClick={onZoom}\n icon=\"search-minus\"\n type=\"button\"\n variant={variant}\n />\n </Tooltip>\n </ButtonGroup>\n );\n}\n\nTimeRangePicker.displayName = 'TimeRangePicker';\n\nconst ZoomOutTooltip = () => (\n <>\n <Trans i18nKey=\"time-picker.range-picker.zoom-out-tooltip\">\n Time range zoom out <br /> CTRL+Z\n </Trans>\n </>\n);\n\nexport const TimePickerTooltip = ({ timeRange, timeZone }: { timeRange: TimeRange; timeZone?: TimeZone }) => {\n const styles = useStyles2(getLabelStyles);\n\n return (\n <>\n {dateTimeFormat(timeRange.from, { timeZone })}\n <div className=\"text-center\">\n <Trans i18nKey=\"time-picker.range-picker.to\">to</Trans>\n </div>\n {dateTimeFormat(timeRange.to, { timeZone })}\n <div className=\"text-center\">\n <span className={styles.utc}>{timeZoneFormatUserFriendly(timeZone)}</span>\n </div>\n </>\n );\n};\n\ntype LabelProps = Pick<TimeRangePickerProps, 'hideText' | 'value' | 'timeZone' | 'quickRanges'>;\n\nexport const TimePickerButtonLabel = memo<LabelProps>(({ hideText, value, timeZone, quickRanges }) => {\n const styles = useStyles2(getLabelStyles);\n\n if (hideText) {\n return null;\n }\n\n return (\n <span className={styles.container} aria-live=\"polite\" aria-atomic=\"true\">\n <span>{formattedRange(value, timeZone, quickRanges)}</span>\n <span className={styles.utc}>{rangeUtil.describeTimeRangeAbbreviation(value, timeZone)}</span>\n </span>\n );\n});\n\nTimePickerButtonLabel.displayName = 'TimePickerButtonLabel';\n\nconst formattedRange = (value: TimeRange, timeZone?: TimeZone, quickRanges?: TimeOption[]) => {\n const adjustedTimeRange = {\n to: dateMath.isMathString(value.raw.to) ? value.raw.to : value.to,\n from: dateMath.isMathString(value.raw.from) ? value.raw.from : value.from,\n };\n return rangeUtil.describeTimeRange(adjustedTimeRange, timeZone, quickRanges);\n};\n\nconst getStyles = (theme: GrafanaTheme2) => {\n return {\n container: css({\n position: 'relative',\n display: 'flex',\n verticalAlign: 'middle',\n }),\n backdrop: css({\n display: 'none',\n [theme.breakpoints.down('sm')]: {\n display: 'block',\n },\n }),\n content: css({\n position: 'absolute',\n right: 0,\n top: '116%',\n zIndex: theme.zIndex.dropdown,\n\n [theme.breakpoints.down('sm')]: {\n position: 'fixed',\n right: '50%',\n top: '50%',\n transform: 'translate(50%, -50%)',\n zIndex: theme.zIndex.modal,\n },\n }),\n };\n};\n\nconst getLabelStyles = (theme: GrafanaTheme2) => {\n return {\n container: css({\n display: 'flex',\n alignItems: 'center',\n whiteSpace: 'nowrap',\n }),\n utc: css({\n color: theme.v1.palette.orange,\n fontSize: theme.typography.size.sm,\n paddingLeft: '6px',\n lineHeight: '28px',\n verticalAlign: 'bottom',\n fontWeight: theme.typography.fontWeightMedium,\n }),\n };\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAsQa,MAAA,qBAAA,GAAwB,KAAiB,CAAC,EAAE,UAAU,KAAO,EAAA,QAAA,EAAU,aAAkB,KAAA;AACpG,EAAM,MAAA,MAAA,GAAS,WAAW,cAAc,CAAA;AAExC,EAAA,IAAI,QAAU,EAAA;AACZ,IAAO,OAAA,IAAA;AAAA;AAGT,EACE,uBAAA,IAAA,CAAC,UAAK,SAAW,EAAA,MAAA,CAAO,WAAW,WAAU,EAAA,QAAA,EAAS,eAAY,MAChE,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,MAAM,EAAA,EAAA,QAAA,EAAA,cAAA,CAAe,KAAO,EAAA,QAAA,EAAU,WAAW,CAAE,EAAA,CAAA;AAAA,oBACpD,GAAA,CAAC,UAAK,SAAW,EAAA,MAAA,CAAO,KAAM,QAAU,EAAA,SAAA,CAAA,6BAAA,CAA8B,KAAO,EAAA,QAAQ,CAAE,EAAA;AAAA,GACzF,EAAA,CAAA;AAEJ,CAAC;AAED,qBAAA,CAAsB,WAAc,GAAA,uBAAA;AAEpC,MAAM,cAAiB,GAAA,CAAC,KAAkB,EAAA,QAAA,EAAqB,WAA+B,KAAA;AAC5F,EAAA,MAAM,iBAAoB,GAAA;AAAA,IACxB,EAAA,EAAI,QAAS,CAAA,YAAA,CAAa,KAAM,CAAA,GAAA,CAAI,EAAE,CAAI,GAAA,KAAA,CAAM,GAAI,CAAA,EAAA,GAAK,KAAM,CAAA,EAAA;AAAA,IAC/D,IAAA,EAAM,QAAS,CAAA,YAAA,CAAa,KAAM,CAAA,GAAA,CAAI,IAAI,CAAI,GAAA,KAAA,CAAM,GAAI,CAAA,IAAA,GAAO,KAAM,CAAA;AAAA,GACvE;AACA,EAAA,OAAO,SAAU,CAAA,iBAAA,CAAkB,iBAAmB,EAAA,QAAA,EAAU,WAAW,CAAA;AAC7E,CAAA;AAgCA,MAAM,cAAA,GAAiB,CAAC,KAAyB,KAAA;AAC/C,EAAO,OAAA;AAAA,IACL,WAAW,GAAI,CAAA;AAAA,MACb,OAAS,EAAA,MAAA;AAAA,MACT,UAAY,EAAA,QAAA;AAAA,MACZ,UAAY,EAAA;AAAA,KACb,CAAA;AAAA,IACD,KAAK,GAAI,CAAA;AAAA,MACP,KAAA,EAAO,KAAM,CAAA,EAAA,CAAG,OAAQ,CAAA,MAAA;AAAA,MACxB,QAAA,EAAU,KAAM,CAAA,UAAA,CAAW,IAAK,CAAA,EAAA;AAAA,MAChC,WAAa,EAAA,KAAA;AAAA,MACb,UAAY,EAAA,MAAA;AAAA,MACZ,aAAe,EAAA,QAAA;AAAA,MACf,UAAA,EAAY,MAAM,UAAW,CAAA;AAAA,KAC9B;AAAA,GACH;AACF,CAAA;;;;"}