Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support customize clear icon #660

Merged
merged 8 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
43 changes: 21 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,22 @@ render(<Picker />, mountNode);
| popupStyle | React.CSSProperties | | customize popup style |
| transitionName | String | '' | css class for animation |
| locale | Object | import from 'rc-picker/lib/locale/en_US' | rc-picker locale |
| inputReadOnly | Boolean | false | set input to read only |
| allowClear | Boolean | false | whether show clear button |
| autoFocus | Boolean | false | whether auto focus |
| showTime | Boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
| inputReadOnly | boolean | false | set input to read only |
| allowClear | boolean \| { clearIcon?: ReactNode } | false | whether show clear button or customize clear button |
| autoFocus | boolean | false | whether auto focus |
| showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
| picker | time \| date \| week \| month \| year | | control which kind of panel should be shown |
| 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 |
| use12Hours | Boolean | false | 12 hours display mode |
| use12Hours | boolean | false | 12 hours display mode |
| value | moment | | current value like input's value |
| defaultValue | moment | | defaultValue like input's defaultValue |
| open | Boolean | false | current open state of picker. controlled prop |
| open | boolean | false | current open state of picker. controlled prop |
| suffixIcon | ReactNode | | The custom suffix icon |
| clearIcon | ReactNode | | The custom clear icon |
| prevIcon | ReactNode | | The custom prev icon |
| nextIcon | ReactNode | | The custom next icon |
| superPrevIcon | ReactNode | | The custom super prev icon |
| superNextIcon | ReactNode | | The custom super next icon |
| disabled | Boolean | false | whether the picker is disabled |
| disabled | boolean | false | whether the picker is disabled |
| placeholder | String | | picker input's placeholder |
| getPopupContainer | function(trigger) | | to set the container of the floating layer, while the default is to create a div element in body |
| onChange | Function(date: moment, dateString: string) | | a callback function, can be executed when the selected time is changing |
Expand All @@ -93,9 +92,9 @@ render(<Picker />, mountNode);
| mode | time \| datetime \| date \| week \| month \| year \| decade | | control which kind of panel |
| picker | time \| date \| week \| month \| year | | control which kind of panel |
| tabIndex | Number | 0 | view [tabIndex](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) |
| showTime | Boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
| showToday | Boolean | false | whether to show today button |
| disabledDate | Function(date:moment) => Boolean | | whether to disable select of current date |
| showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
| showToday | boolean | false | whether to show today button |
| disabledDate | Function(date:moment) => boolean | | whether to disable select of current date |
| dateRender | Function(currentDate:moment, today:moment) => React.Node | | custom rendering function for date cells |
| monthCellRender | Function(currentDate:moment, locale:Locale) => React.Node | | Custom month cell render method |
| renderExtraFooter | (mode) => React.Node | | extra footer |
Expand All @@ -118,33 +117,33 @@ render(<Picker />, mountNode);
| separator | String | '~' | set separator between inputs |
| picker | time \| date \| week \| month \| year | | control which kind of panel |
| placeholder | [String, String] | | placeholder of date input |
| showTime | Boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
| showTime | boolean \| Object | [showTime options](#showTime-options) | to provide an additional time selection |
| showTime.defaultValue | [moment, moment] | | to set default time of selected date |
| use12Hours | Boolean | false | 12 hours display mode |
| use12Hours | boolean | false | 12 hours display mode |
| disabledTime | Function(date: moment, type:'start'\|'end'):Object | | | to specify the time that cannot be selected |
| ranges | { String \| [range: string]: moment[] } \| { [range: string]: () => moment[] } | | preseted ranges for quick selection |
| 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 |
| allowEmpty | [Boolean, Boolean] | | allow range picker clearing text |
| selectable | [Boolean, Boolean] | | whether to selected picker |
| disabled | Boolean | false | whether the range picker is disabled |
| allowEmpty | [boolean, boolean] | | allow range picker clearing text |
| selectable | [boolean, boolean] | | whether to selected picker |
| disabled | boolean | false | whether the range picker is disabled |
| onChange | Function(value:[moment], formatString: [string, string]) | | a callback function, can be executed when the selected time is changing |
| 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 |
| direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. |
| order | Boolean | true | (TimeRangePicker only) `false` to disable auto order |
| order | boolean | true | (TimeRangePicker only) `false` to disable auto order |

### showTime-options

| Property | Type | Default | Description |
| ------------------- | ------- | ------- | ---------------------------------- |
| format | String | | moment format |
| showHour | Boolean | true | whether show hour |
| showMinute | Boolean | true | whether show minute |
| showSecond | Boolean | true | whether show second |
| use12Hours | Boolean | false | 12 hours display mode |
| showHour | boolean | true | whether show hour |
| showMinute | boolean | true | whether show minute |
| showSecond | boolean | true | whether show second |
| use12Hours | boolean | false | 12 hours display mode |
| hourStep | Number | 1 | interval between hours in picker |
| minuteStep | Number | 1 | interval between minutes in picker |
| secondStep | Number | 1 | interval between seconds in picker |
| hideDisabledOptions | Boolean | false | whether hide disabled options |
| hideDisabledOptions | boolean | false | whether hide disabled options |
| defaultValue | moment | null | default initial value |

## Development
Expand Down
43 changes: 22 additions & 21 deletions src/Picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { formatValue, isEqual, parseValue } from './utils/dateUtil';
import { toArray } from './utils/miscUtil';
import { elementsContains, getDefaultFormat, getInputSize } from './utils/uiUtil';
import { legacyPropsWarning } from './utils/warnUtil';
import { useAllowClear } from './hooks/useAllowClear';

export type PickerRefConfig = {
focus: () => void;
Expand All @@ -49,7 +50,7 @@ export type PickerSharedProps<DateType> = {
popupStyle?: React.CSSProperties;
transitionName?: string;
placeholder?: string;
allowClear?: boolean;
allowClear?: boolean | { clearIcon?: React.ReactNode };
autoFocus?: boolean;
disabled?: boolean;
tabIndex?: number;
Expand All @@ -66,6 +67,10 @@ export type PickerSharedProps<DateType> = {

// Render
suffixIcon?: React.ReactNode;
/**
* Clear all icon
* @deprecated Please use `allowClear` instead
**/
clearIcon?: React.ReactNode;
prevIcon?: React.ReactNode;
nextIcon?: React.ReactNode;
Expand Down Expand Up @@ -477,27 +482,23 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
);
}

let clearNode: React.ReactNode;
if (allowClear && mergedValue && !disabled) {
clearNode = (
<span
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseUp={(e) => {
e.preventDefault();
e.stopPropagation();
triggerChange(null);
triggerOpen(false);
}}
className={`${prefixCls}-clear`}
role="button"
>
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
</span>
// ============================ Clear ============================
if (process.env.NODE_ENV !== 'production') {
warning(
!props.clearIcon,
'`clearIcon` will be removed in future. Please use `allowClear` instead.',
);
}
const { allowClear: mergedAllowClear, clearIcon: clearNode } = useAllowClear<DateType>(
"single",
prefixCls,
triggerChange,
triggerOpen,
allowClear,
clearIcon,
mergedValue,
disabled
);

const mergedInputProps: React.InputHTMLAttributes<HTMLInputElement> & { ref: React.MutableRefObject<HTMLInputElement> } = {
id,
Expand Down Expand Up @@ -590,7 +591,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
>
{inputNode}
{suffixNode}
{clearNode}
{mergedAllowClear && clearNode}
</div>
</div>
</PickerTrigger>
Expand Down
68 changes: 26 additions & 42 deletions src/RangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import getRanges from './utils/getRanges';
import { getValue, toArray, updateValues } from './utils/miscUtil';
import { elementsContains, getDefaultFormat, getInputSize } from './utils/uiUtil';
import { legacyPropsWarning } from './utils/warnUtil';
import { useAllowClear } from './hooks/useAllowClear';

function reorderValues<DateType>(
values: RangeValue<DateType>,
Expand Down Expand Up @@ -689,18 +690,18 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
const startStr =
mergedValue && mergedValue[0]
? formatValue(mergedValue[0], {
locale,
format: 'YYYYMMDDHHmmss',
generateConfig,
})
locale,
format: 'YYYYMMDDHHmmss',
generateConfig,
})
: '';
const endStr =
mergedValue && mergedValue[1]
? formatValue(mergedValue[1], {
locale,
format: 'YYYYMMDDHHmmss',
generateConfig,
})
locale,
format: 'YYYYMMDDHHmmss',
generateConfig,
})
: '';

useEffect(() => {
Expand Down Expand Up @@ -752,6 +753,10 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
}
warning(!dateRender, `'dateRender' is deprecated. Please use 'cellRender' instead.`);
warning(!monthCellRender, `'monthCellRender' is deprecated. Please use 'cellRender' instead.`);
warning(
!clearIcon,
'`clearIcon` will be removed in future. Please use `allowClear` instead.',
);
}

// ============================ Private ============================
Expand Down Expand Up @@ -859,7 +864,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
defaultValue={
mergedActivePickerIndex === 0 ? getValue(selectedValue, 1) : getValue(selectedValue, 0)
}
// defaultPickerValue={undefined}
// defaultPickerValue={undefined}
/>
</RangeContext.Provider>
);
Expand Down Expand Up @@ -1038,39 +1043,18 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
);
}

