From dde36511e60c98246ee51e6fd3592e4676892fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kiner-tang=28=E6=96=87=E8=BE=89=29?= <1127031143@qq.com> Date: Thu, 19 Oct 2023 02:03:20 -0500 Subject: [PATCH] feat: Modal & Select support z-index context to manage z-index (#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 --- components/_util/__tests__/useZIndex.test.tsx | 49 ++++++++++ components/_util/hooks/useZIndex.tsx | 56 ++++++++++++ components/_util/zindexContext.ts | 5 ++ components/modal/Modal.tsx | 64 +++++++------ components/modal/__tests__/Modal.test.tsx | 34 +++++++ .../__snapshots__/demo-extend.test.ts.snap | 30 +++++++ .../__snapshots__/demo.test.tsx.snap | 28 ++++++ components/modal/demo/nested.md | 7 ++ components/modal/demo/nested.tsx | 89 +++++++++++++++++++ components/modal/index.en-US.md | 1 + components/modal/index.zh-CN.md | 1 + components/select/index.tsx | 10 ++- package.json | 4 +- 13 files changed, 347 insertions(+), 31 deletions(-) create mode 100644 components/_util/__tests__/useZIndex.test.tsx create mode 100644 components/_util/hooks/useZIndex.tsx create mode 100644 components/_util/zindexContext.ts create mode 100644 components/modal/demo/nested.md create mode 100644 components/modal/demo/nested.tsx diff --git a/components/_util/__tests__/useZIndex.test.tsx b/components/_util/__tests__/useZIndex.test.tsx new file mode 100644 index 000000000000..4695a136c0a4 --- /dev/null +++ b/components/_util/__tests__/useZIndex.test.tsx @@ -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> = ({ + children, + containerType, +}) => { + const [, contextZIndex] = useZIndex(containerType); + return {children}; +}; + +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
Child
; + }; + + const App = () => ( + + + + + + + + ); + render(); + expect(fn).toHaveBeenLastCalledWith( + (1000 + containerBaseZIndexOffset[containerKey as ZIndexContainer]) * 3 + + consumerBaseZIndexOffset[key as ZIndexConsumer], + ); + }); + }); + }); + }); +}); diff --git a/components/_util/hooks/useZIndex.tsx b/components/_util/hooks/useZIndex.tsx new file mode 100644 index 000000000000..6e31ccebb437 --- /dev/null +++ b/components/_util/hooks/useZIndex.tsx @@ -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 = { + Modal: 0, + Drawer: 0, + Popover: 30, + Popconfirm: 60, + Tooltip: 70, + Tour: 70, +}; +export const consumerBaseZIndexOffset: Record = { + 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]; +} diff --git a/components/_util/zindexContext.ts b/components/_util/zindexContext.ts new file mode 100644 index 000000000000..7a636deefe87 --- /dev/null +++ b/components/_util/zindexContext.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +const zIndexContext = React.createContext(undefined); + +export default zIndexContext; diff --git a/components/modal/Modal.tsx b/components/modal/Modal.tsx index 966cd3011ecf..c9ec2dfaee1f 100644 --- a/components/modal/Modal.tsx +++ b/components/modal/Modal.tsx @@ -7,6 +7,7 @@ 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'; @@ -14,6 +15,7 @@ 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; @@ -113,38 +115,44 @@ const Modal: React.FC = (props) => { // Select `ant-modal-content` by `panelRef` const panelRef = usePanelRef(`.${prefixCls}-content`); + // ============================ zIndex ============================ + const [zIndex, contextZIndex] = useZIndex('Modal', restProps.zIndex); + // =========================== Render =========================== return wrapSSR( - + + + , ); diff --git a/components/modal/__tests__/Modal.test.tsx b/components/modal/__tests__/Modal.test.tsx index 08bb6a063392..3b59f09348f0 100644 --- a/components/modal/__tests__/Modal.test.tsx +++ b/components/modal/__tests__/Modal.test.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import { Select } from 'antd'; import type { ModalProps } from '..'; import Modal from '..'; @@ -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( + <> + + + + setIsModalOpen(false)} + maskClosable={false} + closable={false} + styles={{ + content: { + marginBlockStart: 250, + }, + body: { + display: 'flex', + justifyContent: 'center', + }, + }} + > + + + + + + ); +}; + +export default App; diff --git a/components/modal/index.en-US.md b/components/modal/index.en-US.md index 00464168bdd7..0e04e66d8c39 100644 --- a/components/modal/index.en-US.md +++ b/components/modal/index.en-US.md @@ -35,6 +35,7 @@ Additionally, if you need show a simple confirmation dialog, you can use [`App.u Static confirmation Customize className for build-in module destroy confirmation modal dialog +Nested Modal \_InternalPanelDoNotUseOrYouWillBeFired Control modal's animation origin position Wireframe diff --git a/components/modal/index.zh-CN.md b/components/modal/index.zh-CN.md index 2599cc665ddd..380c9f82f134 100644 --- a/components/modal/index.zh-CN.md +++ b/components/modal/index.zh-CN.md @@ -36,6 +36,7 @@ demo: 静态确认对话框 自定义内部模块 className 销毁确认对话框 +嵌套弹框 \_InternalPanelDoNotUseOrYouWillBeFired 控制弹框动画原点 线框风格 diff --git a/components/select/index.tsx b/components/select/index.tsx index f4e899230cce..a779eec63d61 100755 --- a/components/select/index.tsx +++ b/components/select/index.tsx @@ -7,6 +7,7 @@ import type { OptionProps } from 'rc-select/lib/Option'; import type { BaseOptionType, DefaultOptionType } from 'rc-select/lib/Select'; import omit from 'rc-util/lib/omit'; +import { useZIndex } from '../_util/hooks/useZIndex'; import type { SelectCommonPlacement } from '../_util/motion'; import { getTransitionName } from '../_util/motion'; import genPurePanel from '../_util/PurePanel'; @@ -22,8 +23,8 @@ import { FormItemInputContext } from '../form/context'; import { useCompactItemContext } from '../space/Compact'; import useStyle from './style'; import useBuiltinPlacements from './useBuiltinPlacements'; -import useShowArrow from './useShowArrow'; import useIcons from './useIcons'; +import useShowArrow from './useShowArrow'; type RawValue = string | number; @@ -240,6 +241,9 @@ const InternalSelect = < ); } + // ====================== zIndex ========================= + const [zIndex] = useZIndex('Select', props.dropdownStyle?.zIndex as number); + // ====================== Render ======================= return wrapSSR( @@ -266,6 +270,10 @@ const InternalSelect = < getPopupContainer={getPopupContainer || getContextPopupContainer} dropdownClassName={rcSelectRtlDropdownClassName} disabled={mergedDisabled} + dropdownStyle={{ + ...props?.dropdownStyle, + zIndex: props.dropdownStyle?.zIndex ?? zIndex, + }} />, ); }; diff --git a/package.json b/package.json index d139c0332ce8..5688555bf2b9 100644 --- a/package.json +++ b/package.json @@ -319,11 +319,11 @@ "size-limit": [ { "path": "./dist/antd.min.js", - "limit": "399 KiB" + "limit": "400 KiB" }, { "path": "./dist/antd-with-locales.min.js", - "limit": "458 KiB" + "limit": "459 KiB" } ], "tnpm": {