Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/examples/panelRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default () => {
locale={zhCN}
allowClear
defaultValue={defaultStartValue}
panelRender={(node) => (
panelRender={(node, { components: { Panel } }) => (
<>
<button
type="button"
Expand All @@ -36,7 +36,7 @@ export default () => {
>
Change
</button>

<Panel picker="time" />
{customizeNode ? <span>My Panel</span> : node}
</>
)}
Expand Down
68 changes: 37 additions & 31 deletions src/PickerInput/Popup/PopupPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,44 @@ export type PopupPanelProps<DateType extends object = any> = MustProp<DateType>
onPickerValueChange: (date: DateType) => void;
};

export function getPopupPanelSharedContext(
needConfirm: boolean,
onSubmit: VoidFunction,
): PickerHackContextProps {
return {
onCellDblClick: () => {
if (needConfirm) {
onSubmit();
}
},
};
}

export function getPopupPanelPickerProps<DateType extends object = any>(
props: PopupPanelProps<DateType>,
): PickerPanelProps<DateType> {
const { picker, hoverValue, range } = props;

const pickerProps = {
...props,
hoverValue: null,
hoverRangeValue: null,
hideHeader: picker === 'time',
} as PickerPanelProps<DateType>;

if (range) {
pickerProps.hoverRangeValue = hoverValue as PickerPanelProps<DateType>['hoverRangeValue'];
} else {
pickerProps.hoverValue = hoverValue as PickerPanelProps<DateType>['hoverValue'];
}

return pickerProps;
}

export default function PopupPanel<DateType extends object = any>(
props: PopupPanelProps<DateType>,
) {
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 ========================
Expand All @@ -52,29 +77,10 @@ export default function PopupPanel<DateType extends object = any>(
};

// ======================= 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
Expand Down
76 changes: 73 additions & 3 deletions src/PickerInput/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<DateType extends object = any> {
pickerProps: PickerPanelProps<DateType>;
sharedContext: PickerHackContextProps;
}

const PanelRenderContext = React.createContext<PanelRenderContextProps>(null!);

const InternalPanelRenderPanel = React.forwardRef(
<DateType extends object = any>(
props: PanelRenderPanelProps<DateType>,
ref: React.Ref<PickerPanelRef>,
) => {
const context = React.useContext<PanelRenderContextProps<DateType>>(PanelRenderContext);

if (!context) {
return <PickerPanel ref={ref} {...(props as PickerPanelProps<DateType>)} />;
}

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<DateType>;

return (
<PickerHackContext.Provider value={sharedContext}>
<PickerPanel ref={ref} {...mergedProps} />
</PickerHackContext.Provider>
);
},
);

const PanelRenderPanel = InternalPanelRenderPanel as <DateType extends object = any>(
props: PanelRenderPanelProps<DateType> & React.RefAttributes<PickerPanelRef>,
) => React.ReactElement<any>;

if (process.env.NODE_ENV !== 'production') {
InternalPanelRenderPanel.displayName = 'PanelRenderPanel';
}

export type PopupShowTimeConfig<DateType extends object = any> = Omit<
RangeTimeProps<DateType>,
'defaultValue' | 'defaultOpenValue' | 'disabledTime'
Expand Down Expand Up @@ -78,6 +136,7 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
// Change
value,
onSelect,
needConfirm,
isInvalid,
defaultOpenValue,
onOk,
Expand Down Expand Up @@ -182,6 +241,11 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
onSubmit();
};

const panelRenderContext = {
pickerProps: getPopupPanelPickerProps({ ...props, value: popupPanelValue }),
sharedContext: getPopupPanelSharedContext(needConfirm, onSubmit),
};

let mergedNodes: React.ReactNode = (
<div className={`${prefixCls}-panel-layout`}>
{/* `any` here since PresetPanel is reused for both Single & Range Picker which means return type is not stable */}
Expand All @@ -203,10 +267,16 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
</div>
);

if (panelRender) {
mergedNodes = panelRender(mergedNodes);
if (typeof panelRender === 'function') {
mergedNodes = panelRender(mergedNodes, { components: { Panel: PanelRenderPanel } });
}

mergedNodes = (
<PanelRenderContext.Provider value={panelRenderContext}>
{mergedNodes}
</PanelRenderContext.Provider>
);

// ======================== Render ========================
const containerPrefixCls = `${panelPrefixCls}-container`;

Expand Down
19 changes: 18 additions & 1 deletion src/interface.tsx
Original file line number Diff line number Diff line change
@@ -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> = DateType | null | undefined;

export type PanelRenderPanelProps<DateType extends object = any> = Partial<
PickerPanelProps<DateType>
>;

export interface PanelRenderExtra<DateType extends object = any> {
components: {
Panel: React.ComponentType<
PanelRenderPanelProps<DateType> & React.RefAttributes<PickerPanelRef>
>;
};
}

export type Locale = {
locale: string;

Expand Down Expand Up @@ -460,7 +474,10 @@ export interface SharedPickerProps<DateType extends object = any>
showNow?: boolean;
/** @deprecated Please use `showNow` instead */
showToday?: boolean;
panelRender?: (originPanel: React.ReactNode) => React.ReactNode;
panelRender?: (
originPanel: React.ReactNode,
extra: PanelRenderExtra<DateType>,
) => React.ReactNode;
renderExtraFooter?: (mode: PanelMode) => React.ReactNode;
}

Expand Down
25 changes: 25 additions & 0 deletions tests/picker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<DayPicker
open
onChange={onChange}
panelRender={(_, { components: { Panel } }) => <Panel />}
/>,
);

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(
<DayPicker open panelRender={(_, { components: { Panel } }) => <Panel picker="time" />} />,
);

expect(document.querySelector('.rc-picker-time-panel')).toBeTruthy();
});

it('change panel when `picker` changed', () => {
const { rerender } = render(<DayPicker open picker="week" />);
expect(document.querySelector('.rc-picker-week-panel')).toBeTruthy();
Expand Down
Loading