Skip to content

Commit

Permalink
feat: Modal & Select support z-index context to manage z-index (ant-d…
Browse files Browse the repository at this point in the history
…esign#45346)

* feat: z-index manager

* feat: z-index manager

* feat: update snap

* chore: update site-limit

* feat: optimize code

* feat: optimize code

* feat: add test case

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code

* feat: optimize code
  • Loading branch information
kiner-tang authored Oct 19, 2023
1 parent 98a8d30 commit dde3651
Show file tree
Hide file tree
Showing 13 changed files with 347 additions and 31 deletions.
49 changes: 49 additions & 0 deletions components/_util/__tests__/useZIndex.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { PropsWithChildren } from 'react';
import React, { useEffect } from 'react';
import { render } from '@testing-library/react';
import zIndexContext from '../zindexContext';

import type { ZIndexConsumer, ZIndexContainer } from '../hooks/useZIndex';
import { consumerBaseZIndexOffset, containerBaseZIndexOffset, useZIndex } from '../hooks/useZIndex';

const WrapWithProvider: React.FC<PropsWithChildren<{ containerType: ZIndexContainer }>> = ({
children,
containerType,
}) => {
const [, contextZIndex] = useZIndex(containerType);
return <zIndexContext.Provider value={contextZIndex}>{children}</zIndexContext.Provider>;
};

describe('Test useZIndex hooks', () => {
Object.keys(containerBaseZIndexOffset).forEach((containerKey) => {
Object.keys(consumerBaseZIndexOffset).forEach((key) => {
describe(`Test ${key} zIndex in ${containerKey}`, () => {
it('parentZIndex should be parent zIndex', () => {
const fn = jest.fn();
const Child = () => {
const [zIndex] = useZIndex(key as ZIndexConsumer);
useEffect(() => {
fn(zIndex);
}, [zIndex]);
return <div>Child</div>;
};

const App = () => (
<WrapWithProvider containerType={containerKey as ZIndexContainer}>
<WrapWithProvider containerType={containerKey as ZIndexContainer}>
<WrapWithProvider containerType={containerKey as ZIndexContainer}>
<Child />
</WrapWithProvider>
</WrapWithProvider>
</WrapWithProvider>
);
render(<App />);
expect(fn).toHaveBeenLastCalledWith(
(1000 + containerBaseZIndexOffset[containerKey as ZIndexContainer]) * 3 +
consumerBaseZIndexOffset[key as ZIndexConsumer],
);
});
});
});
});
});
56 changes: 56 additions & 0 deletions components/_util/hooks/useZIndex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import useToken from '../../theme/useToken';
import zIndexContext from '../zindexContext';

export type ZIndexContainer = 'Modal' | 'Drawer' | 'Popover' | 'Popconfirm' | 'Tooltip' | 'Tour';

export type ZIndexConsumer =
| 'Select'
| 'Dropdown'
| 'Cascader'
| 'TreeSelect'
| 'AutoComplete'
| 'ColorPicker'
| 'DatePicker'
| 'TimePicker'
| 'Menu';

export const containerBaseZIndexOffset: Record<ZIndexContainer, number> = {
Modal: 0,
Drawer: 0,
Popover: 30,
Popconfirm: 60,
Tooltip: 70,
Tour: 70,
};
export const consumerBaseZIndexOffset: Record<ZIndexConsumer, number> = {
Select: 50,
Dropdown: 50,
Cascader: 50,
TreeSelect: 50,
AutoComplete: 50,
ColorPicker: 30,
DatePicker: 50,
TimePicker: 50,
Menu: 50,
};

function isContainerType(type: ZIndexContainer | ZIndexConsumer): type is ZIndexContainer {
return type in containerBaseZIndexOffset;
}

export function useZIndex(
componentType: ZIndexContainer | ZIndexConsumer,
customZIndex?: number,
): [zIndex: number | undefined, contextZIndex: number] {
const [, token] = useToken();
const parentZIndex = React.useContext(zIndexContext);
const isContainer = isContainerType(componentType);
let zIndex = parentZIndex ?? 0;
if (isContainer) {
zIndex += token.zIndexPopupBase + containerBaseZIndexOffset[componentType];
} else {
zIndex += consumerBaseZIndexOffset[componentType];
}
return [parentZIndex === undefined ? customZIndex : zIndex, zIndex];
}
5 changes: 5 additions & 0 deletions components/_util/zindexContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

const zIndexContext = React.createContext<number | undefined>(undefined);

export default zIndexContext;
64 changes: 36 additions & 28 deletions components/modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import useClosable from '../_util/hooks/useClosable';
import { getTransitionName } from '../_util/motion';
import { canUseDocElement } from '../_util/styleChecker';
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 { ModalProps, MousePosition } from './interface';
import { Footer, renderCloseIcon } from './shared';
import useStyle from './style';
import { useZIndex } from '../_util/hooks/useZIndex';

let mousePosition: MousePosition;

Expand Down Expand Up @@ -113,38 +115,44 @@ const Modal: React.FC<ModalProps> = (props) => {
// Select `ant-modal-content` by `panelRef`
const panelRef = usePanelRef(`.${prefixCls}-content`);

// ============================ zIndex ============================
const [zIndex, contextZIndex] = useZIndex('Modal', restProps.zIndex);

// =========================== Render ===========================
return wrapSSR(
<NoCompactStyle>
<NoFormStyle status override>
<Dialog
width={width}
{...restProps}
getContainer={getContainer === undefined ? getContextPopupContainer : getContainer}
prefixCls={prefixCls}
rootClassName={classNames(hashId, rootClassName)}
footer={dialogFooter}
visible={open ?? visible}
mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel}
closable={mergedClosable}
closeIcon={mergedCloseIcon}
focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
className={classNames(hashId, className, modal?.className)}
style={{ ...modal?.style, ...style }}
classNames={{
wrapper: wrapClassNameExtended,
...modal?.classNames,
...modalClassNames,
}}
styles={{
...modal?.styles,
...modalStyles,
}}
panelRef={panelRef}
/>
<zIndexContext.Provider value={contextZIndex}>
<Dialog
width={width}
{...restProps}
zIndex={zIndex}
getContainer={getContainer === undefined ? getContextPopupContainer : getContainer}
prefixCls={prefixCls}
rootClassName={classNames(hashId, rootClassName)}
footer={dialogFooter}
visible={open ?? visible}
mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel}
closable={mergedClosable}
closeIcon={mergedCloseIcon}
focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
className={classNames(hashId, className, modal?.className)}
style={{ ...modal?.style, ...style }}
classNames={{
wrapper: wrapClassNameExtended,
...modal?.classNames,
...modalClassNames,
}}
styles={{
...modal?.styles,
...modalStyles,
}}
panelRef={panelRef}
/>
</zIndexContext.Provider>
</NoFormStyle>
</NoCompactStyle>,
);
Expand Down
34 changes: 34 additions & 0 deletions components/modal/__tests__/Modal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect } from 'react';
import { Select } from 'antd';

