Skip to content

Commit 9adb1a3

Browse files
committed
feat: datetime range picker display ok button
1 parent 47b6aba commit 9adb1a3

File tree

7 files changed

+283
-20
lines changed

7 files changed

+283
-20
lines changed

src/RangePicker.tsx

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ function reorderValues<DateType>(
5959
function canValueTrigger<DateType>(
6060
value: EventValue<DateType>,
6161
index: number,
62-
disabledList: [boolean, boolean],
6362
allowEmpty?: [boolean, boolean] | null,
6463
): boolean {
6564
if (value) {
@@ -105,6 +104,11 @@ export interface RangePickerSharedProps<DateType> {
105104
) => void;
106105
onFocus?: React.FocusEventHandler<HTMLInputElement>;
107106
onBlur?: React.FocusEventHandler<HTMLInputElement>;
107+
108+
/** @private Internal usage. Do not use in your production env */
109+
components?: {
110+
button: React.ComponentType;
111+
};
108112
}
109113

110114
type OmitPickerProps<Props> = Omit<
@@ -202,6 +206,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
202206
onCalendarChange,
203207
onFocus,
204208
onBlur,
209+
components,
205210
} = props as MergedRangePickerProps<DateType>;
206211

207212
const containerRef = React.useRef<HTMLDivElement>(null);
@@ -415,18 +420,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
415420
onCalendarChange(values, [startStr, endStr]);
416421
}
417422

418-
const canStartValueTrigger = canValueTrigger(
419-
startValue,
420-
0,
421-
mergedDisabled,
422-
allowEmpty,
423-
);
424-
const canEndValueTrigger = canValueTrigger(
425-
endValue,
426-
1,
427-
mergedDisabled,
428-
allowEmpty,
429-
);
423+
const canStartValueTrigger = canValueTrigger(startValue, 0, allowEmpty);
424+
const canEndValueTrigger = canValueTrigger(endValue, 1, allowEmpty);
430425

431426
const canTrigger =
432427
values === null || (canStartValueTrigger && canEndValueTrigger);
@@ -485,7 +480,16 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
485480
// Let popup panel handle keyboard
486481
return operationRef.current.onKeyDown(e);
487482
}
488-
return false;
483+
484+
/* istanbul ignore next */
485+
/* eslint-disable no-lone-blocks */
486+
{
487+
warning(
488+
false,
489+
'Picker not correct forward KeyDown operation. Please help to fire issue about this.',
490+
);
491+
return false;
492+
}
489493
};
490494

491495
// ============================= Text ==============================
@@ -803,13 +807,15 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
803807
}
804808

805809
let rangesNode: React.ReactNode;
806-
if (ranges) {
807-
const rangeList = Object.keys(ranges);
810+
if (ranges || showTime) {
811+
const mergedRanges = ranges || {};
812+
const rangeList = Object.keys(mergedRanges);
813+
const Button = (components && components.button) || 'button';
808814

809815
rangesNode = (
810816
<ul className={`${prefixCls}-ranges`}>
811817
{rangeList.map(label => {
812-
const range = ranges[label];
818+
const range = mergedRanges[label];
813819

814820
return (
815821
<li
@@ -823,6 +829,19 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
823829
</li>
824830
);
825831
})}
832+
833+
{showTime && (
834+
<li
835+
className={`${prefixCls}-ok`}
836+
onClick={() => {
837+
triggerChange(selectedValue);
838+
}}
839+
>
840+
<Button disabled={!getValue(selectedValue, activePickerIndex)}>
841+
{locale.ok}
842+
</Button>
843+
</li>
844+
)}
826845
</ul>
827846
);
828847
}
@@ -854,6 +873,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
854873
</div>
855874
);
856875

