refactor: add ContextIsolator component (#49438)

* refactor: add ContextIsolator component

* fix: fix

* fix: fix

* test: fix test case

* test: add test case

---------

Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
lijianan 2024-06-21 02:10:21 +08:00 committed by GitHub
parent 4b08667a3f
commit 666f38d756
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 181 additions and 132 deletions

View File

@ -0,0 +1,29 @@
import React from 'react';
import { NoFormStyle } from '../form/context';
import { NoCompactStyle } from '../space/Compact';
const ContextIsolator: React.FC<
Readonly<
React.PropsWithChildren<Partial<Record<'isolateSpaceContext' | 'isolateFormContext', boolean>>>
>
> = (props) => {
const { isolateSpaceContext, isolateFormContext, children } = props;
if (children === undefined || children === null) {
return null;
}
let result: React.ReactNode = children;
if (isolateFormContext) {
result = (
<NoFormStyle override status>
{result}
</NoFormStyle>
);
}
if (isolateSpaceContext) {
result = <NoCompactStyle>{result}</NoCompactStyle>;
}
return result;
};
export default ContextIsolator;

View File

@ -1,15 +0,0 @@
import React from 'react';
import { NoFormStyle } from '../form/context';
import { NoCompactStyle } from '../space/Compact';
const getInputAddon = (addon: React.ReactNode): React.ReactNode =>
addon ? (
<NoCompactStyle>
<NoFormStyle override status>
{addon}
</NoFormStyle>
</NoCompactStyle>
) : null;
export default getInputAddon;

View File

@ -0,0 +1,14 @@
import React from 'react';
import { render } from '../../../tests/utils';
import ContextIsolator from '../ContextIsolator';
describe('ContextIsolator component', () => {
it('ContextIsolator should work when Children is null', () => {
[undefined, null].forEach((item) => {
expect(() => {
render(<ContextIsolator>{item}</ContextIsolator>);
}).not.toThrow();
});
});
});

View File

@ -2,6 +2,7 @@ import React, { useContext, useMemo, useRef } from 'react';
import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import ContextIsolator from '../_util/ContextIsolator';
import genPurePanel from '../_util/PurePanel';
import { getStatusClassNames } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
@ -10,7 +11,7 @@ import { ConfigContext } from '../config-provider/context';
import DisabledContext from '../config-provider/DisabledContext';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useSize from '../config-provider/hooks/useSize';
import { FormItemInputContext, NoFormStyle } from '../form/context';
import { FormItemInputContext } from '../form/context';
import type { PopoverProps } from '../popover';
import Popover from '../popover';
import type { Color } from './color';
@ -198,14 +199,14 @@ const ColorPicker: CompoundedComponent = (props) => {
}
}}
content={
<NoFormStyle override status>
<ContextIsolator isolateFormContext>
<ColorPickerPanel
{...colorBaseProps}
onChange={handleChange}
onChangeComplete={handleChangeComplete}
onClear={handleClear}
/>
</NoFormStyle>
</ContextIsolator>
}
overlayClassName={mergedPopupCls}
{...popoverProps}

View File

@ -8,6 +8,7 @@ import { RangePicker as RCRangePicker } from 'rc-picker';
import type { PickerRef } from 'rc-picker';
import type { GenerateConfig } from 'rc-picker/lib/generate/index';
import ContextIsolator from '../../_util/ContextIsolator';
import { useZIndex } from '../../_util/hooks/useZIndex';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import type { AnyObject } from '../../_util/type';
@ -19,7 +20,7 @@ import useSize from '../../config-provider/hooks/useSize';
import { FormItemInputContext } from '../../form/context';
import useVariant from '../../form/hooks/useVariants';
import { useLocale } from '../../locale';
import { NoCompactStyle, useCompactItemContext } from '../../space/Compact';
import { useCompactItemContext } from '../../space/Compact';
import enUS from '../locale/en_US';
import useStyle from '../style';
import { getRangePlaceholder, transPlacement2DropdownAlign, useIcons } from '../util';
@ -106,7 +107,7 @@ export default function generateRangePicker<DateType extends AnyObject>(
const [zIndex] = useZIndex('DatePicker', props.popupStyle?.zIndex as number);
return wrapCSSVar(
<NoCompactStyle>
<ContextIsolator isolateSpaceContext>
<RCRangePicker<DateType>
separator={
<span aria-label="to" className={`${prefixCls}-separator`}>
@ -167,7 +168,7 @@ export default function generateRangePicker<DateType extends AnyObject>(
}}
allowClear={mergedAllowClear}
/>
</NoCompactStyle>,
</ContextIsolator>,
);
});