let clearNode: React.ReactNode;
if (
allowClear &&
((getValue(mergedValue, 0) && !mergedDisabled[0]) ||
(getValue(mergedValue, 1) && !mergedDisabled[1]))
) {
clearNode = (
<span
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseUp={(e) => {
e.preventDefault();
e.stopPropagation();
let values = mergedValue;

if (!mergedDisabled[0]) {
values = updateValues(values, null, 0);
}
if (!mergedDisabled[1]) {
values = updateValues(values, null, 1);
}

triggerChange(values, null);
triggerOpen(false, mergedActivePickerIndex, 'clear');
}}
className={`${prefixCls}-clear`}
>
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
</span>
);
}
const { allowClear: mergedAllowClear, clearIcon: clearNode } = useAllowClear<DateType>(
"range",
prefixCls,
triggerChange,
triggerOpen,
allowClear,
clearIcon,
mergedValue,
mergedDisabled,
mergedActivePickerIndex,
);

const inputSharedProps = {
size: getInputSize(picker, formatList[0], generateConfig),
Expand Down Expand Up @@ -1209,7 +1193,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
}}
/>
{suffixNode}
{clearNode}
{mergedAllowClear && clearNode}
</div>
</PickerTrigger>
</PanelContext.Provider>
Expand Down
79 changes: 79 additions & 0 deletions src/hooks/useAllowClear.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ReactNode } from 'react';
import React from 'react';
import type { SourceType } from './useRangeOpen';
import type { RangeValue } from '../interface';
import { getValue, updateValues } from '../utils/miscUtil';

