Skip to content

Commit 83717a6

Browse files
committed
feat: enhance panelRender API support components
1 parent d27127f commit 83717a6

File tree

5 files changed

+155
-37
lines changed

5 files changed

+155
-37
lines changed

docs/examples/panelRender.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default () => {
2525
locale={zhCN}
2626
allowClear
2727
defaultValue={defaultStartValue}
28-
panelRender={(node) => (
28+
panelRender={(node, { components: { Panel } }) => (
2929
<>
3030
<button
3131
type="button"
@@ -36,7 +36,7 @@ export default () => {
3636
>
3737
Change
3838
</button>
39-
39+
<Panel picker="time" />
4040
{customizeNode ? <span>My Panel</span> : node}
4141
</>
4242
)}

src/PickerInput/Popup/PopupPanel.tsx

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,44 @@ export type PopupPanelProps<DateType extends object = any> = MustProp<DateType>
1818
onPickerValueChange: (date: DateType) => void;
1919
};
2020

21+
export function getPopupPanelSharedContext(
22+
needConfirm: boolean,
23+
onSubmit: VoidFunction,
24+
): PickerHackContextProps {
25+
return {
26+
onCellDblClick: () => {
27+
if (needConfirm) {
28+
onSubmit();
29+
}
30+
},
31+
};
32+
}
33+
34+
export function getPopupPanelPickerProps<DateType extends object = any>(
35+
props: PopupPanelProps<DateType>,
36+
): PickerPanelProps<DateType> {
37+
const { picker, hoverValue, range } = props;
38+
39+
const pickerProps = {
40+
...props,
41+
hoverValue: null,
42+
hoverRangeValue: null,
43+
hideHeader: picker === 'time',
44+
} as PickerPanelProps<DateType>;
45+
46+
if (range) {
47+
pickerProps.hoverRangeValue = hoverValue as PickerPanelProps<DateType>['hoverRangeValue'];
48+
} else {
49+
pickerProps.hoverValue = hoverValue as PickerPanelProps<DateType>['hoverValue'];
50+
}
51+
52+
return pickerProps;
53+
}
54+
2155
export default function PopupPanel<DateType extends object = any>(
2256
props: PopupPanelProps<DateType>,
2357
) {
24-
const {
25-
picker,
26-
multiplePanel,
27-
pickerValue,
28-
onPickerValueChange,
29-
needConfirm,
30-
onSubmit,
31-
range,
32-
hoverValue,
33-
} = props;
58+
const { picker, multiplePanel, pickerValue, onPickerValueChange, needConfirm, onSubmit } = props;
3459
const { prefixCls, generateConfig } = React.useContext(PickerContext);
3560

3661
// ======================== Offset ========================
@@ -52,29 +77,10 @@ export default function PopupPanel<DateType extends object = any>(
5277
};
5378

5479
// ======================= Context ========================
55-
const sharedContext: PickerHackContextProps = {
56-
onCellDblClick: () => {
57-
if (needConfirm) {
58-
onSubmit();
59-
}
60-
},
61-
};
62-
63-
const hideHeader = picker === 'time';
80+
const sharedContext = getPopupPanelSharedContext(needConfirm, onSubmit);
6481

6582
// ======================== Props =========================
66-
const pickerProps = {
67-
...props,
68-
hoverValue: null,
69-
hoverRangeValue: null,
70-
hideHeader,
71-
};
72-
73-
if (range) {
74-
pickerProps.hoverRangeValue = hoverValue;
75-
} else {
76-
pickerProps.hoverValue = hoverValue;
77-
}
83+
const pickerProps = getPopupPanelPickerProps(props);
7884

7985
// ======================== Render ========================
8086
// Multiple

src/PickerInput/Popup/index.tsx

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,75 @@ import { clsx } from 'clsx';
22
import ResizeObserver, { type ResizeObserverProps } from '@rc-component/resize-observer';
33
import * as React from 'react';
44
import type {
5+
PanelRenderPanelProps,
56
RangeTimeProps,
67
SharedPickerProps,
78
SharedTimeProps,
89
ValueDate,
910
} from '../../interface';
11+
import PickerPanel, { type PickerPanelProps, type PickerPanelRef } from '../../PickerPanel';
12+
import { PickerHackContext, type PickerHackContextProps } from '../../PickerPanel/context';
1013
import { toArray } from '../../utils/miscUtil';
1114
import PickerContext from '../context';
1215
import Footer, { type FooterProps } from './Footer';
13-
import PopupPanel, { type PopupPanelProps } from './PopupPanel';
16+
import PopupPanel, {
17+
getPopupPanelPickerProps,
18+
getPopupPanelSharedContext,
19+
type PopupPanelProps,
20+
} from './PopupPanel';
1421
import PresetPanel from './PresetPanel';
1522

23+
interface PanelRenderContextProps<DateType extends object = any> {
24+
pickerProps: PickerPanelProps<DateType>;
25+
sharedContext: PickerHackContextProps;
26+
}
27+
28+
const PanelRenderContext = React.createContext<PanelRenderContextProps>(null!);
29+
30+
const InternalPanelRenderPanel = React.forwardRef(
31+
<DateType extends object = any>(
32+
props: PanelRenderPanelProps<DateType>,
33+
ref: React.Ref<PickerPanelRef>,
34+
) => {
35+
const context = React.useContext<PanelRenderContextProps<DateType>>(PanelRenderContext);
36+
37+
if (!context) {
38+
return <PickerPanel ref={ref} {...(props as PickerPanelProps<DateType>)} />;
39+
}
40+
41+
const { pickerProps, sharedContext } = context;
42+
const mergedPicker = props.picker ?? pickerProps.picker;
43+
const mergedProps = {
44+
...pickerProps,
45+
...props,
46+
picker: mergedPicker,
47+
mode: props.mode ?? (props.picker !== undefined ? mergedPicker : pickerProps.mode),
48+
hideHeader: props.hideHeader ?? mergedPicker === 'time',
49+
components: props.components
50+
? { ...pickerProps.components, ...props.components }
51+
: pickerProps.components,
52+
classNames: props.classNames
53+
? { ...pickerProps.classNames, ...props.classNames }
54+
: pickerProps.classNames,
55+
styles: props.styles ? { ...pickerProps.styles, ...props.styles } : pickerProps.styles,
56+
} as PickerPanelProps<DateType>;
57+
58+
return (
59+
<PickerHackContext.Provider value={sharedContext}>
60+
<PickerPanel ref={ref} {...mergedProps} />
61+
</PickerHackContext.Provider>
62+
);
63+
},
64+
);
65+
66+
const PanelRenderPanel = InternalPanelRenderPanel as <DateType extends object = any>(
67+
props: PanelRenderPanelProps<DateType> & React.RefAttributes<PickerPanelRef>,
68+
) => React.ReactElement<any>;
69+
70+
if (process.env.NODE_ENV !== 'production') {
71+
InternalPanelRenderPanel.displayName = 'PanelRenderPanel';
72+
}
73+
1674
export type PopupShowTimeConfig<DateType extends object = any> = Omit<
1775
RangeTimeProps<DateType>,
1876
'defaultValue' | 'defaultOpenValue' | 'disabledTime'
@@ -78,6 +136,7 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
78136
// Change
79137
value,
80138
onSelect,
139+
needConfirm,
81140
isInvalid,
82141
defaultOpenValue,
83142
onOk,
@@ -182,6 +241,11 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
182241
onSubmit();
183242
};
184243

244+
const panelRenderContext = {
245+
pickerProps: getPopupPanelPickerProps({ ...props, value: popupPanelValue }),
246+
sharedContext: getPopupPanelSharedContext(needConfirm, onSubmit),
247+
};
248+
185249
let mergedNodes: React.ReactNode = (
186250
<div className={`${prefixCls}-panel-layout`}>
187251
{/* `any` here since PresetPanel is reused for both Single & Range Picker which means return type is not stable */}
@@ -203,10 +267,16 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
203267
</div>
204268
);
205269

206-
if (panelRender) {
207-
mergedNodes = panelRender(mergedNodes);
270+
if (typeof panelRender === 'function') {
271+
mergedNodes = panelRender(mergedNodes, { components: { Panel: PanelRenderPanel } });
208272
}
209273

274+
mergedNodes = (
275+
<PanelRenderContext.Provider value={panelRenderContext}>
276+
{mergedNodes}
277+
</PanelRenderContext.Provider>
278+
);
279+
210280
// ======================== Render ========================
211281
const containerPrefixCls = `${panelPrefixCls}-container`;
212282

src/interface.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import type { AlignType, BuildInPlacements } from '@rc-component/trigger';
2+
import type * as React from 'react';
23
import type { GenerateConfig } from './generate';
4+
import type { PickerPanelProps, PickerPanelRef } from './PickerPanel';
35

46
export type NullableDateType<DateType> = DateType | null | undefined;
57

8+
export type PanelRenderPanelProps<DateType extends object = any> = Partial<
9+
PickerPanelProps<DateType>
10+
>;
11+
12+
export interface PanelRenderExtra<DateType extends object = any> {
13+
components: {
14+
Panel: React.ComponentType<
15+
PanelRenderPanelProps<DateType> & React.RefAttributes<PickerPanelRef>
16+
>;
17+
};
18+
}
19+
620
export type Locale = {
721
locale: string;
822

@@ -460,7 +474,10 @@ export interface SharedPickerProps<DateType extends object = any>
460474
showNow?: boolean;
461475
/** @deprecated Please use `showNow` instead */
462476
showToday?: boolean;
463-
panelRender?: (originPanel: React.ReactNode) => React.ReactNode;
477+
panelRender?: (
478+
originPanel: React.ReactNode,
479+
extra: PanelRenderExtra<DateType>,
480+
) => React.ReactNode;
464481
renderExtraFooter?: (mode: PanelMode) => React.ReactNode;
465482
}
466483

tests/picker.spec.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,31 @@ describe('Picker.Basic', () => {
952952
expect(document.querySelector('.rc-picker')).toMatchSnapshot();
953953
});
954954

955+
it('panelRender Panel uses popup context by default', () => {
956+
const onChange = jest.fn();
957+
const { container } = render(
958+
<DayPicker
959+
open
960+
onChange={onChange}
961+
panelRender={(_, { components: { Panel } }) => <Panel />}
962+
/>,
963+
);
964+
965+
selectCell(11);
966+
967+
expect(onChange).toHaveBeenCalled();
968+
expect(isSame(onChange.mock.calls[0][0], '1990-09-11')).toBeTruthy();
969+
expect(container.querySelector('input').value).toEqual('1990-09-11');
970+
});
971+
972+
it('panelRender Panel allows picker override', () => {
973+
render(
974+
<DayPicker open panelRender={(_, { components: { Panel } }) => <Panel picker="time" />} />,
975+
);
976+
977+
expect(document.querySelector('.rc-picker-time-panel')).toBeTruthy();
978+
});
979+
955980
it('change panel when `picker` changed', () => {
956981
const { rerender } = render(<DayPicker open picker="week" />);
957982
expect(document.querySelector('.rc-picker-week-panel')).toBeTruthy();

0 commit comments

Comments
 (0)