View File

@ -8,6 +8,7 @@ import type { PickerRef } from 'rc-picker';
import type { GenerateConfig } from 'rc-picker/lib/generate/index';
import type { PickerMode } from 'rc-picker/lib/interface';
import ContextIsolator from '../../_util/ContextIsolator';
import { useZIndex } from '../../_util/hooks/useZIndex';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import type { AnyObject } from '../../_util/type';
@ -19,7 +20,7 @@ import useSize from '../../config-provider/hooks/useSize';
import { FormItemInputContext } from '../../form/context';
import useVariant from '../../form/hooks/useVariants';
import { useLocale } from '../../locale';
import { NoCompactStyle, useCompactItemContext } from '../../space/Compact';
import { useCompactItemContext } from '../../space/Compact';
import enUS from '../locale/en_US';
import useStyle from '../style';
import { getPlaceholder, transPlacement2DropdownAlign, useIcons } from '../util';
@ -145,7 +146,7 @@ export default function generatePicker<DateType extends AnyObject>(
const [zIndex] = useZIndex('DatePicker', props.popupStyle?.zIndex as number);
return wrapCSSVar(
<NoCompactStyle>
<ContextIsolator isolateSpaceContext>
<RCPicker<DateType>
ref={innerRef}
placeholder={getPlaceholder(locale, mergedPicker, placeholder)}
@ -205,7 +206,7 @@ export default function generatePicker<DateType extends AnyObject>(
allowClear={mergedAllowClear}
removeIcon={removeIcon}
/>
</NoCompactStyle>,
</ContextIsolator>,
);
});

View File

@ -5,13 +5,12 @@ import RcDrawer from 'rc-drawer';
import type { Placement } from 'rc-drawer/lib/Drawer';
import type { CSSMotionProps } from 'rc-motion';
import ContextIsolator from '../_util/ContextIsolator';
import { useZIndex } from '../_util/hooks/useZIndex';
import { getTransitionName } from '../_util/motion';
import { devUseWarning } from '../_util/warning';
import zIndexContext from '../_util/zindexContext';
import { ConfigContext } from '../config-provider';
import { NoFormStyle } from '../form/context';
import { NoCompactStyle } from '../space/Compact';
import { usePanelRef } from '../watermark/context';
import type { DrawerClassNames, DrawerPanelProps, DrawerStyles } from './DrawerPanel';
import DrawerPanel from './DrawerPanel';
@ -161,55 +160,53 @@ const Drawer: React.FC<DrawerProps> & {
const { classNames: contextClassNames = {}, styles: contextStyles = {} } = drawer || {};
return wrapCSSVar(
<NoCompactStyle>
<NoFormStyle status override>
<zIndexContext.Provider value={contextZIndex}>
<RcDrawer
prefixCls={prefixCls}
onClose={onClose}
maskMotion={maskMotion}
motion={panelMotion}
{...rest}
classNames={{
mask: classNames(propClassNames.mask, contextClassNames.mask),
content: classNames(propClassNames.content, contextClassNames.content),
wrapper: classNames(propClassNames.wrapper, contextClassNames.wrapper),
}}
styles={{
mask: {
...propStyles.mask,
...maskStyle,
...contextStyles.mask,
},
content: {
...propStyles.content,
...drawerStyle,
...contextStyles.content,
},
wrapper: {
...propStyles.wrapper,
...contentWrapperStyle,
...contextStyles.wrapper,
},
}}
open={open ?? visible}
mask={mask}
push={push}
width={mergedWidth}
height={mergedHeight}
style={{ ...drawer?.style, ...style }}
className={classNames(drawer?.className, className)}
rootClassName={drawerClassName}
getContainer={getContainer}
afterOpenChange={afterOpenChange ?? afterVisibleChange}
panelRef={panelRef}
zIndex={zIndex}
>
<DrawerPanel prefixCls={prefixCls} {...rest} onClose={onClose} />
</RcDrawer>
</zIndexContext.Provider>
</NoFormStyle>
</NoCompactStyle>,
<ContextIsolator isolateFormContext isolateSpaceContext>
<zIndexContext.Provider value={contextZIndex}>
<RcDrawer
prefixCls={prefixCls}
onClose={onClose}
maskMotion={maskMotion}
motion={panelMotion}
{...rest}
classNames={{
mask: classNames(propClassNames.mask, contextClassNames.mask),
content: classNames(propClassNames.content, contextClassNames.content),
wrapper: classNames(propClassNames.wrapper, contextClassNames.wrapper),
}}
styles={{
mask: {
...propStyles.mask,
...maskStyle,
...contextStyles.mask,
},
content: {
...propStyles.content,
...drawerStyle,
...contextStyles.content,
},
wrapper: {
...propStyles.wrapper,
...contentWrapperStyle,
...contextStyles.wrapper,
},
}}
open={open ?? visible}
mask={mask}
push={push}
width={mergedWidth}
height={mergedHeight}
style={{ ...drawer?.style, ...style }}
className={classNames(drawer?.className, className)}
rootClassName={drawerClassName}
getContainer={getContainer}
afterOpenChange={afterOpenChange ?? afterVisibleChange}
panelRef={panelRef}
zIndex={zIndex}
>
<DrawerPanel prefixCls={prefixCls} {...rest} onClose={onClose} />
</RcDrawer>
</zIndexContext.Provider>
</ContextIsolator>,
);
};

