{"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"}