876+
// ============================= Icons =============================
857877
let suffixNode: React.ReactNode;
858878
if (suffixIcon) {
859879
suffixNode = <span className={`${prefixCls}-suffix`}>{suffixIcon}</span>;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Picker.Range icon 1`] = `
4+
<div
5+
class="rc-picker rc-picker-range"
6+
>
7+
<div
8+
class="rc-picker-input rc-picker-input-active"
9+
>
10+
<input
11+
placeholder=""
12+
readonly=""
13+
size="12"
14+
value="1990-09-03"
15+
/>
16+
</div>
17+
<div
18+
class="rc-picker-range-separator"
19+
>
20+
~
21+
</div>
22+
<div
23+
class="rc-picker-input"
24+
>
25+
<input
26+
placeholder=""
27+
readonly=""
28+
size="12"
29+
value="1990-09-03"
30+
/>
31+
</div>
32+
<div
33+
class="rc-picker-active-bar"
34+
style="left: 0px; width: 0px; position: absolute;"
35+
/>
36+
<span
37+
class="rc-picker-suffix"
38+
>
39+
<span
40+
class="suffix-icon"
41+
/>
42+
</span>
43+
<span
44+
class="rc-picker-clear"
45+
>
46+
<span
47+
class="suffix-icon"
48+
/>
49+
</span>
50+
</div>
51+
`;

tests/keyboard.spec.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import MockDate from 'mockdate';
3+
import { act } from 'react-dom/test-utils';
34
import KeyCode from 'rc-util/lib/KeyCode';
45
import {
56
mount,
@@ -8,6 +9,7 @@ import {
89
MomentPicker,
910
MomentPickerPanel,
1011
Wrapper,
12+
MomentRangePicker,
1113
} from './util/commonUtil';
1214

1315
describe('Picker.Keyboard', () => {
@@ -380,4 +382,85 @@ describe('Picker.Keyboard', () => {
380382
).toBeTruthy();
381383
});
382384
});
385+
386+
describe('range picker', () => {
387+
it('full step', () => {
388+
jest.useFakeTimers();
389+
const onCalendarChange = jest.fn();
390+
const onChange = jest.fn();
391+
const wrapper = mount(
392+
<MomentRangePicker
393+
onCalendarChange={onCalendarChange}
394+
onChange={onChange}
395+
/>,
396+
);
397+
398+
// Start Date
399+
wrapper.openPicker();
400+
wrapper
401+
.find('input')
402+
.first()
403+
.simulate('change', { target: { value: '1990-01-01' } });
404+
wrapper.keyDown(KeyCode.TAB);
405+
wrapper.keyDown(KeyCode.DOWN);
406+
wrapper.keyDown(KeyCode.ENTER);
407+
expect(onCalendarChange.mock.calls[0][1]).toEqual(['1990-01-08', '']);
408+
expect(onChange).not.toHaveBeenCalled();
409+
410+
// End Date
411+
act(() => {
412+
jest.runAllTimers();
413+
});
414+
expect(
415+
wrapper
416+
.find('.rc-picker-input')
417+
.last()
418+
.hasClass('rc-picker-input-active'),
419+
).toBeTruthy();
420+
onCalendarChange.mockReset();
421+
422+
wrapper
423+
.find('input')
424+
.last()
425+
.simulate('change', { target: { value: '2000-01-01' } });
426+
wrapper.keyDown(KeyCode.TAB, {}, 1);
427+
wrapper.keyDown(KeyCode.DOWN, {}, 1);
428+
wrapper.keyDown(KeyCode.ENTER, {}, 1);
429+
expect(onCalendarChange.mock.calls[0][1]).toEqual([
430+
'1990-01-08',
431+
'2000-01-08',
432+
]);
433+
expect(onChange.mock.calls[0][1]).toEqual(['1990-01-08', '2000-01-08']);
434+
435+
jest.useRealTimers();
436+
});
437+
438+
it('full step', () => {
439+
const onCalendarChange = jest.fn();
440+
const onChange = jest.fn();
441+
const onFocus = jest.fn();
442+
const wrapper = mount(
443+
<MomentRangePicker
444+
onFocus={onFocus}
445+
onCalendarChange={onCalendarChange}
446+
onChange={onChange}
447+
/>,
448+
);
449+
450+
wrapper.openPicker();
451+
expect(onFocus).toHaveBeenCalled();
452+
453+
wrapper
454+
.find('input')
455+
.first()
456+
.simulate('change', { target: { value: '2000-01-01' } });
457+
wrapper.keyDown(KeyCode.ESC);
458+
expect(
459+
wrapper
460+
.find('input')
461+
.first()
462+
.props().value,
463+
).toEqual('');
464+
});
465+
});
383466
});

tests/range.spec.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import MockDate from 'mockdate';
3+
import { act } from 'react-dom/test-utils';
34
import KeyCode from 'rc-util/lib/KeyCode';
45
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
56
import { Moment } from 'moment';
@@ -630,4 +631,104 @@ describe('Picker.Range', () => {
630631
.simulate('blur');
631632
expect(wrapper.isOpen()).toBeFalsy();
632633
});
634+
635+
it('icon', () => {
636+
const wrapper = mount(
637+
<MomentRangePicker
638+
defaultValue={[getMoment('1990-09-03'), getMoment('1990-09-03')]}
639+
suffixIcon={<span className="suffix-icon" />}
640+
clearIcon={<span className="suffix-icon" />}
641+
allowClear
642+
/>,
643+
);
644+
645+
expect(wrapper.render()).toMatchSnapshot();
646+
});
647+
648+
it('block native mouseDown in panel to prevent focus changed', () => {
649+
const wrapper = mount(<MomentRangePicker />);
650+
wrapper.openPicker();
651+
652+
const preventDefault = jest.fn();
653+
wrapper
654+
.find('td')
655+
.first()
656+
.simulate('mouseDown', { preventDefault });
657+
658+
expect(preventDefault).toHaveBeenCalled();
659+
});
660+
661+
describe('arrow position', () => {
662+
let domMock: ReturnType<typeof spyElementPrototypes>;
663+
664+
beforeAll(() => {
665+
domMock = spyElementPrototypes(HTMLElement, {
666+
offsetWidth: {
667+
get: () => 100,
668+
},
669+
});
670+
});
671+
672+
afterAll(() => {
673+
domMock.mockRestore();
674+
});
675+
676+
it('end date arrow should move panel left', () => {
677+
const wrapper = mount(<MomentRangePicker />);
678+
wrapper.openPicker(1);
679+
wrapper.update();
680+
expect(
681+
wrapper.find('.rc-picker-panel-container').props().style?.marginLeft,
682+
).toEqual(200);
683+
});
684+
});
685+
686+
it('fixed open need repeat trigger onOpenChange', () => {
687+
jest.useFakeTimers();
688+
const onOpenChange = jest.fn();
689+
const wrapper = mount(
690+
<MomentRangePicker onOpenChange={onOpenChange} open />,
691+
);
692+
693+
for (let i = 0; i < 10; i += 1) {
694+
const clickEvent = new Event('mousedown');
695+
Object.defineProperty(clickEvent, 'target', {
696+
get: () => document.body,
697+
});
698+
act(() => {
699+
window.dispatchEvent(clickEvent);
700+
wrapper
701+
.find('input')
702+
.first()
703+
.simulate('blur');
704+
});
705+
706+
// Maybe not good since onOpenChange trigger twice
707+
expect(onOpenChange).toHaveBeenCalledTimes((i + 1) * 2);
708+
}
709+
act(() => {
710+
jest.runAllTimers();
711+
});
712+
jest.useRealTimers();
713+
});
714+
715+
it('datetime display ok button', () => {
716+
const onCalendarChange = jest.fn();
717+
const wrapper = mount(
718+
<MomentRangePicker showTime onCalendarChange={onCalendarChange} />,
719+
);
720+
wrapper.openPicker();
721+
722+
// Not trigger when not value
723+
expect(wrapper.find('.rc-picker-ok button').props().disabled).toBeTruthy();
724+
725+
// Trigger when valued
726+
onCalendarChange.mockReset();
727+
wrapper.selectCell(11);
728+
wrapper.find('.rc-picker-ok').simulate('click');
729+
expect(onCalendarChange).toHaveBeenCalledWith(
730+
[expect.anything(), null],
731+
['1990-09-11 00:00:00', ''],
732+
);
733+
});
633734
});

tests/setup.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ Object.assign(Enzyme.ReactWrapper.prototype, {
7272
clearValue() {
7373
this.find('.rc-picker-clear-btn').simulate('click');
7474
},
75-
keyDown(which, info = {}) {
76-
this.find('input').simulate('keydown', { ...info, which });
75+
keyDown(which, info = {}, index = 0) {
76+
this.find('input')
77+
.at(index)
78+
.simulate('keydown', { ...info, which });
7779
},
7880
});

0 commit comments

Comments
 (0)