import * as React from 'react'; import classNames from 'classnames'; import padStart from 'lodash/padStart'; import { PickerPanel as RCPickerPanel } from 'rc-picker'; import { Locale } from 'rc-picker/lib/interface'; import { GenerateConfig } from 'rc-picker/lib/generate'; import { PickerPanelBaseProps as RCPickerPanelBaseProps, PickerPanelDateProps as RCPickerPanelDateProps, PickerPanelTimeProps as RCPickerPanelTimeProps, } from 'rc-picker/lib/PickerPanel'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; import enUS from './locale/en_US'; import { ConfigContext } from '../config-provider'; import CalendarHeader from './Header'; type InjectDefaultProps = Omit< Props, 'locale' | 'generateConfig' | 'prevIcon' | 'nextIcon' | 'superPrevIcon' | 'superNextIcon' > & { locale?: typeof enUS; size?: 'large' | 'default' | 'small'; }; // Picker Props export type PickerPanelBaseProps = InjectDefaultProps>; export type PickerPanelDateProps = InjectDefaultProps>; export type PickerPanelTimeProps = InjectDefaultProps>; export type PickerProps = | PickerPanelBaseProps | PickerPanelDateProps | PickerPanelTimeProps; export type CalendarMode = 'year' | 'month'; export type HeaderRender = (config: { value: DateType; type: CalendarMode; onChange: (date: DateType) => void; onTypeChange: (type: CalendarMode) => void; }) => React.ReactNode; export interface CalendarProps { prefixCls?: string; className?: string; style?: React.CSSProperties; locale?: typeof enUS; validRange?: [DateType, DateType]; disabledDate?: (date: DateType) => boolean; dateFullCellRender?: (date: DateType) => React.ReactNode; dateCellRender?: (date: DateType) => React.ReactNode; monthFullCellRender?: (date: DateType) => React.ReactNode; monthCellRender?: (date: DateType) => React.ReactNode; headerRender?: HeaderRender; value?: DateType; defaultValue?: DateType; mode?: CalendarMode; fullscreen?: boolean; onChange?: (date: DateType) => void; onPanelChange?: (date: DateType, mode: CalendarMode) => void; onSelect?: (date: DateType) => void; } function generateCalendar(generateConfig: GenerateConfig) { function isSameMonth(date1: DateType, date2: DateType) { return ( date1 === date2 || (date1 && date2 && generateConfig.getYear(date1) === generateConfig.getYear(date2) && generateConfig.getMonth(date1) === generateConfig.getMonth(date2)) ); } function isSameDate(date1: DateType, date2: DateType) { return ( isSameMonth(date1, date2) && generateConfig.getDate(date1) === generateConfig.getDate(date2) ); } const Calendar = (props: CalendarProps) => { const { prefixCls: customizePrefixCls, className, dateFullCellRender, dateCellRender, monthFullCellRender, monthCellRender, headerRender, value, defaultValue, disabledDate, mode, validRange, fullscreen = true, onChange, onPanelChange, onSelect, } = props; const { getPrefixCls } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('picker', customizePrefixCls); const calendarPrefixCls = `${prefixCls}-calendar`; const today = generateConfig.getNow(); // ====================== State ======================= // Value const [innerValue, setInnerValue] = React.useState( () => value || defaultValue || generateConfig.getNow(), ); const mergedValue = value || innerValue; // Mode const [innerMode, setInnerMode] = React.useState(() => mode || 'month'); const mergedMode = mode || innerMode; const panelMode = React.useMemo<'month' | 'date'>( () => (mergedMode === 'year' ? 'month' : 'date'), [mergedMode], ); // Disabled Date const mergedDisabledDate = React.useMemo(() => { if (validRange) { return (date: DateType) => { return ( generateConfig.isAfter(validRange[0], date) || generateConfig.isAfter(date, validRange[1]) ); }; } return disabledDate; }, [disabledDate, validRange]); // ====================== Events ====================== const triggerPanelChange = (date: DateType, newMode: CalendarMode) => { if (onPanelChange) { onPanelChange(date, newMode); } }; const triggerChange = (date: DateType) => { setInnerValue(date); if (!isSameDate(date, mergedValue)) { triggerPanelChange(date, mergedMode); if (onChange) { onChange(date); } } }; const triggerModeChange = (newMode: CalendarMode) => { setInnerMode(newMode); triggerPanelChange(mergedValue, newMode); }; const onInternalSelect = (date: DateType) => { triggerChange(date); if (onSelect) { onSelect(date); } }; // ====================== Locale ====================== const getDefaultLocale = () => { const { locale } = props; const result = { ...enUS, ...locale, }; result.lang = { ...result.lang, ...((locale || {}) as any).lang, }; return result; }; // ====================== Render ====================== const dateRender = React.useCallback( (date: DateType): React.ReactNode => { if (dateFullCellRender) { return dateFullCellRender(date); } return (
{padStart(String(generateConfig.getDate(date)), 2, '0')}
{dateCellRender && dateCellRender(date)}
); }, [dateFullCellRender, dateCellRender], ); const monthRender = React.useCallback( (date: DateType, locale: Locale): React.ReactNode => { if (monthFullCellRender) { return monthFullCellRender(date); } const months = locale.shortMonths || generateConfig.locale.getShortMonths!(locale.locale); return (
{months[generateConfig.getMonth(date)]}
{monthCellRender && monthCellRender(date)}
); }, [monthFullCellRender, monthCellRender], ); return ( {(mergedLocale: any) => { return (
{headerRender ? ( headerRender({ value: mergedValue, type: mergedMode, onChange: onInternalSelect, onTypeChange: triggerModeChange, }) ) : ( )} monthRender(date, mergedLocale.lang)} onSelect={onInternalSelect} mode={panelMode} picker={panelMode as any} disabledDate={mergedDisabledDate} hideHeader />
); }}
); }; return Calendar; } export default generateCalendar;