@grafana/ui
Version:
Grafana Components Library
1 lines • 13.5 kB
Source Map (JSON)
{"version":3,"file":"TimePickerContent.mjs","sources":["../../../../../src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx"],"sourcesContent":["import { css, cx } from '@emotion/css';\nimport { memo, useMemo, useState } from 'react';\n\nimport { GrafanaTheme2, isDateTime, rangeUtil, RawTimeRange, TimeOption, TimeRange, TimeZone } from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\n\nimport { useStyles2, useTheme2 } from '../../../themes';\nimport { getFocusStyles } from '../../../themes/mixins';\nimport { t, Trans } from '../../../utils/i18n';\nimport { FilterInput } from '../../FilterInput/FilterInput';\nimport { Icon } from '../../Icon/Icon';\nimport { WeekStart } from '../WeekStartPicker';\n\nimport { TimePickerFooter } from './TimePickerFooter';\nimport { TimePickerTitle } from './TimePickerTitle';\nimport { TimeRangeContent } from './TimeRangeContent';\nimport { TimeRangeList } from './TimeRangeList';\nimport { mapOptionToTimeRange, mapRangeToTimeOption } from './mapper';\n\ninterface Props {\n value: TimeRange;\n onChange: (timeRange: TimeRange) => void;\n onChangeTimeZone: (timeZone: TimeZone) => void;\n onChangeFiscalYearStartMonth?: (month: number) => void;\n onError?: (error?: string) => void;\n timeZone?: TimeZone;\n fiscalYearStartMonth?: number;\n quickOptions?: TimeOption[];\n history?: TimeRange[];\n showHistory?: boolean;\n className?: string;\n hideTimeZone?: boolean;\n /** Reverse the order of relative and absolute range pickers. Used to left align the picker in forms */\n isReversed?: boolean;\n hideQuickRanges?: boolean;\n widthOverride?: number;\n weekStart?: WeekStart;\n}\n\nexport interface PropsWithScreenSize extends Props {\n isFullscreen: boolean;\n}\n\ninterface FormProps extends Omit<Props, 'history'> {\n historyOptions?: TimeOption[];\n}\n\nexport const TimePickerContentWithScreenSize = (props: PropsWithScreenSize) => {\n const {\n quickOptions = [],\n isReversed,\n isFullscreen,\n hideQuickRanges,\n timeZone,\n fiscalYearStartMonth,\n value,\n onChange,\n history,\n showHistory,\n className,\n hideTimeZone,\n onChangeTimeZone,\n onChangeFiscalYearStartMonth,\n } = props;\n const isHistoryEmpty = !history?.length;\n const isContainerTall =\n (isFullscreen && showHistory) || (!isFullscreen && ((showHistory && !isHistoryEmpty) || !hideQuickRanges));\n const styles = useStyles2(getStyles, isReversed, hideQuickRanges, isContainerTall, isFullscreen);\n const historyOptions = mapToHistoryOptions(history, timeZone);\n const timeOption = useTimeOption(value.raw, quickOptions);\n const [searchTerm, setSearchQuery] = useState('');\n\n const filteredQuickOptions = quickOptions.filter((o) => o.display.toLowerCase().includes(searchTerm.toLowerCase()));\n\n const onChangeTimeOption = (timeOption: TimeOption) => {\n return onChange(mapOptionToTimeRange(timeOption));\n };\n\n return (\n <div id=\"TimePickerContent\" className={cx(styles.container, className)}>\n <div className={styles.body}>\n {(!isFullscreen || !hideQuickRanges) && (\n <div className={styles.rightSide}>\n <div className={styles.timeRangeFilter}>\n <FilterInput\n width={0}\n value={searchTerm}\n onChange={setSearchQuery}\n placeholder={t('time-picker.content.filter-placeholder', 'Search quick ranges')}\n />\n </div>\n <div className={styles.scrollContent}>\n {!isFullscreen && <NarrowScreenForm {...props} historyOptions={historyOptions} />}\n {!hideQuickRanges && (\n <TimeRangeList options={filteredQuickOptions} onChange={onChangeTimeOption} value={timeOption} />\n )}\n </div>\n </div>\n )}\n {isFullscreen && (\n <div className={styles.leftSide}>\n <FullScreenForm {...props} historyOptions={historyOptions} />\n </div>\n )}\n </div>\n {!hideTimeZone && isFullscreen && (\n <TimePickerFooter\n timeZone={timeZone}\n fiscalYearStartMonth={fiscalYearStartMonth}\n onChangeTimeZone={onChangeTimeZone}\n onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}\n />\n )}\n </div>\n );\n};\n\nexport const TimePickerContent = (props: Props) => {\n const { widthOverride } = props;\n const theme = useTheme2();\n const isFullscreen = (widthOverride || window.innerWidth) >= theme.breakpoints.values.lg;\n return <TimePickerContentWithScreenSize {...props} isFullscreen={isFullscreen} />;\n};\n\nconst NarrowScreenForm = (props: FormProps) => {\n const { value, hideQuickRanges, onChange, timeZone, historyOptions = [], showHistory, onError, weekStart } = props;\n const styles = useStyles2(getNarrowScreenStyles);\n const isAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);\n const [collapsedFlag, setCollapsedFlag] = useState(!isAbsolute);\n const collapsed = hideQuickRanges ? false : collapsedFlag;\n\n const onChangeTimeOption = (timeOption: TimeOption) => {\n return onChange(mapOptionToTimeRange(timeOption, timeZone));\n };\n\n return (\n <fieldset>\n <div className={styles.header}>\n <button\n type={'button'}\n className={styles.expandButton}\n onClick={() => {\n if (!hideQuickRanges) {\n setCollapsedFlag(!collapsed);\n }\n }}\n data-testid={selectors.components.TimePicker.absoluteTimeRangeTitle}\n aria-expanded={!collapsed}\n aria-controls=\"expanded-timerange\"\n >\n <TimePickerTitle>\n <Trans i18nKey=\"time-picker.absolute.title\">Absolute time range</Trans>\n </TimePickerTitle>\n {!hideQuickRanges && <Icon name={!collapsed ? 'angle-up' : 'angle-down'} />}\n </button>\n </div>\n {!collapsed && (\n <div className={styles.body} id=\"expanded-timerange\">\n <div className={styles.form}>\n <TimeRangeContent\n value={value}\n onApply={onChange}\n timeZone={timeZone}\n isFullscreen={false}\n onError={onError}\n weekStart={weekStart}\n />\n </div>\n {showHistory && (\n <TimeRangeList\n title={t('time-picker.absolute.recent-title', 'Recently used absolute ranges')}\n options={historyOptions}\n onChange={onChangeTimeOption}\n placeholderEmpty={null}\n />\n )}\n </div>\n )}\n </fieldset>\n );\n};\n\nconst FullScreenForm = (props: FormProps) => {\n const { onChange, value, timeZone, fiscalYearStartMonth, isReversed, historyOptions, onError, weekStart } = props;\n const styles = useStyles2(getFullScreenStyles, props.hideQuickRanges);\n const onChangeTimeOption = (timeOption: TimeOption) => {\n return onChange(mapOptionToTimeRange(timeOption, timeZone));\n };\n\n return (\n <>\n <div className={styles.container}>\n <div className={styles.title} data-testid={selectors.components.TimePicker.absoluteTimeRangeTitle}>\n <TimePickerTitle>\n <Trans i18nKey=\"time-picker.absolute.title\">Absolute time range</Trans>\n </TimePickerTitle>\n </div>\n <TimeRangeContent\n value={value}\n timeZone={timeZone}\n fiscalYearStartMonth={fiscalYearStartMonth}\n onApply={onChange}\n isFullscreen={true}\n isReversed={isReversed}\n onError={onError}\n weekStart={weekStart}\n />\n </div>\n {props.showHistory && (\n <div className={styles.recent}>\n <TimeRangeList\n title={t('time-picker.absolute.recent-title', 'Recently used absolute ranges')}\n options={historyOptions || []}\n onChange={onChangeTimeOption}\n placeholderEmpty={<EmptyRecentList />}\n />\n </div>\n )}\n </>\n );\n};\n\nconst EmptyRecentList = memo(() => {\n const styles = useStyles2(getEmptyListStyles);\n const emptyRecentListText = t(\n 'time-picker.content.empty-recent-list-info',\n \"It looks like you haven't used this time picker before. As soon as you enter some time intervals, recently used intervals will appear here.\"\n );\n\n return (\n <div className={styles.container}>\n <div>\n <span>{emptyRecentListText}</span>\n </div>\n <Trans i18nKey=\"time-picker.content.empty-recent-list-docs\">\n <div>\n <a\n className={styles.link}\n href=\"https://grafana.com/docs/grafana/latest/dashboards/time-range-controls\"\n target=\"_new\"\n >\n Read the documentation\n </a>\n <span> to find out more about how to enter custom time ranges.</span>\n </div>\n </Trans>\n </div>\n );\n});\n\nfunction mapToHistoryOptions(ranges?: TimeRange[], timeZone?: TimeZone): TimeOption[] {\n if (!Array.isArray(ranges) || ranges.length === 0) {\n return [];\n }\n\n return ranges.map((range) => mapRangeToTimeOption(range, timeZone));\n}\n\nEmptyRecentList.displayName = 'EmptyRecentList';\n\nconst useTimeOption = (raw: RawTimeRange, quickOptions: TimeOption[]): TimeOption | undefined => {\n return useMemo(() => {\n if (!rangeUtil.isRelativeTimeRange(raw)) {\n return;\n }\n return quickOptions.find((option) => {\n return option.from === raw.from && option.to === raw.to;\n });\n }, [raw, quickOptions]);\n};\n\nconst getStyles = (\n theme: GrafanaTheme2,\n isReversed?: boolean,\n hideQuickRanges?: boolean,\n isContainerTall?: boolean,\n isFullscreen?: boolean\n) => ({\n container: css({\n background: theme.colors.background.elevated,\n boxShadow: theme.shadows.z3,\n width: `${isFullscreen ? '546px' : '262px'}`,\n borderRadius: theme.shape.radius.default,\n border: `1px solid ${theme.colors.border.weak}`,\n [`${isReversed ? 'left' : 'right'}`]: 0,\n display: 'flex',\n flexDirection: 'column',\n }),\n body: css({\n display: 'flex',\n flexDirection: 'row-reverse',\n height: `${isContainerTall ? '381px' : '217px'}`,\n maxHeight: '100vh',\n }),\n leftSide: css({\n display: 'flex',\n flexDirection: 'column',\n borderRight: `${isReversed ? 'none' : `1px solid ${theme.colors.border.weak}`}`,\n width: `${!hideQuickRanges ? '60%' : '100%'}`,\n overflow: 'auto',\n scrollbarWidth: 'thin',\n order: isReversed ? 1 : 0,\n }),\n rightSide: css({\n width: `${isFullscreen ? '40%' : '100%'}; !important`,\n borderRight: isReversed ? `1px solid ${theme.colors.border.weak}` : 'none',\n display: 'flex',\n flexDirection: 'column',\n }),\n timeRangeFilter: css({\n padding: theme.spacing(1),\n }),\n spacing: css({\n marginTop: '16px',\n }),\n scrollContent: css({\n overflowY: 'auto',\n scrollbarWidth: 'thin',\n }),\n});\n\nconst getNarrowScreenStyles = (theme: GrafanaTheme2) => ({\n header: css({\n display: 'flex',\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n borderBottom: `1px solid ${theme.colors.border.weak}`,\n padding: '7px 9px 7px 9px',\n }),\n expandButton: css({\n backgroundColor: 'transparent',\n border: 'none',\n display: 'flex',\n width: '100%',\n\n '&:focus-visible': getFocusStyles(theme),\n }),\n body: css({\n borderBottom: `1px solid ${theme.colors.border.weak}`,\n }),\n form: css({\n padding: '7px 9px 7px 9px',\n }),\n});\n\nconst getFullScreenStyles = (theme: GrafanaTheme2, hideQuickRanges?: boolean) => ({\n container: css({\n paddingTop: '9px',\n paddingLeft: '11px',\n paddingRight: !hideQuickRanges ? '20%' : '11px',\n }),\n title: css({\n marginBottom: '11px',\n }),\n recent: css({\n flexGrow: 1,\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'flex-end',\n paddingTop: theme.spacing(1),\n }),\n});\n\nconst getEmptyListStyles = (theme: GrafanaTheme2) => ({\n container: css({\n padding: '12px',\n margin: '12px',\n\n 'a, span': {\n fontSize: '13px',\n },\n }),\n link: css({\n color: theme.colors.text.link,\n }),\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8NA,MAAM,eAAA,GAAkB,KAAK,MAAM;AACjC,EAAM,MAAA,MAAA,GAAS,WAAW,kBAAkB,CAAA;AAC5C,EAAA,MAAM,mBAAsB,GAAA,CAAA;AAAA,IAC1B,4CAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,uBACG,IAAA,CAAA,KAAA,EAAA,EAAI,SAAW,EAAA,MAAA,CAAO,SACrB,EAAA,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,KACC,EAAA,EAAA,QAAA,kBAAA,GAAA,CAAC,MAAM,EAAA,EAAA,QAAA,EAAA,mBAAA,EAAoB,CAC7B,EAAA,CAAA;AAAA,oBACC,GAAA,CAAA,KAAA,EAAA,EAAM,OAAQ,EAAA,4CAAA,EACb,+BAAC,KACC,EAAA,EAAA,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UACC,WAAW,MAAO,CAAA,IAAA;AAAA,UAClB,IAAK,EAAA,wEAAA;AAAA,UACL,MAAO,EAAA,MAAA;AAAA,UACR,QAAA,EAAA;AAAA;AAAA,OAED;AAAA,sBACA,GAAA,CAAC,UAAK,QAAwD,EAAA,0DAAA,EAAA;AAAA,KAAA,EAChE,CACF,EAAA;AAAA,GACF,EAAA,CAAA;AAEJ,CAAC,CAAA;AAUD,eAAA,CAAgB,WAAc,GAAA,iBAAA;AA0G9B,MAAM,kBAAA,GAAqB,CAAC,KAA0B,MAAA;AAAA,EACpD,WAAW,GAAI,CAAA;AAAA,IACb,OAAS,EAAA,MAAA;AAAA,IACT,MAAQ,EAAA,MAAA;AAAA,IAER,SAAW,EAAA;AAAA,MACT,QAAU,EAAA;AAAA;AACZ,GACD,CAAA;AAAA,EACD,MAAM,GAAI,CAAA;AAAA,IACR,KAAA,EAAO,KAAM,CAAA,MAAA,CAAO,IAAK,CAAA;AAAA,GAC1B;AACH,CAAA,CAAA"}