mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 17:44:35 +08:00
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:
parent
4b08667a3f
commit
666f38d756
29
components/_util/ContextIsolator.tsx
Normal file
29
components/_util/ContextIsolator.tsx
Normal 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;
|
@ -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;
|
14
components/_util/__tests__/ContextIsolator.test.tsx
Normal file
14
components/_util/__tests__/ContextIsolator.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
@ -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}
|
||||
|
@ -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>,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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>,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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>,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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>,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user