|
|
|
@@ -1,10 +1,16 @@ |
|
|
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' |
|
|
|
import dayjs, { type Dayjs } from 'dayjs' |
|
|
|
import { RiCalendarLine, RiCloseCircleFill } from '@remixicon/react' |
|
|
|
import cn from '@/utils/classnames' |
|
|
|
import type { DatePickerProps, Period } from '../types' |
|
|
|
import { ViewType } from '../types' |
|
|
|
import { cloneTime, getDaysInMonth, getHourIn12Hour } from '../utils' |
|
|
|
import type { Dayjs } from 'dayjs' |
|
|
|
import dayjs, { |
|
|
|
clearMonthMapCache, |
|
|
|
cloneTime, |
|
|
|
getDateWithTimezone, |
|
|
|
getDaysInMonth, |
|
|
|
getHourIn12Hour, |
|
|
|
} from '../utils/dayjs' |
|
|
|
import { |
|
|
|
PortalToFollowElem, |
|
|
|
PortalToFollowElemContent, |
|
|
|
@@ -22,6 +28,7 @@ import { useTranslation } from 'react-i18next' |
|
|
|
|
|
|
|
const DatePicker = ({ |
|
|
|
value, |
|
|
|
timezone, |
|
|
|
onChange, |
|
|
|
onClear, |
|
|
|
placeholder, |
|
|
|
@@ -32,12 +39,15 @@ const DatePicker = ({ |
|
|
|
const [isOpen, setIsOpen] = useState(false) |
|
|
|
const [view, setView] = useState(ViewType.date) |
|
|
|
const containerRef = useRef<HTMLDivElement>(null) |
|
|
|
const isInitial = useRef(true) |
|
|
|
const inputValue = useRef(value ? value.tz(timezone) : undefined).current |
|
|
|
const defaultValue = useRef(getDateWithTimezone({ timezone })).current |
|
|
|
|
|
|
|
const [currentDate, setCurrentDate] = useState(value || dayjs()) |
|
|
|
const [selectedDate, setSelectedDate] = useState(value) |
|
|
|
const [currentDate, setCurrentDate] = useState(inputValue || defaultValue) |
|
|
|
const [selectedDate, setSelectedDate] = useState(inputValue) |
|
|
|
|
|
|
|
const [selectedMonth, setSelectedMonth] = useState((value || dayjs()).month()) |
|
|
|
const [selectedYear, setSelectedYear] = useState((value || dayjs()).year()) |
|
|
|
const [selectedMonth, setSelectedMonth] = useState((inputValue || defaultValue).month()) |
|
|
|
const [selectedYear, setSelectedYear] = useState((inputValue || defaultValue).year()) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
const handleClickOutside = (event: MouseEvent) => { |
|
|
|
@@ -50,6 +60,25 @@ const DatePicker = ({ |
|
|
|
return () => document.removeEventListener('mousedown', handleClickOutside) |
|
|
|
}, []) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (isInitial.current) { |
|
|
|
isInitial.current = false |
|
|
|
return |
|
|
|
} |
|
|
|
clearMonthMapCache() |
|
|
|
if (value) { |
|
|
|
const newValue = getDateWithTimezone({ date: value, timezone }) |
|
|
|
setCurrentDate(newValue) |
|
|
|
setSelectedDate(newValue) |
|
|
|
onChange(newValue) |
|
|
|
} |
|
|
|
else { |
|
|
|
setCurrentDate(prev => getDateWithTimezone({ date: prev, timezone })) |
|
|
|
setSelectedDate(prev => prev ? getDateWithTimezone({ date: prev, timezone }) : undefined) |
|
|
|
} |
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps |
|
|
|
}, [timezone]) |
|
|
|
|
|
|
|
const handleClickTrigger = (e: React.MouseEvent) => { |
|
|
|
e.stopPropagation() |
|
|
|
if (isOpen) { |
|
|
|
@@ -58,15 +87,15 @@ const DatePicker = ({ |
|
|
|
} |
|
|
|
setView(ViewType.date) |
|
|
|
setIsOpen(true) |
|
|
|
if (value) { |
|
|
|
setCurrentDate(value) |
|
|
|
setSelectedDate(value) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const handleClear = (e: React.MouseEvent) => { |
|
|
|
const newDate = dayjs() |
|
|
|
e.stopPropagation() |
|
|
|
setSelectedDate(undefined) |
|
|
|
setCurrentDate(prev => prev || newDate) |
|
|
|
setSelectedMonth(prev => prev || newDate.month()) |
|
|
|
setSelectedYear(prev => prev || newDate.year()) |
|
|
|
if (!isOpen) |
|
|
|
onClear() |
|
|
|
} |
|
|
|
@@ -84,13 +113,13 @@ const DatePicker = ({ |
|
|
|
}, [currentDate]) |
|
|
|
|
|
|
|
const handleDateSelect = useCallback((day: Dayjs) => { |
|
|
|
const newDate = cloneTime(day, selectedDate || dayjs()) |
|
|
|
const newDate = cloneTime(day, selectedDate || getDateWithTimezone({ timezone })) |
|
|
|
setCurrentDate(newDate) |
|
|
|
setSelectedDate(newDate) |
|
|
|
}, [selectedDate]) |
|
|
|
}, [selectedDate, timezone]) |
|
|
|
|
|
|
|
const handleSelectCurrentDate = () => { |
|
|
|
const newDate = dayjs() |
|
|
|
const newDate = getDateWithTimezone({ timezone }) |
|
|
|
setCurrentDate(newDate) |
|
|
|
setSelectedDate(newDate) |
|
|
|
onChange(newDate) |
|
|
|
@@ -119,19 +148,19 @@ const DatePicker = ({ |
|
|
|
} |
|
|
|
|
|
|
|
const handleSelectHour = useCallback((hour: string) => { |
|
|
|
const selectedTime = selectedDate || dayjs() |
|
|
|
const selectedTime = selectedDate || getDateWithTimezone({ timezone }) |
|
|
|
handleTimeSelect(hour, selectedTime.minute().toString().padStart(2, '0'), selectedTime.format('A') as Period) |
|
|
|
}, [selectedDate]) |
|
|
|
}, [selectedDate, timezone]) |
|
|
|
|
|
|
|
const handleSelectMinute = useCallback((minute: string) => { |
|
|
|
const selectedTime = selectedDate || dayjs() |
|
|
|
const selectedTime = selectedDate || getDateWithTimezone({ timezone }) |
|
|
|
handleTimeSelect(getHourIn12Hour(selectedTime).toString().padStart(2, '0'), minute, selectedTime.format('A') as Period) |
|
|
|
}, [selectedDate]) |
|
|
|
}, [selectedDate, timezone]) |
|
|
|
|
|
|
|
const handleSelectPeriod = useCallback((period: Period) => { |
|
|
|
const selectedTime = selectedDate || dayjs() |
|
|
|
const selectedTime = selectedDate || getDateWithTimezone({ timezone }) |
|
|
|
handleTimeSelect(getHourIn12Hour(selectedTime).toString().padStart(2, '0'), selectedTime.minute().toString().padStart(2, '0'), period) |
|
|
|
}, [selectedDate]) |
|
|
|
}, [selectedDate, timezone]) |
|
|
|
|
|
|
|
const handleOpenYearMonthPicker = () => { |
|
|
|
setSelectedMonth(currentDate.month()) |
|
|
|
@@ -156,15 +185,13 @@ const DatePicker = ({ |
|
|
|
}, []) |
|
|
|
|
|
|
|
const handleYearMonthConfirm = () => { |
|
|
|
setCurrentDate((prev) => { |
|
|
|
return prev ? prev.clone().month(selectedMonth).year(selectedYear) : dayjs().month(selectedMonth).year(selectedYear) |
|
|
|
}) |
|
|
|
setCurrentDate(prev => prev.clone().month(selectedMonth).year(selectedYear)) |
|
|
|
setView(ViewType.date) |
|
|
|
} |
|
|
|
|
|
|
|
const timeFormat = needTimePicker ? 'MMMM D, YYYY hh:mm A' : 'MMMM D, YYYY' |
|
|
|
const displayValue = value?.format(timeFormat) || '' |
|
|
|
const displayTime = (selectedDate || dayjs().startOf('day')).format('hh:mm A') |
|
|
|
const displayTime = selectedDate?.format('hh:mm A') || '--:-- --' |
|
|
|
const placeholderDate = isOpen && selectedDate ? selectedDate.format(timeFormat) : (placeholder || t('time.defaultPlaceholder')) |
|
|
|
|
|
|
|
return ( |