Skip to content

Commit 5b14831

Browse files
authored
feat: Quarter Picker (#39)
* add quarter * picker support quarter * test case * coverag * full coverage
1 parent 0789f46 commit 5b14831

File tree

12 files changed

+337
-183
lines changed

12 files changed

+337
-183
lines changed

examples/basic.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,24 +102,19 @@ export default () => {
102102
</div>
103103
<div style={{ margin: '0 8px' }}>
104104
<h3>Week</h3>
105-
<Picker<Moment>
106-
generateConfig={momentGenerateConfig}
107-
locale={enUS}
108-
picker="week"
109-
/>
105+
<Picker<Moment> generateConfig={momentGenerateConfig} locale={enUS} picker="week" />
106+
</div>
107+
<div style={{ margin: '0 8px' }}>
108+
<h3>Quarter</h3>
109+
<Picker<Moment> generateConfig={momentGenerateConfig} locale={enUS} picker="quarter" />
110110
</div>
111111
<div style={{ margin: '0 8px' }}>
112112
<h3>Time</h3>
113113
<Picker<Moment> {...sharedProps} locale={zhCN} picker="time" />
114114
</div>
115115
<div style={{ margin: '0 8px' }}>
116116
<h3>Time 12</h3>
117-
<Picker<Moment>
118-
{...sharedProps}
119-
locale={zhCN}
120-
picker="time"
121-
use12Hours
122-
/>
117+
<Picker<Moment> {...sharedProps} locale={zhCN} picker="time" use12Hours />
123118
</div>
124119
<div style={{ margin: '0 8px' }}>
125120
<h3>Year</h3>

examples/panel.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ export default () => {
6868
<PickerPanel<Moment> {...sharedProps} locale={zhCN} picker="month" />
6969
</div>
7070

71+
<div style={{ margin: '0 8px' }}>
72+
<h3>Quarter Picker</h3>
73+
<PickerPanel<Moment> {...sharedProps} locale={zhCN} picker="quarter" />
74+
</div>
75+
7176
<div style={{ margin: '0 8px' }}>
7277
<h3>Week Picker US</h3>
7378
<PickerPanel<Moment> {...sharedProps} locale={enUS} picker="week" />
@@ -79,12 +84,7 @@ export default () => {
7984
</div>
8085
<div style={{ margin: '0 8px' }}>
8186
<h3>Uncontrolled</h3>
82-
<PickerPanel<Moment>
83-
{...sharedProps}
84-
locale={jaJP}
85-
value={undefined}
86-
picker="time"
87-
/>
87+
<PickerPanel<Moment> {...sharedProps} locale={jaJP} value={undefined} picker="time" />
8888
</div>
8989
<div style={{ margin: '0 8px' }}>
9090
<h3>Time AM/PM</h3>

src/PickerPanel.tsx

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import DatetimePanel from './panels/DatetimePanel';
1717
import DatePanel from './panels/DatePanel';
1818
import WeekPanel from './panels/WeekPanel';
1919
import MonthPanel from './panels/MonthPanel';
20+
import QuarterPanel from './panels/QuarterPanel';
2021
import YearPanel from './panels/YearPanel';
2122
import DecadePanel from './panels/DecadePanel';
2223
import { GenerateConfig } from './generate';
@@ -84,13 +85,11 @@ export interface PickerPanelSharedProps<DateType> {
8485
components?: Components;
8586
}
8687

87-
export interface PickerPanelBaseProps<DateType>
88-
extends PickerPanelSharedProps<DateType> {
88+
export interface PickerPanelBaseProps<DateType> extends PickerPanelSharedProps<DateType> {
8989
picker: Exclude<PickerMode, 'date' | 'time'>;
9090
}
9191

92-
export interface PickerPanelDateProps<DateType>
93-
extends PickerPanelSharedProps<DateType> {
92+
export interface PickerPanelDateProps<DateType> extends PickerPanelSharedProps<DateType> {
9493
picker?: 'date';
9594
showToday?: boolean;
9695

@@ -149,18 +148,11 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
149148
direction,
150149
} = props as MergedPickerPanelProps<DateType>;
151150

152-
const needConfirmButton: boolean =
153-
(picker === 'date' && !!showTime) || picker === 'time';
151+
const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time';
154152

155153
if (process.env.NODE_ENV !== 'production') {
156-
warning(
157-
!value || generateConfig.isValidate(value),
158-
'Invalidate date pass to `value`.',
159-
);
160-
warning(
161-
!value || generateConfig.isValidate(value),
162-
'Invalidate date pass to `defaultValue`.',
163-
);
154+
warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `value`.');
155+
warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `defaultValue`.');
164156
}
165157

166158
// ============================ State =============================
@@ -174,12 +166,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
174166
defaultOpenValue,
175167
} = panelContext;
176168

177-
const {
178-
inRange,
179-
panelPosition,
180-
rangedValue,
181-
hoverRangedValue,
182-
} = React.useContext(RangeContext);
169+
const { inRange, panelPosition, rangedValue, hoverRangedValue } = React.useContext(RangeContext);
183170
const panelRef = React.useRef<PanelRefProps>({});
184171

185172
// Handle init logic
@@ -198,10 +185,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
198185
});
199186

200187
// View date control
201-
const [viewDate, setInnerViewDate] = useMergedState<
202-
DateType | null,
203-
DateType
204-
>(null, {
188+
const [viewDate, setInnerViewDate] = useMergedState<DateType | null, DateType>(null, {
205189
value: pickerValue,
206190
defaultValue: defaultPickerValue || mergedValue,
207191
postState: date => date || generateConfig.getNow(),
@@ -237,22 +221,14 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
237221
},
238222
);
239223

240-
const [sourceMode, setSourceMode] = React.useState<PanelMode>(
241-
() => mergedMode,
242-
);
224+
const [sourceMode, setSourceMode] = React.useState<PanelMode>(() => mergedMode);
243225

244-
const onInternalPanelChange = (
245-
newMode: PanelMode | null,
246-
viewValue: DateType,
247-
) => {
226+
const onInternalPanelChange = (newMode: PanelMode | null, viewValue: DateType) => {
248227
const nextMode = getInternalNextMode(newMode || mergedMode);
249228
setSourceMode(mergedMode);
250229
setInnerMode(nextMode);
251230

252-
if (
253-
onPanelChange &&
254-
(mergedMode !== nextMode || isEqual(generateConfig, viewDate, viewDate))
255-
) {
231+
if (onPanelChange && (mergedMode !== nextMode || isEqual(generateConfig, viewDate, viewDate))) {
256232
onPanelChange(viewValue, nextMode);
257233
}
258234
};
@@ -392,6 +368,18 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
392368
);
393369
break;
394370

371+
case 'quarter':
372+
panelNode = (
373+
<QuarterPanel<DateType>
374+
{...pickerProps}
375+
onSelect={(date, type) => {
376+
setViewDate(date);
377+
triggerSelect(date, type);
378+
}}
379+
/>
380+
);
381+
break;
382+
395383
case 'week':
396384
panelNode = (
397385
<WeekPanel
@@ -489,17 +477,15 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
489477
<PanelContext.Provider
490478
value={{
491479
...panelContext,
492-
hideHeader:
493-
'hideHeader' in props ? hideHeader : panelContext.hideHeader,
480+
hideHeader: 'hideHeader' in props ? hideHeader : panelContext.hideHeader,
494481
hidePrevBtn: inRange && panelPosition === 'right',
495482
hideNextBtn: inRange && panelPosition === 'left',
496483
}}
497484
>
498485
<div
499486
tabIndex={tabIndex}
500487
className={classNames(`${prefixCls}-panel`, className, {
501-
[`${prefixCls}-panel-has-range`]:
502-
rangedValue && rangedValue[0] && rangedValue[1],
488+
[`${prefixCls}-panel-has-range`]: rangedValue && rangedValue[0] && rangedValue[1],
503489
[`${prefixCls}-panel-has-range-hover`]:
504490
hoverRangedValue && hoverRangedValue[0] && hoverRangedValue[1],
505491
[`${prefixCls}-panel-rtl`]: direction === 'rtl',

src/interface.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface Locale {
88
monthBeforeYear?: boolean;
99
yearFormat: string;
1010
monthFormat?: string;
11+
quarterFormat?: string;
1112

1213
today: string;
1314
now: string;
@@ -39,7 +40,7 @@ export interface Locale {
3940
shortMonths?: string[];
4041
}
4142

42-
export type PanelMode = 'time' | 'date' | 'week' | 'month' | 'year' | 'decade';
43+
export type PanelMode = 'time' | 'date' | 'week' | 'month' | 'quarter' | 'year' | 'decade';
4344

4445
export type PickerMode = Exclude<PanelMode, 'datetime' | 'decade'>;
4546

@@ -51,10 +52,7 @@ export interface PanelRefProps {
5152

5253
export type NullableDateType<DateType> = DateType | null | undefined;
5354

54-
export type OnSelect<DateType> = (
55-
value: DateType,
56-
type: 'key' | 'mouse' | 'submit',
57-
) => void;
55+
export type OnSelect<DateType> = (value: DateType, type: 'key' | 'mouse' | 'submit') => void;
5856

5957
export interface PanelSharedProps<DateType> {
6058
prefixCls: string;
@@ -91,15 +89,10 @@ export interface DisabledTimes {
9189

9290
export type DisabledTime<DateType> = (date: DateType | null) => DisabledTimes;
9391

94-
export type OnPanelChange<DateType> = (
95-
value: DateType,
96-
mode: PanelMode,
97-
) => void;
92+
export type OnPanelChange<DateType> = (value: DateType, mode: PanelMode) => void;
9893

9994
export type EventValue<DateType> = DateType | null;
100-
export type RangeValue<DateType> =
101-
| [EventValue<DateType>, EventValue<DateType>]
102-
| null;
95+
export type RangeValue<DateType> = [EventValue<DateType>, EventValue<DateType>] | null;
10396

10497
export interface Components {
10598
button?: React.ComponentType | string;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as React from 'react';
2+
import { GenerateConfig } from '../../generate';
3+
import { Locale } from '../../interface';
4+
import { isSameQuarter } from '../../utils/dateUtil';
5+
import RangeContext from '../../RangeContext';
6+
import useCellClassName from '../../hooks/useCellClassName';
7+
import PanelBody from '../PanelBody';
8+
9+
export const QUARTER_COL_COUNT = 4;
10+
const QUARTER_ROW_COUNT = 1;
11+
12+
export interface QuarterBodyProps<DateType> {
13+
prefixCls: string;
14+
locale: Locale;
15+
generateConfig: GenerateConfig<DateType>;
16+
value?: DateType | null;
17+
viewDate: DateType;
18+
disabledDate?: (date: DateType) => boolean;
19+
onSelect: (value: DateType) => void;
20+
}
21+
22+
function QuarterBody<DateType>(props: QuarterBodyProps<DateType>) {
23+
const { prefixCls, locale, value, viewDate, generateConfig } = props;
24+
25+
const { rangedValue, hoverRangedValue } = React.useContext(RangeContext);
26+
27+
const cellPrefixCls = `${prefixCls}-cell`;
28+
29+
const getCellClassName = useCellClassName({
30+
cellPrefixCls,
31+
value,
32+
generateConfig,
33+
rangedValue,
34+
hoverRangedValue,
35+
isSameCell: (current, target) => isSameQuarter(generateConfig, current, target),
36+
isInView: () => true,
37+
offsetCell: (date, offset) => generateConfig.addMonth(date, offset * 3),
38+
});
39+
40+
const baseQuarter = generateConfig.setDate(generateConfig.setMonth(viewDate, 0), 1);
41+
42+
return (
43+
<PanelBody
44+
{...props}
45+
rowNum={QUARTER_ROW_COUNT}
46+
colNum={QUARTER_COL_COUNT}
47+
baseDate={baseQuarter}
48+
getCellText={date =>
49+
generateConfig.locale.format(locale.locale, date, locale.quarterFormat || '\\QQ')
50+
}
51+
getCellClassName={getCellClassName}
52+
getCellDate={(date, offset) => generateConfig.addMonth(date, offset * 3)}
53+
titleCell={date => generateConfig.locale.format(locale.locale, date, 'YYYY-\\QQ')}
54+
/>
55+
);
56+
}
57+
58+
export default QuarterBody;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as React from 'react';
2+
import Header from '../Header';
3+
import { Locale } from '../../interface';
4+
import { GenerateConfig } from '../../generate';
5+
import PanelContext from '../../PanelContext';
6+
7+
export interface QuarterHeaderProps<DateType> {
8+
prefixCls: string;
9+
viewDate: DateType;
10+
locale: Locale;
11+
generateConfig: GenerateConfig<DateType>;
12+
13+
onPrevYear: () => void;
14+
onNextYear: () => void;
15+
onYearClick: () => void;
16+
}
17+
18+
function QuarterHeader<DateType>(props: QuarterHeaderProps<DateType>) {
19+
const {
20+
prefixCls,
21+
generateConfig,
22+
locale,
23+
viewDate,
24+
onNextYear,
25+
onPrevYear,
26+
onYearClick,
27+
} = props;
28+
const { hideHeader } = React.useContext(PanelContext);
29+
if (hideHeader) {
30+
return null;
31+
}
32+
33+
const headerPrefixCls = `${prefixCls}-header`;
34+
35+
return (
36+
<Header prefixCls={headerPrefixCls} onSuperPrev={onPrevYear} onSuperNext={onNextYear}>
37+
<button type="button" key="year" onClick={onYearClick} className={`${prefixCls}-year-btn`}>
38+
{generateConfig.locale.format(locale.locale, viewDate, locale.yearFormat)}
39+
</button>
40+
</Header>
41+
);
42+
}
43+
44+
export default QuarterHeader;

0 commit comments

Comments
 (0)