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 { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import DatePickerHeader from './header' import Calendar from '../calendar' import DatePickerFooter from './footer' import YearAndMonthPickerHeader from '../year-and-month-picker/header' import YearAndMonthPickerOptions from '../year-and-month-picker/options' import YearAndMonthPickerFooter from '../year-and-month-picker/footer' import TimePickerHeader from '../time-picker/header' import TimePickerOptions from '../time-picker/options' import { useTranslation } from 'react-i18next' const DatePicker = ({ value, onChange, onClear, placeholder, needTimePicker = true, renderTrigger, }: DatePickerProps) => { const { t } = useTranslation() const [isOpen, setIsOpen] = useState(false) const [view, setView] = useState(ViewType.date) const containerRef = useRef(null) const [currentDate, setCurrentDate] = useState(value || dayjs()) const [selectedDate, setSelectedDate] = useState(value) const [selectedMonth, setSelectedMonth] = useState((value || dayjs()).month()) const [selectedYear, setSelectedYear] = useState((value || dayjs()).year()) useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false) setView(ViewType.date) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) const handleClickTrigger = (e: React.MouseEvent) => { e.stopPropagation() if (isOpen) { setIsOpen(false) return } setView(ViewType.date) setIsOpen(true) } 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() } const days = useMemo(() => { return getDaysInMonth(currentDate) }, [currentDate]) const handleClickNextMonth = useCallback(() => { setCurrentDate(currentDate.clone().add(1, 'month')) }, [currentDate]) const handleClickPrevMonth = useCallback(() => { setCurrentDate(currentDate.clone().subtract(1, 'month')) }, [currentDate]) const handleDateSelect = useCallback((day: Dayjs) => { const newDate = cloneTime(day, selectedDate || dayjs()) setCurrentDate(newDate) setSelectedDate(newDate) }, [selectedDate]) const handleSelectCurrentDate = () => { const newDate = dayjs() setCurrentDate(newDate) setSelectedDate(newDate) onChange(newDate) setIsOpen(false) } const handleConfirmDate = () => { onChange(selectedDate) setIsOpen(false) } const handleClickTimePicker = () => { if (view === ViewType.date) { setView(ViewType.time) return } if (view === ViewType.time) setView(ViewType.date) } const handleTimeSelect = (hour: string, minute: string, period: Period) => { const newTime = cloneTime(dayjs(), dayjs(`1/1/2000 ${hour}:${minute} ${period}`)) setSelectedDate((prev) => { return prev ? cloneTime(prev, newTime) : newTime }) } const handleSelectHour = useCallback((hour: string) => { const selectedTime = selectedDate || dayjs() handleTimeSelect(hour, selectedTime.minute().toString().padStart(2, '0'), selectedTime.format('A') as Period) }, [selectedDate]) const handleSelectMinute = useCallback((minute: string) => { const selectedTime = selectedDate || dayjs() handleTimeSelect(getHourIn12Hour(selectedTime).toString().padStart(2, '0'), minute, selectedTime.format('A') as Period) }, [selectedDate]) const handleSelectPeriod = useCallback((period: Period) => { const selectedTime = selectedDate || dayjs() handleTimeSelect(getHourIn12Hour(selectedTime).toString().padStart(2, '0'), selectedTime.minute().toString().padStart(2, '0'), period) }, [selectedDate]) const handleOpenYearMonthPicker = () => { setSelectedMonth(currentDate.month()) setSelectedYear(currentDate.year()) setView(ViewType.yearMonth) } const handleCloseYearMonthPicker = useCallback(() => { setView(ViewType.date) }, []) const handleMonthSelect = useCallback((month: number) => { setSelectedMonth(month) }, []) const handleYearSelect = useCallback((year: number) => { setSelectedYear(year) }, []) const handleYearMonthCancel = useCallback(() => { setView(ViewType.date) }, []) const handleYearMonthConfirm = () => { setCurrentDate((prev) => { return prev ? prev.clone().month(selectedMonth).year(selectedYear) : dayjs().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 placeholderDate = isOpen && selectedDate ? selectedDate.format(timeFormat) : (placeholder || t('time.defaultPlaceholder')) return ( {renderTrigger ? (renderTrigger({ value, selectedDate, isOpen, handleClear, handleClickTrigger, })) : (
)}
{/* Header */} {view === ViewType.date ? ( ) : view === ViewType.yearMonth ? ( ) : ( )} {/* Content */} { view === ViewType.date ? ( ) : view === ViewType.yearMonth ? ( ) : ( ) } {/* Footer */} { [ViewType.date, ViewType.time].includes(view) ? ( ) : ( ) }
) } export default DatePicker