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(date-picker): RangePicker allowEmpty #2146

Open
wants to merge 4 commits into
base: feature
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 95 additions & 0 deletions components/DatePicker/__demo__/allowEmpty.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
order: 24
title:
zh-CN: 允许空值的范围选择器
en-US: RangePicker allowEmpty
---

## zh-CN

使用 `allowEmpty`和`referenceInterval` 可以使用允许空值的范围选择器。

## en-US

Use `showTime`and`referenceInterval` to allow range selectors that allow null values.

```tsx
import { useState } from 'react';
import { DatePicker, Radio, Space, type RangePickerProps } from 'arco-design';
import dayjs, { Dayjs } from 'dayjs';
const { RangePicker } = DatePicker;

function onSelect(dateString, date) {
console.log('onSelect', dateString, date);
}

const onChange: RangePickerProps['onChange'] = (dateString, date) => {
console.log('onChange: ', dateString, date);
};

function App() {
const [value, setValue] = useState('date');
const mode = value === 'date time' ? 'date' : value;
const ranges: Array<{
radio: string;
format?: string;
referenceInterval: [string | Dayjs, string | Dayjs];
}> = [
{
radio: 'date',
format: 'YYYY-MM-DD',
referenceInterval: ['2019-08-01', '2023-09-01'],
},
{
radio: 'week',
referenceInterval: [dayjs('2023-08-01'), dayjs('2023-09-01')],
},
{
radio: 'month',
referenceInterval: ['2019-08-01', '2023-09-01'],
},
{
radio: 'year',
referenceInterval: ['2019', '2023'],
},
{
radio: 'quarter',
referenceInterval: [dayjs('2019-08-01'), dayjs('2023-09-01')],
},
{
radio: 'date time',
format: 'YYYY-MM-DD HH:mm:ss',
referenceInterval: ['2019-08-01 00:00:00', '2023-09-01 09:09:06'],
},
];
const options = ranges.map((item) => item.radio);
const getReferenceInterval = (radio: string): [string | Dayjs, string | Dayjs] =>
ranges.find((item) => item.radio === radio)!['referenceInterval'];
const style =
value === 'date time'
? {
width: 380,
}
: {
width: 254,
marginBottom: 20,
};
return (
<Space direction="vertical">
<Radio.Group options={options} value={value} onChange={(v) => setValue(v)} type="button" />
<RangePicker
allowEmpty
referenceInterval={getReferenceInterval(value)}
mode={mode}
onChange={onChange}
onSelect={onSelect}
style={style}
showTime={value === 'date time'}
/>
</Space>
);
}

export default App;

```
10 changes: 10 additions & 0 deletions components/DatePicker/interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,16 @@ export type TimePickerRangeProps = Omit<TimePickerProps, 'defaultValue'> & {
* @title RangePicker
*/
export interface BaseRangePickerProps {
/**
* @zh 是否允许留空
* @en Whether to allow input empty
*/
allowEmpty?: boolean;
/**
* @zh 允许留空时的参考区间
* @en Reference interval when blank is allowed
*/
referenceInterval?: [string | Dayjs, string | Dayjs];
/**
* @zh 是否禁用
* @en Whether to disable input box
Expand Down
19 changes: 18 additions & 1 deletion components/DatePicker/panels/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ export default function Footer(props) {
extra,
mode,
shortcutsPlacementLeft,
showSingleConfirmBtn = false,
} = props;

const hasShortcuts = isArray(shortcuts) && shortcuts.length > 0;
const shouldShowNowBtn = showNowBtn && showTime && !hasShortcuts;
const shouldShouldShortcuts = shouldShowNowBtn || (hasShortcuts && !shortcutsPlacementLeft);

return (
<div className={`${prefixCls}-footer`}>
<div
className={`${prefixCls}-footer`}
style={showSingleConfirmBtn ? { display: 'flex', justifyContent: 'end' } : {}}
>
{extra && <div className={`${prefixCls}-footer-extra-wrapper`}>{extra}</div>}
{!showTime && showNowBtn && mode === 'date' && (
<div className={`${prefixCls}-footer-now-wrapper`}>
Expand Down Expand Up @@ -66,6 +70,7 @@ export default function Footer(props) {
{isTimePanel ? DATEPICKER_LOCALE.selectDate : DATEPICKER_LOCALE.selectTime}
</Button>
<Button
style={{ marginRight: '8px' }}
className={`${prefixCls}-btn-confirm`}
type="primary"
size="mini"
Expand All @@ -78,6 +83,18 @@ export default function Footer(props) {
)}
</div>
) : null}
{showSingleConfirmBtn && (
<Button
style={{ marginRight: '8px' }}
className={`${prefixCls}-btn-confirm`}
type="primary"
size="mini"
disabled={disabled}
onClick={onClickConfirmBtn}
>
{DATEPICKER_LOCALE.ok}
</Button>
)}
</div>
);
}
37 changes: 27 additions & 10 deletions components/DatePicker/picker-range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ const Picker = (baseProps: RangePickerProps) => {
componentConfig?.DatePicker
);
const {
allowEmpty = false,
referenceInterval = [],
allowClear,
className,
style,
Expand Down Expand Up @@ -113,9 +115,7 @@ const Picker = (baseProps: RangePickerProps) => {
utcOffset,
timezone,
} = props;

const prefixCls = getPrefixCls('picker-range');

const weekStart = isUndefined(props.dayStartOfWeek)
? getDefaultWeekStart(locale.dayjsLocale)
: props.dayStartOfWeek;
Expand Down Expand Up @@ -178,6 +178,10 @@ const Picker = (baseProps: RangePickerProps) => {
const mergedValue = 'value' in props ? propsValueDayjs : value;

const panelValue = shortcutsValue || valueShow || mergedValue || [];
const disabledConfirm = (() => {
if (allowEmpty) return false;
return !(isArray(panelValue) && panelValue[0] && panelValue[1]);
})();

const selectedLength = getAvailableDayjsLength(valueShow || mergedValue);

Expand Down Expand Up @@ -492,6 +496,17 @@ const Picker = (baseProps: RangePickerProps) => {

// Callback when click the confirm button
function onClickConfirmBtn() {
if (allowEmpty && valueShow) {
const focusedIndex = customTriggerElement
? selectedLength === 0 || selectedLength === 2
? 0
: 1
: focusedInputIndex;
onSelectPanel(
'',
getDayjsValue(referenceInterval, format, utcOffset, timezone)[focusedIndex]
);
}
onConfirmValue();
const localePanelValue = panelValue.map((v) => getLocaleDayjsValue(v, locale.dayjsLocale));
onOk &&
Expand Down Expand Up @@ -551,13 +566,11 @@ const Picker = (baseProps: RangePickerProps) => {
}

const sortedValueShow = getSortedDayjsArray(newValueShow);

onSelectValueShow(sortedValueShow);
setInputValue(undefined);
setHoverPlaceholderValue(undefined);

const newSelectedLength = getAvailableDayjsLength(newValueShow);

if (resetRange) {
if (selectedLength === 0 || (selectedLength === 2 && !isHalfAvailable)) {
customTriggerElement ? setFocusedInputIndex(1) : switchFocusedInput(true);
Expand Down Expand Up @@ -745,11 +758,14 @@ const Picker = (baseProps: RangePickerProps) => {
onSelectShortcut: onHandleSelectShortcut,
};

const shouldShowFooter =
(showTime && panelModes[0] === 'date' && panelModes[1] === 'date') ||
extra ||
(isArray(shortcuts) && shortcuts.length && !shortcutsPlacementLeft);

const shouldShowFooter = (() => {
if (allowEmpty) return true;
return (
(showTime && panelModes[0] === 'date' && panelModes[1] === 'date') ||
extra ||
(isArray(shortcuts) && shortcuts.length && !shortcutsPlacementLeft)
);
})();
const content = (
<>
<RangePickerPanel
Expand Down Expand Up @@ -781,12 +797,13 @@ const Picker = (baseProps: RangePickerProps) => {
<Footer
{...shortcutsProps}
DATEPICKER_LOCALE={locale.DatePicker}
disabled={!(isArray(panelValue) && panelValue[0] && panelValue[1])}
disabled={disabledConfirm}
onClickConfirmBtn={onClickConfirmBtn}
extra={extra}
shortcutsPlacementLeft={shortcutsPlacementLeft}
onClickSelectTimeBtn={onClickSelectTimeBtn}
isTimePanel={isTimePanel}
showSingleConfirmBtn={!showTime && allowEmpty}
/>
)}
</>
Expand Down