View File

@ -5,7 +5,7 @@ import classNames from 'classnames';
import type { InputNumberProps as RcInputNumberProps, ValueType } from 'rc-input-number';
import RcInputNumber from 'rc-input-number';
import getInputAddon from '../_util/InputAddon';
import ContextIsolator from '../_util/ContextIsolator';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
@ -137,8 +137,20 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
controls={controlsTemp}
prefix={prefix}
suffix={suffixNode}
addonBefore={getInputAddon(addonBefore)}
addonAfter={getInputAddon(addonAfter)}
addonBefore={
addonBefore && (
<ContextIsolator isolateFormContext isolateSpaceContext>
{addonBefore}
</ContextIsolator>
)
}
addonAfter={
addonAfter && (
<ContextIsolator isolateFormContext isolateSpaceContext>
{addonAfter}
</ContextIsolator>
)
}
classNames={{
input: inputNumberClass,
variant: classNames(

View File

@ -4,8 +4,8 @@ import type { InputRef, InputProps as RcInputProps } from 'rc-input';
import RcInput from 'rc-input';
import { composeRef } from 'rc-util/lib/ref';
import ContextIsolator from '../_util/ContextIsolator';
import getAllowClear from '../_util/getAllowClear';
import getInputAddon from '../_util/InputAddon';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
@ -198,8 +198,20 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
input?.className,
)}
onChange={handleChange}
addonBefore={getInputAddon(addonBefore)}
addonAfter={getInputAddon(addonAfter)}
addonBefore={
addonBefore && (
<ContextIsolator isolateFormContext isolateSpaceContext>
{addonBefore}
</ContextIsolator>
)
}
addonAfter={
addonAfter && (
<ContextIsolator isolateFormContext isolateSpaceContext>
{addonAfter}
</ContextIsolator>
)
}
classNames={{
...classes,
...input?.classNames,

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { supportNodeRef, useComposeRef } from 'rc-util';
import { NoCompactStyle } from '../space/Compact';
import ContextIsolator from '../_util/ContextIsolator';
import type { MenuProps } from './menu';
// Used for Dropdown only
@ -43,9 +43,9 @@ export const OverrideProvider = React.forwardRef<
return (
<OverrideContext.Provider value={context}>
<NoCompactStyle>
<ContextIsolator isolateSpaceContext>
{canRef ? React.cloneElement(children as React.ReactElement, { ref: mergedRef }) : children}
</NoCompactStyle>
</ContextIsolator>
</OverrideContext.Provider>
);
});

View File

@ -3,6 +3,7 @@ import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import Dialog from 'rc-dialog';
import ContextIsolator from '../_util/ContextIsolator';
import useClosable, { pickClosable } from '../_util/hooks/useClosable';
import { useZIndex } from '../_util/hooks/useZIndex';
import { getTransitionName } from '../_util/motion';
@ -11,9 +12,7 @@ import { devUseWarning } from '../_util/warning';
import zIndexContext from '../_util/zindexContext';
import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import { NoFormStyle } from '../form/context';
import Skeleton from '../skeleton';
import { NoCompactStyle } from '../space/Compact';
import { usePanelRef } from '../watermark/context';
import type { ModalProps, MousePosition } from './interface';
import { Footer, renderCloseIcon } from './shared';
@ -127,49 +126,47 @@ const Modal: React.FC<ModalProps> = (props) => {
// =========================== Render ===========================
return wrapCSSVar(
<NoCompactStyle>
<NoFormStyle status override>
<zIndexContext.Provider value={contextZIndex}>
<Dialog
width={width}
{...restProps}
zIndex={zIndex}
getContainer={getContainer === undefined ? getContextPopupContainer : getContainer}
prefixCls={prefixCls}
rootClassName={classNames(hashId, rootClassName, cssVarCls, rootCls)}
footer={dialogFooter}
visible={open ?? visible}
mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel as any}
closable={mergedClosable}
closeIcon={mergedCloseIcon}
focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
className={classNames(hashId, className, modalContext?.className)}
style={{ ...modalContext?.style, ...style }}
classNames={{
...modalContext?.classNames,
...modalClassNames,
wrapper: classNames(wrapClassNameExtended, modalClassNames?.wrapper),
}}
styles={{ ...modalContext?.styles, ...modalStyles }}
panelRef={panelRef}
>
{loading ? (
<Skeleton
active
title={false}
paragraph={{ rows: 4 }}
className={`${prefixCls}-body-skeleton`}
/>
) : (
children
)}
</Dialog>
</zIndexContext.Provider>
</NoFormStyle>
</NoCompactStyle>,
<ContextIsolator isolateFormContext isolateSpaceContext>
<zIndexContext.Provider value={contextZIndex}>
<Dialog
width={width}
{...restProps}
zIndex={zIndex}
getContainer={getContainer === undefined ? getContextPopupContainer : getContainer}
prefixCls={prefixCls}
rootClassName={classNames(hashId, rootClassName, cssVarCls, rootCls)}
footer={dialogFooter}
visible={open ?? visible}
mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel as any}
closable={mergedClosable}
closeIcon={mergedCloseIcon}
focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
className={classNames(hashId, className, modalContext?.className)}
style={{ ...modalContext?.style, ...style }}
classNames={{
...modalContext?.classNames,
...modalClassNames,
wrapper: classNames(wrapClassNameExtended, modalClassNames?.wrapper),
}}
styles={{ ...modalContext?.styles, ...modalStyles }}
panelRef={panelRef}
>
{loading ? (
<Skeleton
active
title={false}
paragraph={{ rows: 4 }}
className={`${prefixCls}-body-skeleton`}
/>
) : (
children
)}
</Dialog>
</zIndexContext.Provider>
</ContextIsolator>,
);
};

View File

@ -10,6 +10,7 @@ import type {
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { PresetColorType } from '../_util/colors';
import ContextIsolator from '../_util/ContextIsolator';
import type { RenderFunction } from '../_util/getRenderPropValue';
import { useZIndex } from '../_util/hooks/useZIndex';
import { getTransitionName } from '../_util/motion';
@ -20,7 +21,6 @@ import type { LiteralUnion } from '../_util/type';
import { devUseWarning } from '../_util/warning';
import zIndexContext from '../_util/zindexContext';
import { ConfigContext } from '../config-provider';
import { NoCompactStyle } from '../space/Compact';
import { useToken } from '../theme/internal';
import PurePanel from './PurePanel';
import useStyle from './style';
@ -241,9 +241,9 @@ const InternalTooltip = React.forwardRef<TooltipRef, TooltipProps>((props, ref)
}, [overlay, title]);
const memoOverlayWrapper = (
<NoCompactStyle>
<ContextIsolator isolateSpaceContext>
{typeof memoOverlay === 'function' ? memoOverlay() : memoOverlay}
</NoCompactStyle>
</ContextIsolator>
);
const {