Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions docs/examples/multiple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,50 @@ export default () => {
return (
<div>
<SinglePicker {...sharedLocale} multiple ref={singleRef} onOpenChange={console.error} />
<SinglePicker {...sharedLocale} multiple ref={singleRef} needConfirm />
<SinglePicker {...sharedLocale} multiple picker="week" defaultValue={[
dayjs('2021-01-01'),
dayjs('2021-01-08'),
]} />
<SinglePicker {...sharedLocale} multiple needConfirm />
<SinglePicker
{...sharedLocale}
multiple
picker="week"
defaultValue={[dayjs('2021-01-01'), dayjs('2021-01-08')]}
/>
<SinglePicker
{...sharedLocale}
multiple
defaultValue={[dayjs('2021-01-01'), dayjs('2021-01-08')]}
tagRender={({ label, value, closable, onClose }) => {
const locked = value.isBefore(dayjs('2021-01-05'), 'day');

return (
<span
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 4,
marginInlineEnd: 4,
padding: '0 6px',
border: '1px solid #1677ff',
borderRadius: 12,
}}
>
<span>{label}</span>
{!closable || locked ? (
<span>locked</span>
) : (
<button
type="button"
onMouseDown={(event) => {
event.preventDefault();
}}
onClick={onClose}
>
remove
</button>
)}
</span>
);
}}
/>
</div>
);
};
22 changes: 18 additions & 4 deletions src/PickerInput/Selector/SingleSelector/MultipleDates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateType extends object = any> extends Pick<
PickerProps,
'maxTagCount'
'maxTagCount' | 'tagRender'
> {
prefixCls: string;
value: DateType[];
Expand All @@ -28,6 +28,7 @@ export default function MultipleDates<DateType extends object = any>(
formatDate,
disabled,
maxTagCount,
tagRender,
placeholder,
} = props;

Expand Down Expand Up @@ -60,12 +61,25 @@ export default function MultipleDates<DateType extends object = any>(

function renderItem(date: DateType) {
const displayLabel: React.ReactNode = formatDate(date);
const closable = !disabled;

const onClose = (event?: React.MouseEvent) => {
const onClose: CustomTagProps<DateType>['onClose'] = (event) => {
if (event) event.stopPropagation();
onRemove(date);
if (!disabled) {
onRemove(date);
}
};
Comment thread
QDyanbing marked this conversation as resolved.

if (tagRender) {
return tagRender({
label: displayLabel,
value: date,
disabled: !!disabled,
closable,
onClose,
});
}

return renderSelector(displayLabel, onClose);
}

Expand Down
5 changes: 3 additions & 2 deletions src/PickerInput/Selector/SingleSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import useRootProps from '../hooks/useRootProps';
import MultipleDates from './MultipleDates';

export interface SingleSelectorProps<DateType extends object = any>
extends SelectorProps<DateType>,
Pick<PickerProps, 'multiple' | 'maxTagCount'> {
extends SelectorProps<DateType>, Pick<PickerProps, 'multiple' | 'maxTagCount' | 'tagRender'> {
id?: string;

value?: DateType[];
Expand Down Expand Up @@ -75,6 +74,7 @@ function SingleSelector<DateType extends object = any>(
onInputChange,
multiple,
maxTagCount,
tagRender,

// Valid
format,
Expand Down Expand Up @@ -170,6 +170,7 @@ function SingleSelector<DateType extends object = any>(
onRemove={onMultipleRemove}
formatDate={getText}
maxTagCount={maxTagCount}
tagRender={tagRender}
disabled={disabled}
removeIcon={removeIcon}
placeholder={placeholder}
Expand Down
22 changes: 17 additions & 5 deletions src/PickerInput/SinglePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,27 @@ import useSemantic from '../hooks/useSemantic';

// TODO: isInvalidateDate with showTime.disabledTime should not provide `range` prop

export interface BasePickerProps<DateType extends object = any>
extends SharedPickerProps<DateType> {
export interface CustomTagProps<DateType extends object = any> {
label: React.ReactNode;
value: DateType;
disabled: boolean;
onClose: (event?: React.MouseEvent<HTMLElement>) => void;
closable: boolean;
}

export interface BasePickerProps<
DateType extends object = any,
> extends SharedPickerProps<DateType> {
// 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<DateType>) => React.ReactNode;

// Value
value?: DateType | DateType[] | null;
Expand Down Expand Up @@ -100,8 +111,7 @@ export interface BasePickerProps<DateType extends object = any>
}

export interface PickerProps<DateType extends object = any>
extends BasePickerProps<DateType>,
Omit<SharedTimeProps<DateType>, 'format' | 'defaultValue'> {}
extends BasePickerProps<DateType>, Omit<SharedTimeProps<DateType>, 'format' | 'defaultValue'> {}

/** Internal usage. For cross function get same aligned props */
export type ReplacedPickerProps<DateType extends object = any> = {
Expand Down Expand Up @@ -174,6 +184,7 @@ function Picker<DateType extends object = any>(

suffixIcon,
removeIcon,
tagRender,

// Focus
onFocus,
Expand Down Expand Up @@ -657,6 +668,7 @@ function Picker<DateType extends object = any>(
// Icon
suffixIcon={suffixIcon}
removeIcon={removeIcon}
tagRender={tagRender}
// Active
activeHelp={!!internalHoverValue}
allHelp={!!internalHoverValue && hoverSource === 'preset'}
Expand Down
7 changes: 6 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand All @@ -40,6 +44,7 @@ export type {
PickerRef,
BasePickerProps,
BasePickerPanelProps,
CustomTagProps,
SharedTimeProps,
};
export default Picker;
57 changes: 57 additions & 0 deletions tests/multiple.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,63 @@ describe('Picker.Multiple', () => {
expect(container.querySelector('.custom-remove')).toBeTruthy();
});

it('tagRender', () => {
const onChange = jest.fn();
const { container } = render(
<DayPicker
multiple
onChange={onChange}
defaultValue={[getDay('2000-01-01'), getDay('2000-01-02')]}
tagRender={({ label, value, onClose }) => (
<span className="custom-tag">
<span className="custom-tag-label">{label}</span>
{value.date() !== 1 && (
<button type="button" className="custom-tag-close" onClick={onClose}>
x
</button>
)}
</span>
)}
/>,
);

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 }) => (
<button type="button" className="custom-tag-close" onClick={onClose}>
{label}
</button>
));

const { container } = render(
<DayPicker
multiple
disabled
onChange={onChange}
defaultValue={[getDay('2000-01-01')]}
tagRender={tagRender}
/>,
);

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(<DayPicker multiple placeholder="bamboo" />);
Expand Down
Loading