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 7 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
48 changes: 26 additions & 22 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 { getClearIcon } from './utils/getClearIcon';

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,28 +482,27 @@ 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 clearNode: React.ReactNode = getClearIcon<DateType>(
false,
prefixCls,
triggerChange,
triggerOpen,
allowClear,
clearIcon,
mergedValue,
disabled,
);

const mergedAllowClear = !!allowClear && mergedValue && !disabled;

const mergedInputProps: React.InputHTMLAttributes<HTMLInputElement> & { ref: React.MutableRefObject<HTMLInputElement> } = {
id,
tabIndex,
Expand All @@ -515,7 +519,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
...inputProps,
size: getInputSize(picker, formatList[0], generateConfig),
name,
...pickAttrs(props, { aria: true, data: true}),
...pickAttrs(props, { aria: true, data: true }),
autoComplete,
};

Expand Down Expand Up @@ -590,7 +594,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
>
{inputNode}
{suffixNode}
{clearNode}
{mergedAllowClear && clearNode}
</div>
</div>
</PickerTrigger>
Expand Down
72 changes: 30 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 { getClearIcon } from './utils/getClearIcon';

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,22 @@ 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);
}
const clearNode: React.ReactNode = getClearIcon<DateType>(
true,
prefixCls,
triggerChange,
triggerOpen,
allowClear,
clearIcon,
mergedValue,
mergedDisabled,
mergedActivePickerIndex,
);

triggerChange(values, null);
triggerOpen(false, mergedActivePickerIndex, 'clear');
}}
className={`${prefixCls}-clear`}
>
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
</span>
);
}
const mergedAllowClear = allowClear && (
(getValue(mergedValue as RangeValue<DateType>, 0) && !mergedDisabled[0]) ||
(getValue(mergedValue as RangeValue<DateType>, 1) && !mergedDisabled[1])
);

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

export function getClearIcon<DateType>(
isRange: boolean,
prefixCls,
triggerChange: (date: DateType | RangeValue<DateType> | null, sourceIndex?: 0 | 1) => void,
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
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 = typeof allowClear === "object" ? allowClear.clearIcon : clearIcon;

return (
<span
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseUp={(e) => {
e.preventDefault();
e.stopPropagation();
if (isRange) {
e.preventDefault();
e.stopPropagation();
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