diff --git a/docs/examples/multiple.tsx b/docs/examples/multiple.tsx index 0510eed20..0d667c893 100644 --- a/docs/examples/multiple.tsx +++ b/docs/examples/multiple.tsx @@ -29,11 +29,50 @@ export default () => { return (
- - + + + { + const locked = value.isBefore(dayjs('2021-01-05'), 'day'); + + return ( + + {label} + {!closable || locked ? ( + locked + ) : ( + + )} + + ); + }} + />
); }; diff --git a/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx b/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx index 38afe3e8a..84d731ee3 100644 --- a/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx +++ b/src/PickerInput/Selector/SingleSelector/MultipleDates.tsx @@ -2,11 +2,11 @@ import { clsx } from 'clsx'; import Overflow from '@rc-component/overflow'; import * as React from 'react'; import type { MouseEventHandler } from 'react'; -import type { PickerProps } from '../../SinglePicker'; +import type { CustomTagProps, PickerProps } from '../../SinglePicker'; export interface MultipleDatesProps extends Pick< PickerProps, - 'maxTagCount' + 'maxTagCount' | 'tagRender' > { prefixCls: string; value: DateType[]; @@ -28,6 +28,7 @@ export default function MultipleDates( formatDate, disabled, maxTagCount, + tagRender, placeholder, } = props; @@ -60,12 +61,25 @@ export default function MultipleDates( function renderItem(date: DateType) { const displayLabel: React.ReactNode = formatDate(date); + const closable = !disabled; - const onClose = (event?: React.MouseEvent) => { + const onClose: CustomTagProps['onClose'] = (event) => { if (event) event.stopPropagation(); - onRemove(date); + if (!disabled) { + onRemove(date); + } }; + if (tagRender) { + return tagRender({ + label: displayLabel, + value: date, + disabled: !!disabled, + closable, + onClose, + }); + } + return renderSelector(displayLabel, onClose); } diff --git a/src/PickerInput/Selector/SingleSelector/index.tsx b/src/PickerInput/Selector/SingleSelector/index.tsx index 7a373b9c0..ed97ba1ad 100644 --- a/src/PickerInput/Selector/SingleSelector/index.tsx +++ b/src/PickerInput/Selector/SingleSelector/index.tsx @@ -11,8 +11,7 @@ import useRootProps from '../hooks/useRootProps'; import MultipleDates from './MultipleDates'; export interface SingleSelectorProps - extends SelectorProps, - Pick { + extends SelectorProps, Pick { id?: string; value?: DateType[]; @@ -75,6 +74,7 @@ function SingleSelector( onInputChange, multiple, maxTagCount, + tagRender, // Valid format, @@ -170,6 +170,7 @@ function SingleSelector( onRemove={onMultipleRemove} formatDate={getText} maxTagCount={maxTagCount} + tagRender={tagRender} disabled={disabled} removeIcon={removeIcon} placeholder={placeholder} diff --git a/src/PickerInput/SinglePicker.tsx b/src/PickerInput/SinglePicker.tsx index df2a86a81..bdb8b78cb 100644 --- a/src/PickerInput/SinglePicker.tsx +++ b/src/PickerInput/SinglePicker.tsx @@ -36,16 +36,27 @@ import useSemantic from '../hooks/useSemantic'; // TODO: isInvalidateDate with showTime.disabledTime should not provide `range` prop -export interface BasePickerProps - extends SharedPickerProps { +export interface CustomTagProps { + label: React.ReactNode; + value: DateType; + disabled: boolean; + onClose: (event?: React.MouseEvent) => void; + closable: boolean; +} + +export interface BasePickerProps< + DateType extends object = any, +> extends SharedPickerProps { // Structure id?: string; /** Not support `time` or `datetime` picker */ multiple?: boolean; removeIcon?: React.ReactNode; - /** Only work when `multiple` is in used */ + /** Only works when `multiple` is in use */ maxTagCount?: number | 'responsive'; + /** Only works when `multiple` is in use */ + tagRender?: (props: CustomTagProps) => React.ReactNode; // Value value?: DateType | DateType[] | null; @@ -100,8 +111,7 @@ export interface BasePickerProps } export interface PickerProps - extends BasePickerProps, - Omit, 'format' | 'defaultValue'> {} + extends BasePickerProps, Omit, 'format' | 'defaultValue'> {} /** Internal usage. For cross function get same aligned props */ export type ReplacedPickerProps = { @@ -174,6 +184,7 @@ function Picker( suffixIcon, removeIcon, + tagRender, // Focus onFocus, @@ -657,6 +668,7 @@ function Picker( // Icon suffixIcon={suffixIcon} removeIcon={removeIcon} + tagRender={tagRender} // Active activeHelp={!!internalHoverValue} allHelp={!!internalHoverValue && hoverSource === 'preset'} diff --git a/src/index.tsx b/src/index.tsx index 68b81a4a1..773412e66 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -29,7 +29,11 @@ import type { PickerRef, SharedTimeProps } from './interface'; import RangePicker, { type RangePickerProps } from './PickerInput/RangePicker'; -import Picker, { type BasePickerProps, type PickerProps } from './PickerInput/SinglePicker'; +import Picker, { + type BasePickerProps, + type CustomTagProps, + type PickerProps, +} from './PickerInput/SinglePicker'; import PickerPanel, { type BasePickerPanelProps, type PickerPanelProps } from './PickerPanel'; export { Picker, RangePicker, PickerPanel }; @@ -40,6 +44,7 @@ export type { PickerRef, BasePickerProps, BasePickerPanelProps, + CustomTagProps, SharedTimeProps, }; export default Picker; diff --git a/tests/multiple.spec.tsx b/tests/multiple.spec.tsx index ee1af0495..3a9de1f88 100644 --- a/tests/multiple.spec.tsx +++ b/tests/multiple.spec.tsx @@ -140,6 +140,63 @@ describe('Picker.Multiple', () => { expect(container.querySelector('.custom-remove')).toBeTruthy(); }); + it('tagRender', () => { + const onChange = jest.fn(); + const { container } = render( + ( + + {label} + {value.date() !== 1 && ( + + )} + + )} + />, + ); + + expect(container.querySelectorAll('.custom-tag')).toHaveLength(2); + expect(container.querySelectorAll('.custom-tag-close')).toHaveLength(1); + + fireEvent.click(container.querySelector('.custom-tag-close')); + expect(onChange).toHaveBeenCalledWith(expect.anything(), ['2000-01-01']); + }); + + it('tagRender should not remove when disabled', () => { + const onChange = jest.fn(); + const tagRender = jest.fn(({ label, onClose }) => ( + + )); + + const { container } = render( + , + ); + + expect(tagRender).toHaveBeenCalledWith( + expect.objectContaining({ + disabled: true, + closable: false, + }), + ); + + fireEvent.click(container.querySelector('.custom-tag-close')); + expect(onChange).not.toHaveBeenCalled(); + expect(container.querySelectorAll('.custom-tag-close')).toHaveLength(1); + }); + describe('placeholder', () => { it('show placeholder', () => { const { container } = render();