date-fns#set TypeScript Examples

The following examples show how to use date-fns#set. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Insights.ts    From nyxo-app with GNU General Public License v3.0 6 votes vote down vote up
dummyInsights: InsightState = {
  bedTimeWindow: {
    start: set(new Date(), { hours: 22, minutes: 0, seconds: 0 }).toISOString(),
    end: set(new Date(), {
      hours: 23,
      minutes: 30,
      seconds: 0
    }).toISOString(),
    center: set(new Date(), {
      hours: 22,
      minutes: 45,
      seconds: 0
    }).toISOString()
  }
}
Example #2
Source File: DateTimeUtils.ts    From UUI with MIT License 5 votes vote down vote up
export function getZeroDate() {
  return set(new Date(0), { hours: 0 })
}
Example #3
Source File: DateTimePicker.tsx    From UUI with MIT License 4 votes vote down vote up
DateTimePicker = UUIFunctionComponent({
  name: 'DateTimePicker',
  nodes: {
    Root: 'div',
    CalendarIcon: Icons.Calendar,
    Popover: UUIPopover,
    TextField: UUITextField,
    Activator: 'div',
    Container: 'div',
    Toolbar: 'div',
    Main: 'div',
    SelectZone: 'div',
    Section: 'div',
    YearMonthSelect: UUIYearMonthSelect,
    DateSelect: UUIDateSelect,
    TimeSelect: UUITimeSelect,
    DateTimeShortcut: UUIDateTimeShortcut,
  },
  propTypes: DateTimePickerPropTypes,
}, (props: DateTimePickerFeatureProps, { nodes }) => {
  const {
    Root, CalendarIcon, Popover, TextField,
    Activator, Container, Main, SelectZone, Section,
    YearMonthSelect, DateSelect, TimeSelect, DateTimeShortcut,
  } = nodes

  const timeSelectRef = useRef<any | null>(null)
  const [active, setActive] = useState(false)
  const [yearMonth, setYearMonth] = useState<Date>(props.value || new Date())

  const initialInnerValue = useMemo(() => {
    return {
      date: props.value,
      input: props.value === null ? '' : formatDateTime(props.value),
    }
  }, [props.value])
  const [innerValue, setInnerValue, resetInnerValue] = usePendingValue<DateTimePickerInnerValue>(initialInnerValue, (value) => {
    props.onChange(value.date)
  }, { resetWhenInitialValueChanged: true })

  const timeSelectScrollToValue = useCallback((value: Date, animate?: boolean) => {
    if (timeSelectRef.current) {
      timeSelectRef.current.scrollToValue(value, animate)
    }
  }, [])
  const openPopover = useCallback(() => { setActive(true) }, [])
  const closePopover = useCallback(() => { setActive(false) }, [])
  const handleUserValueChange = useCallback((value: Date | null) => {
    props.onChange(value)
    setYearMonth(value || new Date())
  }, [props])
  const handleUserInputChange = useCallback((value: string) => {
    if (value === '') {
      handleUserValueChange(null)
      timeSelectScrollToValue(getZeroDate())
    } else {
      setInnerValue((oldValue) => ({ ...oldValue, input: value }))
    }
  }, [handleUserValueChange, setInnerValue, timeSelectScrollToValue])
  const handleUserInputCommit = useCallback(() => {
    const input = innerValue.input
    if (input === '') return;
    const originalInput = formatDateTime(innerValue.date)
    if (originalInput === input) return;
    try {
      const result = tryParseDateTimeFromString(input)
      handleUserValueChange(result)
      timeSelectScrollToValue(result)
    } catch {
      resetInnerValue()
    }
  }, [handleUserValueChange, innerValue, resetInnerValue, timeSelectScrollToValue])
  const handleDateSelect = useCallback((value: Date) => {
    setInnerValue((oldValue) => {
      const newDate = set(oldValue.date || getZeroDate(), {
        year: value.getFullYear(),
        month: value.getMonth(),
        date: value.getDate(),
      })
      const newInput = formatDateTime(newDate)
      return { date: newDate, input: newInput }
    })
  }, [setInnerValue])
  const handleTimeSelect = useCallback((value: Date) => {
    setInnerValue((oldValue) => {
      const newDate = set(oldValue.date || new Date(), {
        hours: value.getHours(),
        minutes: value.getMinutes(),
        seconds: value.getSeconds(),
        milliseconds: value.getMilliseconds(),
      })
      const newInput = formatDateTime(newDate)
      return { date: newDate, input: newInput }
    })
  }, [setInnerValue])
  const handleShortcutSelect = useCallback((date: Date) => {
    props.onChange(date)
    timeSelectScrollToValue(date || getZeroDate(), false)
    setTimeout(() => { closePopover() }, 10)
  }, [closePopover, props, timeSelectScrollToValue])

  return (
    <Root>
      <Popover
        placement={'bottom-start'}
        active={active}
        onClickAway={() => {
          resetInnerValue();
          timeSelectScrollToValue(props.value || getZeroDate(), false)
          setTimeout(() => { closePopover() }, 10)
        }}
        activator={
          <Activator>
            <TextField
              placeholder={props.placeholder}
              value={innerValue.input}
              onChange={handleUserInputChange}
              customize={{
                Root: {
                  onClick: () => {
                    openPopover()
                  }
                },
                Input: {
                  onBlur: () => {
                    handleUserInputCommit()
                  },
                  onKeyDown: (event) => {
                    if (event.key === 'Enter') {
                      handleUserInputCommit()
                    }
                  }
                }
              }}
            />
            <CalendarIcon />
          </Activator>
        }
      >
        <Container>
          <Main>
            {props.shortcuts && (
              <DateTimeShortcut
                options={props.shortcuts}
                onSelect={handleShortcutSelect}
              />
            )}
            <SelectZone>
              <YearMonthSelect
                value={yearMonth}
                onChange={(value) => { setYearMonth(value) }}
              />
              <Section>
                <DateSelect
                  yearMonth={yearMonth}
                  selectedDates={innerValue.date ? [innerValue.date] : []}
                  onSelect={handleDateSelect}
                />
                <TimeSelect
                  ref={timeSelectRef}
                  value={innerValue.date || getZeroDate()}
                  onChange={handleTimeSelect}
                />
              </Section>
            </SelectZone>
          </Main>
          <PickerButtons
            // confirmLabel={props.confirmLabel}
            // cancelLabel={props.cancelLabel}
            onCancel={() => {
              resetInnerValue()
              timeSelectScrollToValue(props.value || getZeroDate(), false)
              setTimeout(() => { closePopover() }, 10)
            }}
            onConfirm={() => {
              setInnerValue((value) => value, true)
              timeSelectScrollToValue(innerValue.date || getZeroDate(), false)
              setTimeout(() => { closePopover() }, 10)
            }}
          />
        </Container>
      </Popover>
    </Root>
  )
})
Example #4
Source File: DateTimeRangePicker.tsx    From UUI with MIT License 4 votes vote down vote up
DateTimeRangePicker = UUIFunctionComponent({
  name: 'DateTimeRangePicker',
  nodes: {
    Root: 'div',
    ConnectIcon: Icons.ArrowRight,
    CalendarIcon: Icons.Calendar,
    Popover: UUIPopover,
    TextField: UUITextField,
    Activator: 'div',
    Container: 'div',
    Toolbar: 'div',
    Main: 'div',
    StartSection: 'div',
    EndSection: 'div',
    Section: 'div',
    PickerButtons: UUIPickerButtons,
    YearMonthSelect: UUIYearMonthSelect,
    DateSelect: UUIDateSelect,
    TimeSelect: UUITimeSelect,
    DateTimeShortcut: UUIDateTimeShortcut,
  },
  propTypes: DateTimeRangePickerPropTypes,
}, (props: DateTimeRangePickerFeatureProps, { nodes }) => {
  const {
    Root, ConnectIcon, CalendarIcon, Popover, TextField,
    Activator, Container, Main, StartSection, EndSection, Section,
    YearMonthSelect, DateSelect, TimeSelect, DateTimeShortcut, PickerButtons,
  } = nodes

  const startTimeSelectRef = useRef<any | null>(null)
  const endTimeSelectRef = useRef<any | null>(null)
  const startInputRef = useRef<HTMLInputElement | null>(null)
  const endInputRef = useRef<HTMLInputElement | null>(null)
  const [active, setActive] = useState(false)
  const [whichFocusing, setWhichFocusing] = useState<'start' | 'end'>()
  const [hoverDate, setHoverDate] = useState<Date>()

  const initialInnerValue = useMemo(() => {
    if (props.value === null) {
      return {
        startDate: null,
        endDate: null,
        startInput: '',
        endInput: '',
        startYearMonth: startOfMonth(new Date),
        endYearMonth: add(startOfMonth(new Date), { months: 1 }),
      }
    }
    return {
      startDate: props.value[0],
      endDate: props.value[1],
      startInput: formatDateTime(props.value[0]),
      endInput: formatDateTime(props.value[1]),
      startYearMonth: startOfMonth(props.value[0]),
      endYearMonth: isSameMonth(props.value[0], props.value[1]) ? add(startOfMonth(props.value[1]), { months: 1 }) : props.value[1],
    }
  }, [props.value])
  const [innerValue, setInnerValue, resetInnerValue] = usePendingValue<DateTimeRangePickerInnerValue>(initialInnerValue, (value) => {
    if (value.startDate && value.endDate) {
      handleValueOnChange([value.startDate, value.endDate])
      closePopover()
    }
  }, { resetWhenInitialValueChanged: true })

  const selectedDates = useMemo(() => {
    return compact([innerValue.startDate, innerValue.endDate])
  }, [innerValue.endDate, innerValue.startDate])

  const timeSelectScrollToValue = useCallback((type: 'start' | 'end', value: Date, animate?: boolean) => {
    if (type === 'start' && startTimeSelectRef.current) {
      startTimeSelectRef.current.scrollToValue(value, animate)
    }
    if (type === 'end' && endTimeSelectRef.current) {
      endTimeSelectRef.current.scrollToValue(value, animate)
    }
  }, [])
  const openPopover = useCallback(() => { setActive(true) }, [])
  const closePopover = useCallback(() => { setActive(false) }, [])
  const handleValueOnChange = useCallback((value: DateTimeRangePickerValue | null) => {
    const sortedValue = value?.sort((i, j) => Number(i) - Number(j)) || null
    props.onChange(sortedValue)
  }, [props])

  /**
   *
   */
  const handleInputOnSubmit = useCallback((type: 'start' | 'end') => {
    if (innerValue.startDate && innerValue.endDate) {
      const originalInput = formatDateTime(type === 'start' ? innerValue.startDate : innerValue.endDate)
      const input = type === 'start' ? innerValue.startInput : innerValue.endInput
      if (originalInput === input) return;
      try {
        if (input === '') {
          handleValueOnChange(null)
        } else {
          const result = tryParseDateTimeFromString(input)
          handleValueOnChange(type === 'start' ? [result, innerValue.endDate] : [innerValue.startDate, result])
        }
      } catch {
        resetInnerValue()
      }
    }
  }, [handleValueOnChange, innerValue.endInput, innerValue.endDate, innerValue.startInput, innerValue.startDate, resetInnerValue])
    /**
   * handle user change year or month in YearMonthSelect.
   */
  const handleStartYearMonthSelect = useCallback((value: Date) => {
    setInnerValue((oldValue) => {
      const startYearMonthDate = value
      let endYearMonthDate = oldValue.endYearMonth
      if (!isBefore(startYearMonthDate, endYearMonthDate)) {
        endYearMonthDate = add(startYearMonthDate, { months: 1 })
      }
      return {
        ...oldValue,
        startYearMonth: startYearMonthDate,
        endYearMonth: endYearMonthDate,
      }
    })
  }, [setInnerValue])
  const handleEndYearMonthSelect = useCallback((value: Date) => {
    setInnerValue((oldValue) => {
      const endYearMonthDate = value
      let startYearMonthDate = oldValue.startYearMonth
      if (!isAfter(endYearMonthDate, startYearMonthDate)) {
        startYearMonthDate = add(endYearMonthDate, { months: -1 })
      }
      return {
        ...oldValue,
        startYearMonth: startYearMonthDate,
        endYearMonth: endYearMonthDate,
      }
    })
  }, [setInnerValue])
  /**
   * handle user select date in DateSelect.
   */
  const handleDateSelect = useCallback((value: Date) => {
    let newStartValue = innerValue.startDate
    let newEndValue = innerValue.endDate
    if (
      (newStartValue !== null && newEndValue !== null) ||
      (newStartValue === null && newEndValue === null)
    ) {
      if (whichFocusing === 'end') {
        newStartValue = null
        newEndValue = value
      } else {
        newStartValue = value
        newEndValue = null
      }
    } else {
      if (newStartValue === null) newStartValue = value
      if (newEndValue === null) newEndValue = value
      if (isAfter(newStartValue, newEndValue)) {
        const tmp = new Date(newStartValue)
        newStartValue = new Date(newEndValue)
        newEndValue = tmp
      }
    }
    setInnerValue((oldValue) => {
      return {
        ...oldValue,
        startDate: newStartValue,
        startInput: formatDateTime(newStartValue),
        endDate: newEndValue,
        endInput: formatDateTime(newEndValue),
      }
    })
  }, [innerValue.endDate, innerValue.startDate, setInnerValue, whichFocusing])
  /**
   * handle user select date in TimeSelect.
   */
  const handleTimeSelect = useCallback((type: 'start' | 'end') => {
    return (value: Date) => {
      setInnerValue((oldValue) => {
        const oldDate = type === 'start' ? oldValue.startDate : oldValue.endDate
        const newDate = set(oldDate || getZeroDate(), {
          hours: value.getHours(),
          minutes: value.getMinutes(),
          seconds: value.getSeconds(),
        })
        const newInput = formatDateTime(newDate)
        return {
          ...oldValue,
          ...(type === 'start' ? {
            startDate: newDate,
            startInput: newInput,
          } : {}),
          ...(type === 'end' ? {
            endDate: newDate,
            endInput: newInput,
          } : {}),
        }
      })
    }
  }, [setInnerValue])

  return (
    <Root>
      <Popover
        placement={'bottom-start'}
        active={active}
        onClickAway={() => {
          resetInnerValue();
          timeSelectScrollToValue('start', props.value ? props.value[0] : getZeroDate(), false)
          timeSelectScrollToValue('end', props.value ? props.value[1] : getZeroDate(), false)
          setTimeout(() => { closePopover() }, 10)
        }}
        activator={
          <Activator
            onClick={() => {
              openPopover()
              setTimeout(() => {
                const focusedElement = ReactHelper.document?.activeElement
                if (startInputRef.current === focusedElement || endInputRef.current === focusedElement) return;
                if (startInputRef.current) {
                  startInputRef.current.focus()
                }
              }, 0)
            }}
          >
            <TextField
              placeholder={props.startPlaceholder}
              value={innerValue.startInput}
              onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, startInput: value })) }}
              customize={{
                Input: {
                  ref: startInputRef,
                  onFocus: () => {
                    setWhichFocusing('start')
                  },
                  onBlur: () => {
                    setWhichFocusing(undefined)
                    handleInputOnSubmit('start')
                  },
                  onKeyDown: (event) => {
                    if (event.key === 'Enter') {
                      handleInputOnSubmit('start')
                    }
                  }
                }
              }}
            />
            <ConnectIcon />
            <TextField
              placeholder={props.endPlaceholder}
              value={innerValue.endInput}
              onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, endInput: value })) }}
              customize={{
                Input: {
                  ref: endInputRef,
                  onFocus: () => {
                    setWhichFocusing('end')
                  },
                  onBlur: () => {
                    setWhichFocusing(undefined)
                    handleInputOnSubmit('end')
                  },
                  onKeyDown: (event) => {
                    if (event.key === 'Enter') {
                      handleInputOnSubmit('end')
                    }
                  }
                }
              }}
            />
            <CalendarIcon />
          </Activator>
        }
      >
        <Container>
          <Main tabIndex={-1}>
            {props.shortcuts && (
              <DateTimeShortcut
                options={props.shortcuts}
                onSelect={(value) => {
                  handleValueOnChange(value)
                  timeSelectScrollToValue('start', value ? value[0] : getZeroDate(), false)
                  timeSelectScrollToValue('end', value ? value[1] : getZeroDate(), false)
                  closePopover()
                }}
              />
            )}
            <StartSection>
              <YearMonthSelect
                value={innerValue.startYearMonth}
                onChange={handleStartYearMonthSelect}
              />
              <Section>
                <DateSelect
                  yearMonth={innerValue.startYearMonth}
                  selectedDates={selectedDates}
                  onSelect={handleDateSelect}
                  hoverDate={hoverDate}
                  onHoverDateChange={(date) => { setHoverDate(date) }}
                />
                <TimeSelect
                  ref={startTimeSelectRef}
                  value={innerValue.startDate || getZeroDate()}
                  onChange={handleTimeSelect('start')}
                />
              </Section>
            </StartSection>
            <EndSection>
              <YearMonthSelect
                value={innerValue.endYearMonth}
                onChange={handleEndYearMonthSelect}
              />
              <Section>
                <DateSelect
                  yearMonth={innerValue.endYearMonth}
                  selectedDates={selectedDates}
                  onSelect={handleDateSelect}
                  hoverDate={hoverDate}
                  onHoverDateChange={(date) => { setHoverDate(date) }}
                />
                <TimeSelect
                  ref={endTimeSelectRef}
                  value={innerValue.endDate || getZeroDate()}
                  onChange={handleTimeSelect('end')}
                />
              </Section>
            </EndSection>
          </Main>
          <PickerButtons
            confirmLabel={props.confirmLabel}
            cancelLabel={props.cancelLabel}
            onCancel={() => {
              resetInnerValue()
              timeSelectScrollToValue('start', props.value ? props.value[0] : getZeroDate(), false)
              timeSelectScrollToValue('end', props.value ? props.value[1] : getZeroDate(), false)
              setTimeout(() => { closePopover() }, 10)
            }}
            onConfirm={() => {
              setInnerValue((value) => value, true)
              if (innerValue.startDate && innerValue.endDate) {
                let data = [innerValue.startDate, innerValue.endDate]
                if (isAfter(innerValue.startDate, innerValue.endDate)) {
                  data = data.reverse()
                }
                timeSelectScrollToValue('start', data[0], false)
                timeSelectScrollToValue('end', data[1], false)
              } else {
                timeSelectScrollToValue('start', getZeroDate(), false)
                timeSelectScrollToValue('end', getZeroDate(), false)
              }
              setTimeout(() => { closePopover() }, 10)
            }}
          />
        </Container>
      </Popover>
    </Root>
  )
})
Example #5
Source File: TimePicker.tsx    From UUI with MIT License 4 votes vote down vote up
TimePicker = UUIFunctionComponent({
  name: 'TimePicker',
  nodes: {
    Root: 'div',
    ClockIcon: Icons.Clock,
    Popover: UUIPopover,
    TextField: UUITextField,
    Activator: 'div',
    Container: 'div',
    Toolbar: 'div',
    Main: 'div',
    TimeSelect: UUITimeSelect,
    DateTimeShortcut: UUIDateTimeShortcut,
    PickerButtons: UUIPickerButtons,
  },
  propTypes: TimePickerPropTypes,
}, (props: TimePickerFeatureProps, { nodes }) => {
  const {
    Root, ClockIcon, Popover, TextField,
    Activator, Container, Toolbar, Main,
    DateTimeShortcut, TimeSelect, PickerButtons,
  } = nodes

  const timeSelectRef = useRef<any | null>(null)
  const [active, setActive] = useState(false)

  const initialInnerValue = useMemo(() => {
    return {
      date: props.value,
      input: props.value === null ? '' : formatTime(props.value),
    }
  }, [props.value])
  const [innerValue, setInnerValue, resetInnerValue] = usePendingValue<TimePickerInnerValue>(initialInnerValue, (value) => {
    props.onChange(value.date)
  }, { resetWhenInitialValueChanged: true })

  const timeSelectScrollToValue = useCallback((value: Date, animate?: boolean) => {
    if (timeSelectRef.current) {
      timeSelectRef.current.scrollToValue(value, animate)
    }
  }, [])
  const openPopover = useCallback(() => { setActive(true) }, [])
  const closePopover = useCallback(() => { setActive(false) }, [])
  const handleValueOnChange = useCallback((value: Date | null) => {
    props.onChange(value)
  }, [props])
  const handleInputOnSubmit = useCallback(() => {
    const originalInput = formatTime(innerValue.date)
    const input = innerValue.input
    if (originalInput === input) return;
    try {
      if (input === '') {
        handleValueOnChange(null)
        timeSelectScrollToValue(getZeroDate())
      } else {
        const result = tryParseTimeFromString(input)
        handleValueOnChange(result)
        timeSelectScrollToValue(result)
      }
    } catch {
      resetInnerValue()
    }
  }, [handleValueOnChange, innerValue, resetInnerValue, timeSelectScrollToValue])
  const handleTimeSelect = useCallback((value: Date) => {
    setInnerValue((oldValue) => {
      const newDate = set(oldValue.date || getZeroDate(), {
        hours: value.getHours(),
        minutes: value.getMinutes(),
        seconds: value.getSeconds(),
      })
      const newInput = formatTime(newDate)
      return { date: newDate, input: newInput }
    })
  }, [setInnerValue])
  const handleShortcutSelect = useCallback((date: Date) => {
    props.onChange(date)
    timeSelectScrollToValue(date || getZeroDate(), false)
    closePopover()
  }, [closePopover, props, timeSelectScrollToValue])

  return (
    <Root>
      <Popover
        placement={'bottom-start'}
        active={active}
        onClickAway={() => {
          resetInnerValue();
          timeSelectScrollToValue(props.value || getZeroDate(), false)
          setTimeout(() => { closePopover() }, 10)
        }}
        activator={
          <Activator>
            <TextField
              placeholder={props.placeholder}
              value={innerValue.input}
              onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, input: value })) }}
              customize={{
                Root: {
                  onClick: () => {
                    openPopover()
                  }
                },
                Input: {
                  onBlur: () => {
                    handleInputOnSubmit()
                  },
                  onKeyDown: (event) => {
                    if (event.key === 'Enter') {
                      handleInputOnSubmit()
                    }
                  }
                }
              }}
            />
            <ClockIcon />
          </Activator>
        }
      >
        <Container>
          <Toolbar>
            {props.shortcuts && (
              <DateTimeShortcut
                options={props.shortcuts}
                onSelect={handleShortcutSelect}
              />
            )}
          </Toolbar>
          <Main>
            <TimeSelect
              ref={timeSelectRef}
              value={innerValue.date || getZeroDate()}
              onChange={handleTimeSelect}
            />
            <PickerButtons
              confirmLabel={props.confirmLabel}
              cancelLabel={props.cancelLabel}
              onCancel={() => {
                resetInnerValue()
                timeSelectScrollToValue(props.value || getZeroDate(), false)
                setTimeout(() => { closePopover() }, 10)
              }}
              onConfirm={() => {
                setInnerValue((value) => value, true)
                timeSelectScrollToValue(innerValue.date || getZeroDate(), false)
                setTimeout(() => { closePopover() }, 10)
              }}
            />
          </Main>
        </Container>
      </Popover>
    </Root>
  )
})
Example #6
Source File: TimeRangePicker.tsx    From UUI with MIT License 4 votes vote down vote up
TimeRangePicker = UUIFunctionComponent({
  name: 'TimeRangePicker',
  nodes: {
    Root: 'div',
    ConnectIcon: Icons.ArrowRight,
    ClockIcon: Icons.Clock,
    Popover: UUIPopover,
    TextField: UUITextField,
    Activator: 'div',
    Container: 'div',
    Toolbar: 'div',
    Main: 'div',
    StartSection: 'div',
    EndSection: 'div',
    PickerButtons: UUIPickerButtons,
    TimeSelect: UUITimeSelect,
    DateTimeShortcut: UUIDateTimeShortcut,
  },
  propTypes: TimeRangePickerPropTypes,
}, (props: TimeRangePickerFeatureProps, { nodes }) => {
  const {
    Root, ConnectIcon, ClockIcon, Popover, TextField,
    Activator, Container, Toolbar, Main, StartSection, EndSection,
    TimeSelect, DateTimeShortcut, PickerButtons,
  } = nodes

  const startTimeSelectRef = useRef<any | null>(null)
  const endTimeSelectRef = useRef<any | null>(null)
  const startInputRef = useRef<HTMLInputElement | null>(null)
  const endInputRef = useRef<HTMLInputElement | null>(null)
  const [active, setActive] = useState(false)

  const initialInnerValue = useMemo(() => {
    if (props.value === null) {
      return {
        startDate: null,
        endDate: null,
        startInput: '',
        endInput: '',
      }
    }
    return {
      startDate: props.value[0],
      endDate: props.value[1],
      startInput: formatTime(props.value[0]),
      endInput: formatTime(props.value[1]),
    }
  }, [props.value])
  const [innerValue, setInnerValue, resetInnerValue] = usePendingValue<TimeRangePickerInnerValue>(initialInnerValue, (value) => {
    if (value.startDate && value.endDate) {
      handleValueOnChange([value.startDate, value.endDate])
      closePopover()
    }
  }, { resetWhenInitialValueChanged: true })

  const timeSelectScrollToValue = useCallback((type: 'start' | 'end', value: Date, animate?: boolean) => {
    if (type === 'start' && startTimeSelectRef.current) {
      startTimeSelectRef.current.scrollToValue(value, animate)
    }
    if (type === 'end' && endTimeSelectRef.current) {
      endTimeSelectRef.current.scrollToValue(value, animate)
    }
  }, [])
  const openPopover = useCallback(() => { setActive(true) }, [])
  const closePopover = useCallback(() => { setActive(false) }, [])
  const handleValueOnChange = useCallback((value: TimeRangePickerValue | null) => {
    const sortedValue = value?.sort((i, j) => Number(i) - Number(j)) || null
    props.onChange(sortedValue)
  }, [props])

  /**
   *
   */
  const handleInputOnSubmit = useCallback((type: 'start' | 'end') => {
    if (innerValue.startDate && innerValue.endDate) {
      const originalInput = formatTime(type === 'start' ? innerValue.startDate : innerValue.endDate)
      const input = type === 'start' ? innerValue.startInput : innerValue.endInput
      if (originalInput === input) return;
      try {
        if (input === '') {
          handleValueOnChange(null)
        } else {
          const result = tryParseTimeFromString(input)
          handleValueOnChange(type === 'start' ? [result, innerValue.endDate] : [innerValue.startDate, result])
        }
      } catch {
        resetInnerValue()
      }
    }
  }, [handleValueOnChange, innerValue.endInput, innerValue.endDate, innerValue.startInput, innerValue.startDate, resetInnerValue])
  /**
   * handle user select date in TimeSelect.
   */
  const handleTimeSelect = useCallback((type: 'start' | 'end') => {
    return (value: Date) => {
      setInnerValue((oldValue) => {
        const oldDate = type === 'start' ? oldValue.startDate : oldValue.endDate
        const newDate = set(oldDate || getZeroDate(), {
          hours: value.getHours(),
          minutes: value.getMinutes(),
          seconds: value.getSeconds(),
        })
        const newInput = formatTime(newDate)
        return {
          ...oldValue,
          ...(type === 'start' ? {
            startDate: newDate,
            startInput: newInput,
          } : {}),
          ...(type === 'end' ? {
            endDate: newDate,
            endInput: newInput,
          } : {}),
        }
      })
    }
  }, [setInnerValue])

  return (
    <Root>
      <Popover
        placement={'bottom-start'}
        active={active}
        onClickAway={() => {
          resetInnerValue();
          timeSelectScrollToValue('start', props.value ? props.value[0] : getZeroDate(), false)
          timeSelectScrollToValue('end', props.value ? props.value[1] : getZeroDate(), false)
          setTimeout(() => { closePopover() }, 10)
        }}
        activator={
          <Activator
            onClick={() => {
              openPopover()
              setTimeout(() => {
                const focusedElement = ReactHelper.document?.activeElement
                if (startInputRef.current === focusedElement || endInputRef.current === focusedElement) return;
                if (startInputRef.current) {
                  startInputRef.current.focus()
                }
              }, 0)
            }}
          >
            <TextField
              placeholder={props.startPlaceholder}
              value={innerValue.startInput}
              onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, startInput: value })) }}
              customize={{
                Input: {
                  ref: startInputRef,
                  onBlur: () => {
                    handleInputOnSubmit('start')
                  },
                  onKeyDown: (event) => {
                    if (event.key === 'Enter') {
                      handleInputOnSubmit('start')
                    }
                  }
                }
              }}
            />
            <ConnectIcon />
            <TextField
              placeholder={props.endPlaceholder}
              value={innerValue.endInput}
              onChange={(value) => { setInnerValue((oldValue) => ({ ...oldValue, endInput: value })) }}
              customize={{
                Input: {
                  ref: endInputRef,
                  onBlur: () => {
                    handleInputOnSubmit('end')
                  },
                  onKeyDown: (event) => {
                    if (event.key === 'Enter') {
                      handleInputOnSubmit('end')
                    }
                  }
                }
              }}
            />
            <ClockIcon />
          </Activator>
        }
      >
        <Container>
          <Main tabIndex={-1}>
            <Toolbar>
              {props.shortcuts && (
                <DateTimeShortcut
                  options={props.shortcuts}
                  onSelect={(value) => {
                    handleValueOnChange(value)
                    timeSelectScrollToValue('start', value ? value[0] : getZeroDate(), false)
                    timeSelectScrollToValue('end', value ? value[1] : getZeroDate(), false)
                    closePopover()
                  }}
                />
              )}
            </Toolbar>
            <StartSection>
              <TimeSelect
                ref={startTimeSelectRef}
                value={innerValue.startDate || getZeroDate()}
                onChange={handleTimeSelect('start')}
              />
            </StartSection>
            <EndSection>
              <TimeSelect
                ref={endTimeSelectRef}
                value={innerValue.endDate || getZeroDate()}
                onChange={handleTimeSelect('end')}
              />
            </EndSection>
          </Main>
          <PickerButtons
            confirmLabel={props.confirmLabel}
            cancelLabel={props.cancelLabel}
            onCancel={() => {
              resetInnerValue()
              timeSelectScrollToValue('start', props.value ? props.value[0] : getZeroDate(), false)
              timeSelectScrollToValue('end', props.value ? props.value[1] : getZeroDate(), false)
              setTimeout(() => { closePopover() }, 10)
            }}
            onConfirm={() => {
              setInnerValue((value) => value, true)
              if (innerValue.startDate && innerValue.endDate) {
                let data = [innerValue.startDate, innerValue.endDate]
                if (isAfter(innerValue.startDate, innerValue.endDate)) {
                  data = data.reverse()
                }
                timeSelectScrollToValue('start', data[0], false)
                timeSelectScrollToValue('end', data[1], false)
              } else {
                timeSelectScrollToValue('start', getZeroDate(), false)
                timeSelectScrollToValue('end', getZeroDate(), false)
              }
              setTimeout(() => { closePopover() }, 10)
            }}
          />
        </Container>
      </Popover>
    </Root>
  )
})
Example #7
Source File: TimeSelect.tsx    From UUI with MIT License 4 votes vote down vote up
TimeSelect = UUIFunctionComponent({
  name: 'TimeSelect',
  nodes: {
    Root: 'div',
    SelectZone: 'div',
    Separator: 'div',
    OptionList: 'div',
    Option: 'div',
  },
  propTypes: TimeSelectPropTypes,
}, (props: TimeSelectFeatureProps, { nodes, NodeDataProps, ref }) => {
  const {
    Root, SelectZone, Separator,
    OptionList, Option,
  } = nodes

  const allOptions = useMemo(() => {
    return {
      hours: range(0, 24),
      minutes: range(0, 60),
      seconds: range(0, 60),
    }
  }, [])

  const activeOptionValue = {
    hours: props.value.getHours(),
    minutes: props.value.getMinutes(),
    seconds: props.value.getSeconds(),
  }

  const [disableHandleScroll, setDisableHandleScroll] = useState(false)
  const hourListRef = useRef<HTMLDivElement | null>(null)
  const minuteListRef = useRef<HTMLDivElement | null>(null)
  const secondListRef = useRef<HTMLDivElement | null>(null)

  const getItemHeight = useCallback((target: HTMLElement) => {
    const styles = window.getComputedStyle(target)
    const optionHeightPx = styles.getPropertyValue('--option-height')
    return Number(optionHeightPx.replace('px', ''))
  }, [])
  const scrollToValue = useCallback((value: Date, animate?: boolean) => {
    setDisableHandleScroll(true)
    const targetScrollTo = (ref: React.MutableRefObject<HTMLDivElement | null>, value: number, animate?: boolean) => {
      const target = ref.current as HTMLElement
      const itemHeight = getItemHeight(target)
      target.scrollTo({ top: value * itemHeight, behavior: animate ? "smooth" : "auto" })
    }
    targetScrollTo(hourListRef, value.getHours(), animate)
    targetScrollTo(minuteListRef, value.getMinutes(), animate)
    targetScrollTo(secondListRef, value.getSeconds(), animate)
    setTimeout(() => {
      setDisableHandleScroll(false)
    }, 500)
  }, [getItemHeight])

  useImperativeHandle(ref, () => {
    return {
      scrollToValue: scrollToValue,
    }
  })

  const scrollTo = useCallback((target: HTMLElement, top: number) => {
    target.scrollTo({ top, behavior: "smooth" })
  }, [])

  const debouncedScrollOnChange = useRef({
    hours: debounce(scrollTo, 300),
    minutes: debounce(scrollTo, 300),
    seconds: debounce(scrollTo, 300),
  })

  const handleScroll = useCallback((type: TimeSelectType) => {
    if (disableHandleScroll) return;
    const options = allOptions[type]
    return (event: React.UIEvent<HTMLDivElement, UIEvent>) => {
      const target = event.target as HTMLElement
      const itemHeight = getItemHeight(target)
      const scrollTop = target.scrollTop
      const currentIndex = Math.round((scrollTop) / itemHeight)

      const newValue = options[currentIndex];
      props.onChange(set(props.value, { [type]: newValue }))
      debouncedScrollOnChange.current[type](target, currentIndex * itemHeight)
    }
  }, [allOptions, disableHandleScroll, getItemHeight, props])

  return (
    <Root>
      {TimeSelectTypeArray.map((type, index) => {
        return (
          <React.Fragment key={type}>
            {index !== 0 && (
              <Separator>:</Separator>
            )}
            <OptionList
              ref={[hourListRef, minuteListRef, secondListRef][index]}
              key={`option-list-${type}`}
              onScroll={handleScroll(type)}
            >
              {allOptions[type].map((option) => {
                const active = activeOptionValue[type] === option
                return (
                  <Option
                    {...NodeDataProps({
                      'active': active,
                    })}
                    key={`${type}-${option}`}
                    onClick={() => {
                      const newValue = set(props.value, { [type]: option })
                      props.onChange(newValue)
                      scrollToValue(newValue)
                    }}
                  >{padStart(String(option), 2, '0')}</Option>
                )
              })}
            </OptionList>
          </React.Fragment>
        )
      })}
      <SelectZone />
    </Root>
  )
})