From 163395da2eece9dc2eb473dc0c18920ca4d46b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 31 Jan 2024 11:18:04 +0800 Subject: [PATCH 1/8] refactor: multiple no needConfirm --- docs/demo/multiple.md | 8 ++++++ docs/examples/multiple.tsx | 37 +++++++++++++++++++++++++ src/PickerInput/hooks/useFilledProps.ts | 5 ++-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 docs/demo/multiple.md create mode 100644 docs/examples/multiple.tsx diff --git a/docs/demo/multiple.md b/docs/demo/multiple.md new file mode 100644 index 000000000..8cbb6d647 --- /dev/null +++ b/docs/demo/multiple.md @@ -0,0 +1,8 @@ +--- +title: multiple +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/multiple.tsx b/docs/examples/multiple.tsx new file mode 100644 index 000000000..fe93fb3de --- /dev/null +++ b/docs/examples/multiple.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import '../../assets/index.less'; +import type { PickerRef } from '../../src/interface'; +import SinglePicker from '../../src/PickerInput/SinglePicker'; + +import dayjs, { type Dayjs } from 'dayjs'; +import 'dayjs/locale/ar'; +import 'dayjs/locale/zh-cn'; +import LocalizedFormat from 'dayjs/plugin/localizedFormat'; +import dayjsGenerateConfig from '../../src/generate/dayjs'; +import zhCN from '../../src/locale/zh_CN'; + +dayjs.locale('zh-cn'); +dayjs.extend(LocalizedFormat); + +console.clear(); + +(window as any).dayjs = dayjs; + +const sharedLocale = { + locale: zhCN, + generateConfig: dayjsGenerateConfig, + style: { width: 300 }, +}; + +export default () => { + const singleRef = React.useRef(null); + + const [value, setValue] = React.useState(null); + + return ( +
+ + +
+ ); +}; diff --git a/src/PickerInput/hooks/useFilledProps.ts b/src/PickerInput/hooks/useFilledProps.ts index f65198364..e87e23bb4 100644 --- a/src/PickerInput/hooks/useFilledProps.ts +++ b/src/PickerInput/hooks/useFilledProps.ts @@ -128,8 +128,9 @@ export default function useFilledProps< const internalPicker: InternalMode = picker === 'date' && showTime ? 'datetime' : picker; /** The picker is `datetime` or `time` */ - const complexPicker = internalPicker === 'time' || internalPicker === 'datetime' || multiple; - const mergedNeedConfirm = needConfirm ?? complexPicker; + const multipleInteractivePicker = internalPicker === 'time' || internalPicker === 'datetime'; + const complexPicker = multipleInteractivePicker || multiple; + const mergedNeedConfirm = needConfirm ?? multipleInteractivePicker; // ========================== Time ========================== // Auto `format` need to check `showTime.showXXX` first. From d059148358c2d080f9082b8e0238a90b1468c8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 31 Jan 2024 11:30:24 +0800 Subject: [PATCH 2/8] fix: clear --- src/PickerInput/hooks/useRangeValue.ts | 2 +- tests/multiple.spec.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PickerInput/hooks/useRangeValue.ts b/src/PickerInput/hooks/useRangeValue.ts index 68a855e15..8e1c8a52a 100644 --- a/src/PickerInput/hooks/useRangeValue.ts +++ b/src/PickerInput/hooks/useRangeValue.ts @@ -137,7 +137,7 @@ export function useInnerValue date) as ValueType, generateConfig); } // Update merged value diff --git a/tests/multiple.spec.tsx b/tests/multiple.spec.tsx index 26d1b55ed..477628f42 100644 --- a/tests/multiple.spec.tsx +++ b/tests/multiple.spec.tsx @@ -27,7 +27,7 @@ describe('Picker.Multiple', () => { const onChange = jest.fn(); const onCalendarChange = jest.fn(); const { container } = render( - , + , ); expect(container.querySelector('.rc-picker-multiple')).toBeTruthy(); @@ -61,7 +61,7 @@ describe('Picker.Multiple', () => { it('panel click to remove', () => { const onChange = jest.fn(); - const { container } = render(); + const { container } = render(); openPicker(container); selectCell(1); @@ -96,6 +96,7 @@ describe('Picker.Multiple', () => { const { container } = render( , From 246bd98fc9cf3710b6accd37bf1a8a7a3bd0c631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 31 Jan 2024 14:04:57 +0800 Subject: [PATCH 3/8] docs: add limit --- docs/demo/limitation.md | 8 ++++++++ docs/examples/limitation.tsx | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 docs/demo/limitation.md create mode 100644 docs/examples/limitation.tsx diff --git a/docs/demo/limitation.md b/docs/demo/limitation.md new file mode 100644 index 000000000..8697fd98f --- /dev/null +++ b/docs/demo/limitation.md @@ -0,0 +1,8 @@ +--- +title: limitation +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/limitation.tsx b/docs/examples/limitation.tsx new file mode 100644 index 000000000..74f8300d3 --- /dev/null +++ b/docs/examples/limitation.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import '../../assets/index.less'; +import SinglePicker from '../../src/PickerInput/SinglePicker'; + +import dayjs from 'dayjs'; +import 'dayjs/locale/ar'; +import 'dayjs/locale/zh-cn'; +import LocalizedFormat from 'dayjs/plugin/localizedFormat'; +import dayjsGenerateConfig from '../../src/generate/dayjs'; +import zhCN from '../../src/locale/zh_CN'; + +dayjs.locale('zh-cn'); +dayjs.extend(LocalizedFormat); + +console.clear(); + +(window as any).dayjs = dayjs; + +const sharedLocale = { + locale: zhCN, + generateConfig: dayjsGenerateConfig, + style: { width: 300 }, +}; + +export default () => { + return ( +
+ +
+ ); +}; From b755fc7e8f1dba1e3a4aece30b432bc81054a703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 31 Jan 2024 17:03:07 +0800 Subject: [PATCH 4/8] chore: fix part of limitation --- docs/examples/limitation.tsx | 3 +- src/PickerInput/Popup/PopupPanel.tsx | 56 +++--------- src/PickerPanel/DatePanel/index.tsx | 19 ++-- src/PickerPanel/DecadePanel/index.tsx | 5 +- src/PickerPanel/MonthPanel/index.tsx | 8 +- src/PickerPanel/PanelHeader.tsx | 118 +++++++++++++++++++++---- src/PickerPanel/QuarterPanel/index.tsx | 5 +- src/PickerPanel/WeekPanel/index.tsx | 9 +- src/PickerPanel/YearPanel/index.tsx | 32 ++++--- src/PickerPanel/context.ts | 10 ++- src/PickerPanel/index.tsx | 4 + src/interface.tsx | 6 +- src/utils/dateUtil.ts | 21 +++-- 13 files changed, 191 insertions(+), 105 deletions(-) diff --git a/docs/examples/limitation.tsx b/docs/examples/limitation.tsx index 74f8300d3..d09c447f5 100644 --- a/docs/examples/limitation.tsx +++ b/docs/examples/limitation.tsx @@ -27,7 +27,8 @@ export default () => {
= MustProp Omit, 'onPickerValueChange' | 'showTime'> & FooterProps & { multiplePanel?: boolean; - minDate?: DateType; - maxDate?: DateType; range?: boolean; onPickerValueChange: (date: DateType) => void; @@ -23,17 +21,8 @@ export type PopupPanelProps = MustProp export default function PopupPanel( props: PopupPanelProps, ) { - const { - picker, - multiplePanel, - pickerValue, - onPickerValueChange, - onSubmit, - minDate, - maxDate, - range, - hoverValue, - } = props; + const { picker, multiplePanel, pickerValue, onPickerValueChange, onSubmit, range, hoverValue } = + props; const { prefixCls, generateConfig } = React.useContext(PickerContext); // ======================== Offset ======================== @@ -63,36 +52,6 @@ export default function PopupPanel( const hideHeader = picker === 'time'; - // ====================== Limitation ====================== - const needLimit = React.useCallback( - (currentPickerValue: DateType) => { - let hidePrev = false; - let hideNext = false; - - const dateBeforePickerValue = internalOffsetDate(currentPickerValue, -1); - if (minDate && generateConfig.isAfter(minDate, dateBeforePickerValue)) { - hidePrev = true; - } - - const dateAfterPickerValue = internalOffsetDate(currentPickerValue, 1); - if (maxDate && generateConfig.isAfter(dateAfterPickerValue, maxDate)) { - hideNext = true; - } - - return { - hidePrev, - hideNext, - }; - }, - [minDate, maxDate, internalOffsetDate, generateConfig], - ); - - const firstPanelNeedLimit = React.useMemo(() => needLimit(pickerValue), [pickerValue, needLimit]); - const secondPanelNeedLimit = React.useMemo( - () => needLimit(nextPickerValue), - [nextPickerValue, needLimit], - ); - // ======================== Props ========================= const pickerProps = { ...props, @@ -113,12 +72,18 @@ export default function PopupPanel( return (
{...pickerProps} /> {...pickerProps} @@ -135,7 +100,6 @@ export default function PopupPanel( diff --git a/src/PickerPanel/DatePanel/index.tsx b/src/PickerPanel/DatePanel/index.tsx index fe6fe2a62..7b24b9e2e 100644 --- a/src/PickerPanel/DatePanel/index.tsx +++ b/src/PickerPanel/DatePanel/index.tsx @@ -46,7 +46,8 @@ export default function DatePanel(props: DatePane // ========================== Base ========================== const [info, now] = useInfo(props, mode); const weekFirstDay = generateConfig.locale.getWeekFirstDay(locale.locale); - const baseDate = getWeekStartDate(locale.locale, generateConfig, pickerValue); + const monthStartDate = generateConfig.setDate(pickerValue, 1); + const baseDate = getWeekStartDate(locale.locale, generateConfig, monthStartDate); const month = generateConfig.getMonth(pickerValue); // =========================== PrefixColumn =========================== @@ -176,12 +177,16 @@ export default function DatePanel(props: DatePane
{/* Header */} - { - onPickerValueChange(generateConfig.addMonth(pickerValue, offset)); - }} - onSuperOffset={(offset) => { - onPickerValueChange(generateConfig.addYear(pickerValue, offset)); + + offset={(distance) => generateConfig.addMonth(pickerValue, distance)} + superOffset={(distance) => generateConfig.addYear(pickerValue, distance)} + onChange={onPickerValueChange} + // Limitation + getStart={(date) => generateConfig.setDate(date, 1)} + getEnd={(date) => { + let clone = generateConfig.setDate(date, 1); + clone = generateConfig.addMonth(clone, 1); + return generateConfig.addDate(clone, -1); }} > {monthYearNodes} diff --git a/src/PickerPanel/DecadePanel/index.tsx b/src/PickerPanel/DecadePanel/index.tsx index d57a067d5..59a9f5036 100644 --- a/src/PickerPanel/DecadePanel/index.tsx +++ b/src/PickerPanel/DecadePanel/index.tsx @@ -88,9 +88,8 @@ export default function DecadePanel(
{/* Header */} { - onPickerValueChange(generateConfig.addYear(pickerValue, offset * 100)); - }} + superOffset={(distance) => generateConfig.addYear(pickerValue, distance * 100)} + onChange={onPickerValueChange} > {yearNode} diff --git a/src/PickerPanel/MonthPanel/index.tsx b/src/PickerPanel/MonthPanel/index.tsx index 26421b2b6..2d18e1801 100644 --- a/src/PickerPanel/MonthPanel/index.tsx +++ b/src/PickerPanel/MonthPanel/index.tsx @@ -91,9 +91,11 @@ export default function MonthPanel(
{/* Header */} { - onPickerValueChange(generateConfig.addYear(pickerValue, offset)); - }} + superOffset={(distance) => generateConfig.addYear(pickerValue, distance)} + onChange={onPickerValueChange} + // Limitation + getStart={(date) => generateConfig.setMonth(date, 0)} + getEnd={(date) => generateConfig.setMonth(date, 11)} > {yearNode} diff --git a/src/PickerPanel/PanelHeader.tsx b/src/PickerPanel/PanelHeader.tsx index 6233ec4dd..6c0aa2c5a 100644 --- a/src/PickerPanel/PanelHeader.tsx +++ b/src/PickerPanel/PanelHeader.tsx @@ -1,3 +1,5 @@ +import { isSameOrAfter } from '@/utils/dateUtil'; +import classNames from 'classnames'; import * as React from 'react'; import { PickerHackContext, usePanelContext } from './context'; @@ -5,17 +7,26 @@ const HIDDEN_STYLE: React.CSSProperties = { visibility: 'hidden', }; -export interface HeaderProps { - onOffset?: (offset: number) => void; - onSuperOffset?: (offset: number) => void; +export interface HeaderProps { + offset?: (distance: number, date: DateType) => DateType; + superOffset?: (distance: number, date: DateType) => DateType; + onChange: (date: DateType) => void; + + // Limitation + getStart: (date: DateType) => DateType; + getEnd: (date: DateType) => DateType; children?: React.ReactNode; } -function PanelHeader(props: HeaderProps) { +function PanelHeader(props: HeaderProps) { const { - onOffset, - onSuperOffset, + offset, + superOffset, + onChange, + + getStart, + getEnd, children, } = props; @@ -28,59 +39,136 @@ function PanelHeader(props: HeaderProps) { nextIcon = '\u203A', superPrevIcon = '\u00AB', superNextIcon = '\u00BB', + + // Limitation + minDate, + maxDate, + generateConfig, + locale, + pickerValue, + panelType: type, } = usePanelContext(); const headerPrefixCls = `${prefixCls}-header`; const { hidePrev, hideNext, hideHeader } = React.useContext(PickerHackContext); + // ======================= Limitation ======================= + const disabledOffsetPrev = React.useMemo(() => { + if (!minDate || !offset) { + return false; + } + + const prevPanelStartDate = getStart(offset(-1, pickerValue)); + + return !isSameOrAfter(generateConfig, locale, prevPanelStartDate, minDate, type); + }, [minDate, offset, pickerValue, getStart, generateConfig, locale, type]); + + const disabledSuperOffsetPrev = React.useMemo(() => { + if (!minDate || !superOffset) { + return false; + } + + const prevPanelStartDate = getStart(superOffset(-1, pickerValue)); + + return !isSameOrAfter(generateConfig, locale, prevPanelStartDate, minDate, type); + }, [minDate, superOffset, pickerValue, getStart, generateConfig, locale, type]); + + const disabledOffsetNext = React.useMemo(() => { + if (!maxDate || !offset) { + return false; + } + + const nextPanelEndDate = getEnd(offset(1, pickerValue)); + + return !isSameOrAfter(generateConfig, locale, maxDate, nextPanelEndDate, type); + }, [maxDate, offset, pickerValue, getEnd, generateConfig, locale, type]); + + const disabledSuperOffsetNext = React.useMemo(() => { + if (!maxDate || !superOffset) { + return false; + } + + const nextPanelEndDate = getEnd(superOffset(1, pickerValue)); + + return !isSameOrAfter(generateConfig, locale, maxDate, nextPanelEndDate, type); + }, [maxDate, superOffset, pickerValue, getEnd, generateConfig, locale, type]); + + // ========================= Offset ========================= + const onOffset = (distance: number) => { + if (offset) { + onChange(offset(distance, pickerValue)); + } + }; + + const onSuperOffset = (distance: number) => { + if (superOffset) { + onChange(superOffset(distance, pickerValue)); + } + }; + + // ========================= Render ========================= if (hideHeader) { return null; } - // ========================= Render ========================= + const prevBtnCls = `${headerPrefixCls}-prev-btn`; + const nextBtnCls = `${headerPrefixCls}-next-btn`; + const superPrevBtnCls = `${headerPrefixCls}-super-prev-btn`; + const superNextBtnCls = `${headerPrefixCls}-super-next-btn`; + return (
- {onSuperOffset && ( + {superOffset && ( )} - {onOffset && ( + {offset && ( )}
{children}
- {onOffset && ( + {offset && ( )} - {onSuperOffset && ( + {superOffset && (