export function useAllowClear<DateType>(
type: "single" | "range",
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
prefixCls,
triggerChange: (date: DateType | RangeValue<DateType> | null, sourceIndex?: 0 | 1) => void,
triggerOpen: (open: boolean, activeIndex?: false | 0 | 1, source?: SourceType) => void,
allowClear?: boolean | { clearIcon?: ReactNode },
clearIcon?: ReactNode,
mergedValue?: DateType | RangeValue<DateType>,
mergedDisabled?: boolean | [boolean, boolean],
mergedActivePickerIndex?: 0 | 1
) {
const mergedClearIcon = React.useMemo(() => {
if (typeof allowClear === "object") {
return allowClear.clearIcon;
}
if (!!clearIcon) return clearIcon;
}, [allowClear, clearIcon]);


const mergedAllowClear = React.useMemo(() => {
if (
!!allowClear
) {
if (type === "single") {
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
return mergedValue && !mergedDisabled
} else {
return (
(getValue(mergedValue as RangeValue<DateType>, 0) && !mergedDisabled[0]) ||
(getValue(mergedValue as RangeValue<DateType>, 1) && !mergedDisabled[1])
)
}
}
return false;
}, [allowClear, mergedDisabled, mergedValue, type]);

return {
allowClear: mergedAllowClear,
clearIcon: (
<span
onMouseDown={(e) => {
e.preventDefault();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preventDefault 有用么?有可能导致用户自定义的 icon 事件失效

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这是现有逻辑,去掉后不知道会不会导致一些不可意料的问题

e.stopPropagation();
}}
onMouseUp={(e) => {
e.preventDefault();
e.stopPropagation();
if (type === "range") {
e.preventDefault();
e.stopPropagation();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里可以去掉吧

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这是现有逻辑,去掉后不知道会不会导致一些不可意料的问题

let values = mergedValue as RangeValue<DateType>;

if (!mergedDisabled[0]) {
values = updateValues(values, null, 0);
}
if (!mergedDisabled[1]) {
values = updateValues(values, null, 1);
}

triggerChange(values, null);
triggerOpen(false, mergedActivePickerIndex, 'clear');
} else {
triggerChange(null);
triggerOpen(false);
}
}}
className={`${prefixCls}-clear`}
role="button"
>
{mergedClearIcon || <span className={`${prefixCls}-clear-btn`} />}
</span>
)
};
}
1 change: 1 addition & 0 deletions tests/__snapshots__/range.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ exports[`Picker.Range icon 1`] = `
</span>
<span
class="rc-picker-clear"
role="button"
>
<span
class="suffix-icon"
Expand Down
Loading
Loading