Skip to content

Commit 049cbf4

Browse files
authored
feat: support customize clear icon (#660)
* feat: 支持 自定义清除按钮 * docs: update docs * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code
1 parent 5f916b6 commit 049cbf4

File tree

8 files changed

+206
-120
lines changed

8 files changed

+206
-120
lines changed

README.md

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,22 @@ render(<Picker />, mountNode);
5353
| popupStyle | React.CSSProperties | | customize popup style |
5454
| transitionName | String | '' | css class for animation |
5555
| locale | Object | import from 'rc-picker/lib/locale/en_US' | rc-picker locale |
56-
| inputReadOnly | Boolean | false | set input to read only |
57-
| allowClear | Boolean | false | whether show clear button |
58-
| autoFocus | Boolean | false | whether auto focus |
59-
| showTime | Boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
56+
| inputReadOnly | boolean | false | set input to read only |
57+
| allowClear | boolean \| { clearIcon?: ReactNode } | false | whether show clear button or customize clear button |
58+
| autoFocus | boolean | false | whether auto focus |
59+
| showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
6060
| picker | time \| date \| week \| month \| year | | control which kind of panel should be shown |
6161
| format | String \| String[] | depends on whether you set timePicker and your locale | use to format/parse date(without time) value to/from input. When an array is provided, all values are used for parsing and first value for display |
62-
| use12Hours | Boolean | false | 12 hours display mode |
62+
| use12Hours | boolean | false | 12 hours display mode |
6363
| value | moment | | current value like input's value |
6464
| defaultValue | moment | | defaultValue like input's defaultValue |
65-
| open | Boolean | false | current open state of picker. controlled prop |
65+
| open | boolean | false | current open state of picker. controlled prop |
6666
| suffixIcon | ReactNode | | The custom suffix icon |
67-
| clearIcon | ReactNode | | The custom clear icon |
6867
| prevIcon | ReactNode | | The custom prev icon |
6968
| nextIcon | ReactNode | | The custom next icon |
7069
| superPrevIcon | ReactNode | | The custom super prev icon |
7170
| superNextIcon | ReactNode | | The custom super next icon |
72-
| disabled | Boolean | false | whether the picker is disabled |
71+
| disabled | boolean | false | whether the picker is disabled |
7372
| placeholder | String | | picker input's placeholder |
7473
| getPopupContainer | function(trigger) | | to set the container of the floating layer, while the default is to create a div element in body |
7574
| onChange | Function(date: moment, dateString: string) | | a callback function, can be executed when the selected time is changing |
@@ -93,9 +92,9 @@ render(<Picker />, mountNode);
9392
| mode | time \| datetime \| date \| week \| month \| year \| decade | | control which kind of panel |
9493
| picker | time \| date \| week \| month \| year | | control which kind of panel |
9594
| tabIndex | Number | 0 | view [tabIndex](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) |
96-
| showTime | Boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
97-
| showToday | Boolean | false | whether to show today button |
98-
| disabledDate | Function(date:moment) => Boolean | | whether to disable select of current date |
95+
| showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
96+
| showToday | boolean | false | whether to show today button |
97+
| disabledDate | Function(date:moment) => boolean | | whether to disable select of current date |
9998
| dateRender | Function(currentDate:moment, today:moment) => React.Node | | custom rendering function for date cells |
10099
| monthCellRender | Function(currentDate:moment, locale:Locale) => React.Node | | Custom month cell render method |
101100
| renderExtraFooter | (mode) => React.Node | | extra footer |
@@ -118,33 +117,33 @@ render(<Picker />, mountNode);
118117
| separator | String | '~' | set separator between inputs |
119118
| picker | time \| date \| week \| month \| year | | control which kind of panel |
120119
| placeholder | [String, String] | | placeholder of date input |
121-
| showTime | Boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
120+
| showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
122121
| showTime.defaultValue | [moment, moment] | | to set default time of selected date |
123-
| use12Hours | Boolean | false | 12 hours display mode |
122+
| use12Hours | boolean | false | 12 hours display mode |
124123
| disabledTime | Function(date: moment, type:'start'\|'end'):Object | | | to specify the time that cannot be selected |
125124
| ranges | { String \| [range: string]: moment[] } \| { [range: string]: () => moment[] } | | preseted ranges for quick selection |
126125
| format | String \| String[] | depends on whether you set timePicker and your locale | use to format/parse date(without time) value to/from input. When an array is provided, all values are used for parsing and first value for display |
127-
| allowEmpty | [Boolean, Boolean] | | allow range picker clearing text |
128-
| selectable | [Boolean, Boolean] | | whether to selected picker |
129-
| disabled | Boolean | false | whether the range picker is disabled |
126+
| allowEmpty | [boolean, boolean] | | allow range picker clearing text |
127+
| selectable | [boolean, boolean] | | whether to selected picker |
128+
| disabled | boolean | false | whether the range picker is disabled |
130129
| onChange | Function(value:[moment], formatString: [string, string]) | | a callback function, can be executed when the selected time is changing |
131130
| onCalendarChange | Function(value:[moment], formatString: [string, string], info: { range:'start'\|'end' }) | | a callback function, can be executed when the start time or the end time of the range is changing |
132131
| direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. |
133-
| order | Boolean | true | (TimeRangePicker only) `false` to disable auto order |
132+
| order | boolean | true | (TimeRangePicker only) `false` to disable auto order |
134133

135134
### showTime-options
136135

137136
| Property | Type | Default | Description |
138137
| ------------------- | ------- | ------- | ---------------------------------- |
139138
| format | String | | moment format |
140-
| showHour | Boolean | true | whether show hour |
141-
| showMinute | Boolean | true | whether show minute |
142-
| showSecond | Boolean | true | whether show second |
143-
| use12Hours | Boolean | false | 12 hours display mode |
139+
| showHour | boolean | true | whether show hour |
140+
| showMinute | boolean | true | whether show minute |
141+
| showSecond | boolean | true | whether show second |
142+
| use12Hours | boolean | false | 12 hours display mode |
144143
| hourStep | Number | 1 | interval between hours in picker |
145144
| minuteStep | Number | 1 | interval between minutes in picker |
146145
| secondStep | Number | 1 | interval between seconds in picker |
147-
| hideDisabledOptions | Boolean | false | whether hide disabled options |
146+
| hideDisabledOptions | boolean | false | whether hide disabled options |
148147
| defaultValue | moment | null | default initial value |
149148

150149
## Development

src/Picker.tsx

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { formatValue, isEqual, parseValue } from './utils/dateUtil';
3737
import { toArray } from './utils/miscUtil';
3838
import { elementsContains, getDefaultFormat, getInputSize } from './utils/uiUtil';
3939
import { legacyPropsWarning } from './utils/warnUtil';
40+
import { getClearIcon } from './utils/getClearIcon';
4041

4142
export type PickerRefConfig = {
4243
focus: () => void;
@@ -49,7 +50,7 @@ export type PickerSharedProps<DateType> = {
4950
popupStyle?: React.CSSProperties;
5051
transitionName?: string;
5152
placeholder?: string;
52-
allowClear?: boolean;
53+
allowClear?: boolean | { clearIcon?: React.ReactNode };
5354
autoFocus?: boolean;
5455
disabled?: boolean;
5556
tabIndex?: number;
@@ -66,6 +67,10 @@ export type PickerSharedProps<DateType> = {
6667

6768
// Render
6869
suffixIcon?: React.ReactNode;
70+
/**
71+
* Clear all icon
72+
* @deprecated Please use `allowClear` instead
73+
**/
6974
clearIcon?: React.ReactNode;
7075
prevIcon?: React.ReactNode;
7176
nextIcon?: React.ReactNode;
@@ -477,28 +482,41 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
477482
);
478483
}
479484

480-
let clearNode: React.ReactNode;
481-
if (allowClear && mergedValue && !disabled) {
482-
clearNode = (
483-
<span
484-
onMouseDown={(e) => {
485-
e.preventDefault();
486-
e.stopPropagation();
487-
}}
488-
onMouseUp={(e) => {
489-
e.preventDefault();
490-
e.stopPropagation();
491-
triggerChange(null);
492-
triggerOpen(false);
493-
}}
494-
className={`${prefixCls}-clear`}
495-
role="button"
496-
>
497-
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
498-
</span>
485+
// ============================ Clear ============================
486+
if (process.env.NODE_ENV !== 'production') {
487+
warning(
488+
!props.clearIcon,
489+
'`clearIcon` will be removed in future. Please use `allowClear` instead.',
499490
);
500491
}
501492

493+
const mergedClearIcon: React.ReactNode = getClearIcon(
494+
prefixCls,
495+
allowClear,
496+
clearIcon,
497+
);
498+
499+
const clearNode: React.ReactNode = (
500+
<span
501+
onMouseDown={(e) => {
502+
e.preventDefault();
503+
e.stopPropagation();
504+
}}
505+
onMouseUp={(e) => {
506+
e.preventDefault();
507+
e.stopPropagation();
508+
triggerChange(null);
509+
triggerOpen(false);
510+
}}
511+
className={`${prefixCls}-clear`}
512+
role="button"
513+
>
514+
{mergedClearIcon}
515+
</span>
516+
);
517+
518+
const mergedAllowClear = !!allowClear && mergedValue && !disabled;
519+
502520
const mergedInputProps: React.InputHTMLAttributes<HTMLInputElement> & { ref: React.MutableRefObject<HTMLInputElement> } = {
503521
id,
504522
tabIndex,
@@ -515,7 +533,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
515533
...inputProps,
516534
size: getInputSize(picker, formatList[0], generateConfig),
517535
name,
518-
...pickAttrs(props, { aria: true, data: true}),
536+
...pickAttrs(props, { aria: true, data: true }),
519537
autoComplete,
520538
};
521539

@@ -590,7 +608,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
590608
>
591609
{inputNode}
592610
{suffixNode}
593-
{clearNode}
611+
{mergedAllowClear && clearNode}
594612
</div>
595613
</div>
596614
</PickerTrigger>

src/RangePicker.tsx

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import getRanges from './utils/getRanges';
4848
import { getValue, toArray, updateValues } from './utils/miscUtil';
4949
import { elementsContains, getDefaultFormat, getInputSize } from './utils/uiUtil';
5050
import { legacyPropsWarning } from './utils/warnUtil';
51+
import { getClearIcon } from './utils/getClearIcon';
5152

5253
function reorderValues<DateType>(
5354
values: RangeValue<DateType>,
@@ -689,18 +690,18 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
689690
const startStr =
690691
mergedValue && mergedValue[0]
691692
? formatValue(mergedValue[0], {
692-
locale,
693-
format: 'YYYYMMDDHHmmss',
694-
generateConfig,
695-
})
693+
locale,
694+
format: 'YYYYMMDDHHmmss',
695+
generateConfig,
696+
})
696697
: '';
697698
const endStr =
698699
mergedValue && mergedValue[1]
699700
? formatValue(mergedValue[1], {
700-
locale,
701-
format: 'YYYYMMDDHHmmss',
702-
generateConfig,
703-
})
701+
locale,
702+
format: 'YYYYMMDDHHmmss',
703+
generateConfig,
704+
})
704705
: '';
705706

706707
useEffect(() => {
@@ -752,6 +753,10 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
752753
}
753754
warning(!dateRender, `'dateRender' is deprecated. Please use 'cellRender' instead.`);
754755
warning(!monthCellRender, `'monthCellRender' is deprecated. Please use 'cellRender' instead.`);
756+
warning(
757+
!clearIcon,
758+
'`clearIcon` will be removed in future. Please use `allowClear` instead.',
759+
);
755760
}
756761

757762
// ============================ Private ============================
@@ -859,7 +864,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
859864
defaultValue={
860865
mergedActivePickerIndex === 0 ? getValue(selectedValue, 1) : getValue(selectedValue, 0)
861866
}
862-
// defaultPickerValue={undefined}
867+
// defaultPickerValue={undefined}
863868
/>
864869
</RangeContext.Provider>
865870
);
@@ -1038,39 +1043,44 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
10381043
);
10391044
}
10401045

