diff --git a/docs/examples/panelRender.tsx b/docs/examples/panelRender.tsx
index f44065e4b..77f29ded0 100644
--- a/docs/examples/panelRender.tsx
+++ b/docs/examples/panelRender.tsx
@@ -25,7 +25,7 @@ export default () => {
locale={zhCN}
allowClear
defaultValue={defaultStartValue}
- panelRender={(node) => (
+ panelRender={(node, { components: { Panel } }) => (
<>
-
+
{customizeNode ? My Panel : node}
>
)}
diff --git a/src/PickerInput/Popup/PopupPanel.tsx b/src/PickerInput/Popup/PopupPanel.tsx
index e59b4fba3..478cf8cc2 100644
--- a/src/PickerInput/Popup/PopupPanel.tsx
+++ b/src/PickerInput/Popup/PopupPanel.tsx
@@ -18,19 +18,44 @@ export type PopupPanelProps = MustProp
onPickerValueChange: (date: DateType) => void;
};
+export function getPopupPanelSharedContext(
+ needConfirm: boolean,
+ onSubmit: VoidFunction,
+): PickerHackContextProps {
+ return {
+ onCellDblClick: () => {
+ if (needConfirm) {
+ onSubmit();
+ }
+ },
+ };
+}
+
+export function getPopupPanelPickerProps(
+ props: PopupPanelProps,
+): PickerPanelProps {
+ const { picker, hoverValue, range } = props;
+
+ const pickerProps = {
+ ...props,
+ hoverValue: null,
+ hoverRangeValue: null,
+ hideHeader: picker === 'time',
+ } as PickerPanelProps;
+
+ if (range) {
+ pickerProps.hoverRangeValue = hoverValue as PickerPanelProps['hoverRangeValue'];
+ } else {
+ pickerProps.hoverValue = hoverValue as PickerPanelProps['hoverValue'];
+ }
+
+ return pickerProps;
+}
+
export default function PopupPanel(
props: PopupPanelProps,
) {
- const {
- picker,
- multiplePanel,
- pickerValue,
- onPickerValueChange,
- needConfirm,
- onSubmit,
- range,
- hoverValue,
- } = props;
+ const { picker, multiplePanel, pickerValue, onPickerValueChange, needConfirm, onSubmit } = props;
const { prefixCls, generateConfig } = React.useContext(PickerContext);
// ======================== Offset ========================
@@ -52,29 +77,10 @@ export default function PopupPanel(
};
// ======================= Context ========================
- const sharedContext: PickerHackContextProps = {
- onCellDblClick: () => {
- if (needConfirm) {
- onSubmit();
- }
- },
- };
-
- const hideHeader = picker === 'time';
+ const sharedContext = getPopupPanelSharedContext(needConfirm, onSubmit);
// ======================== Props =========================
- const pickerProps = {
- ...props,
- hoverValue: null,
- hoverRangeValue: null,
- hideHeader,
- };
-
- if (range) {
- pickerProps.hoverRangeValue = hoverValue;
- } else {
- pickerProps.hoverValue = hoverValue;
- }
+ const pickerProps = getPopupPanelPickerProps(props);
// ======================== Render ========================
// Multiple
diff --git a/src/PickerInput/Popup/index.tsx b/src/PickerInput/Popup/index.tsx
index f02810390..aa0709071 100644
--- a/src/PickerInput/Popup/index.tsx
+++ b/src/PickerInput/Popup/index.tsx
@@ -2,17 +2,75 @@ import { clsx } from 'clsx';
import ResizeObserver, { type ResizeObserverProps } from '@rc-component/resize-observer';
import * as React from 'react';
import type {
+ PanelRenderPanelProps,
RangeTimeProps,
SharedPickerProps,
SharedTimeProps,
ValueDate,
} from '../../interface';
+import PickerPanel, { type PickerPanelProps, type PickerPanelRef } from '../../PickerPanel';
+import { PickerHackContext, type PickerHackContextProps } from '../../PickerPanel/context';
import { toArray } from '../../utils/miscUtil';
import PickerContext from '../context';
import Footer, { type FooterProps } from './Footer';
-import PopupPanel, { type PopupPanelProps } from './PopupPanel';
+import PopupPanel, {
+ getPopupPanelPickerProps,
+ getPopupPanelSharedContext,
+ type PopupPanelProps,
+} from './PopupPanel';
import PresetPanel from './PresetPanel';
+interface PanelRenderContextProps {
+ pickerProps: PickerPanelProps;
+ sharedContext: PickerHackContextProps;
+}
+
+const PanelRenderContext = React.createContext(null!);
+
+const InternalPanelRenderPanel = React.forwardRef(
+ (
+ props: PanelRenderPanelProps,
+ ref: React.Ref,
+ ) => {
+ const context = React.useContext>(PanelRenderContext);
+
+ if (!context) {
+ return )} />;
+ }
+
+ const { pickerProps, sharedContext } = context;
+ const mergedPicker = props.picker ?? pickerProps.picker;
+ const mergedProps = {
+ ...pickerProps,
+ ...props,
+ picker: mergedPicker,
+ mode: props.mode ?? (props.picker !== undefined ? mergedPicker : pickerProps.mode),
+ hideHeader: props.hideHeader ?? mergedPicker === 'time',
+ components: props.components
+ ? { ...pickerProps.components, ...props.components }
+ : pickerProps.components,
+ classNames: props.classNames
+ ? { ...pickerProps.classNames, ...props.classNames }
+ : pickerProps.classNames,
+ styles: props.styles ? { ...pickerProps.styles, ...props.styles } : pickerProps.styles,
+ } as PickerPanelProps;
+
+ return (
+
+
+
+ );
+ },
+);
+
+const PanelRenderPanel = InternalPanelRenderPanel as (
+ props: PanelRenderPanelProps & React.RefAttributes,
+) => React.ReactElement;
+
+if (process.env.NODE_ENV !== 'production') {
+ InternalPanelRenderPanel.displayName = 'PanelRenderPanel';
+}
+
export type PopupShowTimeConfig = Omit<
RangeTimeProps,
'defaultValue' | 'defaultOpenValue' | 'disabledTime'
@@ -78,6 +136,7 @@ export default function Popup(props: PopupProps(props: PopupProps
{/* `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(props: PopupProps
);
- if (panelRender) {
- mergedNodes = panelRender(mergedNodes);
+ if (typeof panelRender === 'function') {
+ mergedNodes = panelRender(mergedNodes, { components: { Panel: PanelRenderPanel } });
}
+ mergedNodes = (
+
+ {mergedNodes}
+
+ );
+
// ======================== Render ========================
const containerPrefixCls = `${panelPrefixCls}-container`;
diff --git a/src/interface.tsx b/src/interface.tsx
index 964f80c2b..be7146740 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -1,8 +1,22 @@
import type { AlignType, BuildInPlacements } from '@rc-component/trigger';
+import type * as React from 'react';
import type { GenerateConfig } from './generate';
+import type { PickerPanelProps, PickerPanelRef } from './PickerPanel';
export type NullableDateType = DateType | null | undefined;
+export type PanelRenderPanelProps = Partial<
+ PickerPanelProps
+>;
+
+export interface PanelRenderExtra {
+ components: {
+ Panel: React.ComponentType<
+ PanelRenderPanelProps & React.RefAttributes
+ >;
+ };
+}
+
export type Locale = {
locale: string;
@@ -460,7 +474,10 @@ export interface SharedPickerProps
showNow?: boolean;
/** @deprecated Please use `showNow` instead */
showToday?: boolean;
- panelRender?: (originPanel: React.ReactNode) => React.ReactNode;
+ panelRender?: (
+ originPanel: React.ReactNode,
+ extra: PanelRenderExtra,
+ ) => React.ReactNode;
renderExtraFooter?: (mode: PanelMode) => React.ReactNode;
}
diff --git a/tests/picker.spec.tsx b/tests/picker.spec.tsx
index 76bca254c..146de3f75 100644
--- a/tests/picker.spec.tsx
+++ b/tests/picker.spec.tsx
@@ -952,6 +952,31 @@ describe('Picker.Basic', () => {
expect(document.querySelector('.rc-picker')).toMatchSnapshot();
});
+ it('panelRender Panel uses popup context by default', () => {
+ const onChange = jest.fn();
+ const { container } = render(
+ }
+ />,
+ );
+
+ selectCell(11);
+
+ expect(onChange).toHaveBeenCalled();
+ expect(isSame(onChange.mock.calls[0][0], '1990-09-11')).toBeTruthy();
+ expect(container.querySelector('input').value).toEqual('1990-09-11');
+ });
+
+ it('panelRender Panel allows picker override', () => {
+ render(
+ } />,
+ );
+
+ expect(document.querySelector('.rc-picker-time-panel')).toBeTruthy();
+ });
+
it('change panel when `picker` changed', () => {
const { rerender } = render();
expect(document.querySelector('.rc-picker-week-panel')).toBeTruthy();