import type { ModalProps } from '..';
import Modal from '..';
Expand Down Expand Up @@ -182,4 +183,37 @@ describe('Modal', () => {
expect(document.querySelector('.first-origin')).toMatchSnapshot();
expect(document.querySelector('.second-props-origin')).toMatchSnapshot();
});

it('z-index should be accumulated in nested Modal', () => {
const options = [
{
label: 'Option 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
];
render(
<>
<Select open options={options} popupClassName="select0" />
<Modal open>
<Select open options={options} popupClassName="select1" />
<Modal open>
<Select open options={options} popupClassName="select2" />
</Modal>
</Modal>
</>,
);
expect(
(document.querySelectorAll('.ant-modal-wrap')[0] as HTMLDivElement)!.style.zIndex,
).toBeFalsy();
expect((document.querySelectorAll('.ant-modal-wrap')[1] as HTMLDivElement)!.style.zIndex).toBe(
'2000',
);
expect((document.querySelector('.select0') as HTMLDivElement)!.style.zIndex).toBeFalsy();
expect((document.querySelector('.select1') as HTMLDivElement)!.style.zIndex).toBe('1050');
expect((document.querySelector('.select2') as HTMLDivElement)!.style.zIndex).toBe('2050');
});
});
30 changes: 30 additions & 0 deletions components/modal/__tests__/__snapshots__/demo-extend.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,36 @@ exports[`renders components/modal/demo/modal-render.tsx extend context correctly

exports[`renders components/modal/demo/modal-render.tsx extend context correctly 2`] = `[]`;

exports[`renders components/modal/demo/nested.tsx extend context correctly 1`] = `
<button
aria-checked="false"
class="ant-switch"
role="switch"
style="position: relative; z-index: 4000;"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
Open
</span>
<span
class="ant-switch-inner-unchecked"
>
Close
</span>
</span>
</button>
`;

exports[`renders components/modal/demo/nested.tsx extend context correctly 2`] = `[]`;

exports[`renders components/modal/demo/position.tsx extend context correctly 1`] = `
Array [
<button
Expand Down
28 changes: 28 additions & 0 deletions components/modal/__tests__/__snapshots__/demo.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,34 @@ exports[`renders components/modal/demo/modal-render.tsx correctly 1`] = `
</button>
`;

exports[`renders components/modal/demo/nested.tsx correctly 1`] = `
<button
aria-checked="false"
class="ant-switch"
role="switch"
style="position:relative;z-index:4000"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
Open
</span>
<span
class="ant-switch-inner-unchecked"
>
Close
</span>
</span>
</button>
`;

exports[`renders components/modal/demo/position.tsx correctly 1`] = `
Array [
<button
Expand Down
7 changes: 7 additions & 0 deletions components/modal/demo/nested.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## zh-CN

嵌套弹框

## en-US

Nested modal.
Loading

0 comments on commit dde3651

Please sign in to comment.