1041-
let clearNode: React.ReactNode;
1042-
if (
1043-
allowClear &&
1044-
((getValue(mergedValue, 0) && !mergedDisabled[0]) ||
1045-
(getValue(mergedValue, 1) && !mergedDisabled[1]))
1046-
) {
1047-
clearNode = (
1048-
<span
1049-
onMouseDown={(e) => {
1050-
e.preventDefault();
1051-
e.stopPropagation();
1052-
}}
1053-
onMouseUp={(e) => {
1054-
e.preventDefault();
1055-
e.stopPropagation();
1056-
let values = mergedValue;
1046+
const mergedClearIcon: React.ReactNode = getClearIcon(
1047+
prefixCls,
1048+
allowClear,
1049+
clearIcon,
1050+
);
10571051

1058-
if (!mergedDisabled[0]) {
1059-
values = updateValues(values, null, 0);
1060-
}
1061-
if (!mergedDisabled[1]) {
1062-
values = updateValues(values, null, 1);
1063-
}
1052+
const clearNode: React.ReactNode = (
1053+
<span
1054+
onMouseDown={(e) => {
1055+
e.preventDefault();
1056+
e.stopPropagation();
1057+
}}
1058+
onMouseUp={(e) => {
1059+
e.preventDefault();
1060+
e.stopPropagation();
1061+
let values = mergedValue;
10641062

1065-
triggerChange(values, null);
1066-
triggerOpen(false, mergedActivePickerIndex, 'clear');
1067-
}}
1068-
className={`${prefixCls}-clear`}
1069-
>
1070-
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
1071-
</span>
1072-
);
1073-
}
1063+
if (!mergedDisabled[0]) {
1064+
values = updateValues(values, null, 0);
1065+
}
1066+
if (!mergedDisabled[1]) {
1067+
values = updateValues(values, null, 1);
1068+
}
1069+
1070+
triggerChange(values, null);
1071+
triggerOpen(false, mergedActivePickerIndex, 'clear');
1072+
}}
1073+
className={`${prefixCls}-clear`}
1074+
role="button"
1075+
>
1076+
{mergedClearIcon}
1077+
</span>
1078+
);
1079+
1080+
const mergedAllowClear = allowClear && (
1081+
(getValue(mergedValue as RangeValue<DateType>, 0) && !mergedDisabled[0]) ||
1082+
(getValue(mergedValue as RangeValue<DateType>, 1) && !mergedDisabled[1])
1083+
);
10741084

10751085
const inputSharedProps = {
10761086
size: getInputSize(picker, formatList[0], generateConfig),
@@ -1209,7 +1219,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
12091219
}}
12101220
/>
12111221
{suffixNode}
1212-
{clearNode}
1222+
{mergedAllowClear && clearNode}
12131223
</div>
12141224
</PickerTrigger>
12151225
</PanelContext.Provider>

src/utils/getClearIcon.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ReactNode } from "react";
2+
import React from "react";
3+
4+
export function getClearIcon(
5+
prefixCls,
6+
allowClear?: boolean | { clearIcon?: ReactNode },
7+
clearIcon?: ReactNode,
8+
) {
9+
10+
const mergedClearIcon = typeof allowClear === "object" ? allowClear.clearIcon : clearIcon;
11+
12+
return (
13+
mergedClearIcon || <span className={`${prefixCls}-clear-btn`} />
14+
);
15+
}

tests/__snapshots__/range.spec.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ exports[`Picker.Range icon 1`] = `
4545
</span>
4646
<span
4747
class="rc-picker-clear"
48+
role="button"
4849
>
4950
<span
5051
class="suffix-icon"

0 commit comments

Comments
 (0)