fix(mobile): fix the issue of preview images being covered by page (#5535)

* refactor(mobile): replace mobile BasicZIndexProvider with zIndexContext from core library

* fix(mobile): fix the issue of preview images being covered by page
This commit is contained in:
Zeke Zhang 2024-10-30 11:06:29 +08:00 committed by GitHub
parent c43933e51b
commit 9e5355cbed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1178 additions and 59 deletions

View File

@ -8,6 +8,7 @@
*/ */
export * from './Action'; export * from './Action';
export * from './Action.Designer';
export * from './ActionBar'; export * from './ActionBar';
export * from './context'; export * from './context';
export * from './hooks'; export * from './hooks';
@ -15,5 +16,5 @@ export * from './hooks/useGetAriaLabelOfAction';
export * from './hooks/useGetAriaLabelOfDrawer'; export * from './hooks/useGetAriaLabelOfDrawer';
export * from './hooks/useGetAriaLabelOfModal'; export * from './hooks/useGetAriaLabelOfModal';
export * from './hooks/useGetAriaLabelOfPopover'; export * from './hooks/useGetAriaLabelOfPopover';
export * from './Action.Designer';
export * from './types'; export * from './types';
export * from './zIndexContext';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,72 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { expect, test } from '@nocobase/test/e2e';
import { shouldDisplayImageNormally } from './templates';
test.describe('zIndex', () => {
test('should display image normally', async ({ page, mockMobilePage, mockRecord }) => {
const nocoPage = await mockMobilePage(shouldDisplayImageNormally).waitForInit();
const record = await mockRecord('image');
await nocoPage.goto();
const title = record.attachment[0].title;
// 通过鼠标 hover 到 Add block 按钮来检查是否符合预期
const check = async (level: number) => {
try {
switch (level) {
case 0:
await page.getByLabel('schema-initializer-Grid-').hover({ timeout: 300 });
break;
case 1:
await page.getByLabel('schema-initializer-Grid-popup').hover({ timeout: 300 });
break;
case 2:
await page.getByLabel('schema-initializer-Grid-popup').nth(1).hover({ timeout: 300 });
break;
case 3:
await page.getByLabel('schema-initializer-Grid-popup').nth(2).hover({ timeout: 300 });
break;
}
} catch (e) {
return;
}
await page.waitForTimeout(300);
await expect(page.getByText('Desktop data blocks')).not.toBeVisible();
};
// 1. 在主页面,点击图片预览,图片不能被主页面盖住
await page.getByRole('link', { name: title }).click();
await page.waitForTimeout(300);
await check(0);
await page.getByLabel('Close lightbox').click();
// 2. 进入第一层子页面,然后点击图片预览, 图片不能被子页面盖住
await page.getByLabel('action-Action.Link-View-view-').click();
await page.getByRole('link', { name: title }).nth(1).click();
await page.waitForTimeout(300);
await check(1);
await page.getByLabel('Close lightbox').click();
// 3. 进入第二层子页面,然后点击图片预览, 图片不能被子页面盖住
await page.getByLabel('action-Action-Edit-update-').click();
await page.getByRole('link', { name: title }).nth(2).click();
await page.waitForTimeout(300);
await check(2);
await page.getByLabel('Close lightbox').click();
// 4. 进入第三层子页面,然后点击图片预览, 图片不能被子页面盖住
await page.getByLabel('action-Action-Edit-update-').nth(2).click();
await page.getByRole('link', { name: title }).nth(3).click();
await page.waitForTimeout(300);
await check(3);
await page.getByLabel('Close lightbox').click();
});
});

View File

@ -8,14 +8,14 @@
*/ */
import { ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; import { ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { Action, SchemaComponent, useActionContext } from '@nocobase/client'; import { Action, SchemaComponent, useActionContext, useZIndexContext, zIndexContext } from '@nocobase/client';
import { ConfigProvider } from 'antd'; import { ConfigProvider } from 'antd';
import { Popup } from 'antd-mobile'; import { Popup } from 'antd-mobile';
import { CloseOutline } from 'antd-mobile-icons'; import { CloseOutline } from 'antd-mobile-icons';
import React, { useCallback, useEffect, useMemo } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import { useMobileActionDrawerStyle } from './ActionDrawer.style'; import { useMobileActionDrawerStyle } from './ActionDrawer.style';
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider';
import { usePopupContainer } from './FilterAction'; import { usePopupContainer } from './FilterAction';
import { MIN_Z_INDEX_INCREMENT } from './zIndex';
export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: string }) => { export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: string }) => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
@ -23,7 +23,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
const { visible, setVisible } = useActionContext(); const { visible, setVisible } = useActionContext();
const { popupContainerRef, visiblePopup } = usePopupContainer(visible); const { popupContainerRef, visiblePopup } = usePopupContainer(visible);
const { styles } = useMobileActionDrawerStyle(); const { styles } = useMobileActionDrawerStyle();
const { basicZIndex } = useBasicZIndex(); const parentZIndex = useZIndexContext();
// this schema need to add padding in the content area of the popup // this schema need to add padding in the content area of the popup
const isSpecialSchema = isChangePasswordSchema(fieldSchema) || isEditProfileSchema(fieldSchema); const isSpecialSchema = isChangePasswordSchema(fieldSchema) || isEditProfileSchema(fieldSchema);
@ -32,7 +32,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
const specialStyle = isSpecialSchema ? { backgroundColor: 'white' } : {}; const specialStyle = isSpecialSchema ? { backgroundColor: 'white' } : {};
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT;
const zIndexStyle = useMemo(() => { const zIndexStyle = useMemo(() => {
return { return {
@ -66,7 +66,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
}, [newZIndex]); }, [newZIndex]);
return ( return (
<BasicZIndexProvider basicZIndex={newZIndex}> <zIndexContext.Provider value={newZIndex}>
<ConfigProvider theme={theme}> <ConfigProvider theme={theme}>
<Popup <Popup
visible={visiblePopup} visible={visiblePopup}
@ -120,7 +120,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
) : null} ) : null}
</Popup> </Popup>
</ConfigProvider> </ConfigProvider>
</BasicZIndexProvider> </zIndexContext.Provider>
); );
}); });

View File

@ -1,33 +0,0 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React, { useMemo } from 'react';
const BasicZIndexContext = React.createContext<{
basicZIndex: number;
}>({
basicZIndex: 0,
});
/**
* used to accumulate z-index in nested popups
* @param props
* @returns
*/
export const BasicZIndexProvider: React.FC<{ basicZIndex: number }> = (props) => {
const value = useMemo(() => ({ basicZIndex: props.basicZIndex }), [props.basicZIndex]);
return <BasicZIndexContext.Provider value={value}>{props.children}</BasicZIndexContext.Provider>;
};
export const useBasicZIndex = () => {
return React.useContext(BasicZIndexContext);
};
// minimum z-index increment
export const MIN_Z_INDEX_INCREMENT = 10;

View File

@ -7,14 +7,14 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import { Filter, withDynamicSchemaProps } from '@nocobase/client'; import { Filter, useZIndexContext, withDynamicSchemaProps } from '@nocobase/client';
import { ConfigProvider } from 'antd'; import { ConfigProvider } from 'antd';
import { Popup } from 'antd-mobile'; import { Popup } from 'antd-mobile';
import { CloseOutline } from 'antd-mobile-icons'; import { CloseOutline } from 'antd-mobile-icons';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMobileActionDrawerStyle } from './ActionDrawer.style'; import { useMobileActionDrawerStyle } from './ActionDrawer.style';
import { MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider'; import { MIN_Z_INDEX_INCREMENT } from './zIndex';
const OriginFilterAction = Filter.Action; const OriginFilterAction = Filter.Action;
@ -24,11 +24,11 @@ export const FilterAction = withDynamicSchemaProps((props) => {
{...props} {...props}
Container={(props) => { Container={(props) => {
const { visiblePopup, popupContainerRef } = usePopupContainer(props.open); const { visiblePopup, popupContainerRef } = usePopupContainer(props.open);
const { basicZIndex } = useBasicZIndex(); const parentZIndex = useZIndexContext();
const { styles } = useMobileActionDrawerStyle(); const { styles } = useMobileActionDrawerStyle();
const { t } = useTranslation(); const { t } = useTranslation();
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT;
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
const closePopup = useCallback(() => { const closePopup = useCallback(() => {
@ -116,9 +116,9 @@ export const usePopupContainer = (visible: boolean) => {
const [mobileContainer] = useState<HTMLElement>(() => document.querySelector('.mobile-container')); const [mobileContainer] = useState<HTMLElement>(() => document.querySelector('.mobile-container'));
const [visiblePopup, setVisiblePopup] = useState(false); const [visiblePopup, setVisiblePopup] = useState(false);
const popupContainerRef = React.useRef<HTMLDivElement>(null); const popupContainerRef = React.useRef<HTMLDivElement>(null);
const { basicZIndex } = useBasicZIndex(); const parentZIndex = useZIndexContext();
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT;
useEffect(() => { useEffect(() => {
if (!visible) { if (!visible) {

View File

@ -8,22 +8,23 @@
*/ */
import { useField } from '@formily/react'; import { useField } from '@formily/react';
import { useZIndexContext, zIndexContext } from '@nocobase/client';
import { ConfigProvider } from 'antd'; import { ConfigProvider } from 'antd';
import { Popup } from 'antd-mobile'; import { Popup } from 'antd-mobile';
import { CloseOutline } from 'antd-mobile-icons'; import { CloseOutline } from 'antd-mobile-icons';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider';
import { usePopupContainer } from './FilterAction'; import { usePopupContainer } from './FilterAction';
import { useInternalPopoverNesterUsedInMobileStyle } from './InternalPopoverNester.style'; import { useInternalPopoverNesterUsedInMobileStyle } from './InternalPopoverNester.style';
import { MIN_Z_INDEX_INCREMENT } from './zIndex';
const Container = (props) => { const Container = (props) => {
const { onOpenChange } = props; const { onOpenChange } = props;
const { visiblePopup, popupContainerRef } = usePopupContainer(props.open); const { visiblePopup, popupContainerRef } = usePopupContainer(props.open);
const { styles } = useInternalPopoverNesterUsedInMobileStyle(); const { styles } = useInternalPopoverNesterUsedInMobileStyle();
const field = useField(); const field = useField();
const { basicZIndex } = useBasicZIndex(); const parentZIndex = useZIndexContext();
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT;
const title = field.title || ''; const title = field.title || '';
const zIndexStyle = useMemo(() => { const zIndexStyle = useMemo(() => {
@ -49,7 +50,7 @@ const Container = (props) => {
}, [newZIndex]); }, [newZIndex]);
return ( return (
<BasicZIndexProvider basicZIndex={newZIndex}> <zIndexContext.Provider value={newZIndex}>
<ConfigProvider theme={theme}> <ConfigProvider theme={theme}>
<div onClick={openPopup}>{props.children}</div> <div onClick={openPopup}>{props.children}</div>
<Popup <Popup
@ -77,7 +78,7 @@ const Container = (props) => {
<div style={{ height: 50 }}></div> <div style={{ height: 50 }}></div>
</Popup> </Popup>
</ConfigProvider> </ConfigProvider>
</BasicZIndexProvider> </zIndexContext.Provider>
); );
}; };

View File

@ -16,12 +16,14 @@ import {
useActionContext, useActionContext,
useApp, useApp,
useTabsContext, useTabsContext,
useZIndexContext,
zIndexContext,
} from '@nocobase/client'; } from '@nocobase/client';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { usePluginTranslation } from '../../locale'; import { usePluginTranslation } from '../../locale';
import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from '../BasicZIndexProvider'; import { MIN_Z_INDEX_INCREMENT } from '../zIndex';
import { useMobileActionPageStyle } from './MobileActionPage.style'; import { useMobileActionPageStyle } from './MobileActionPage.style';
import { MobileTabsForMobileActionPage } from './MobileTabsForMobileActionPage'; import { MobileTabsForMobileActionPage } from './MobileTabsForMobileActionPage';
@ -94,11 +96,11 @@ export const MobileActionPage = ({ level, footerNodeName }) => {
const { styles } = useMobileActionPageStyle(); const { styles } = useMobileActionPageStyle();
const tabContext = useTabsContext(); const tabContext = useTabsContext();
const containerDOM = useMemo(() => document.querySelector('.nb-mobile-subpages-slot'), []); const containerDOM = useMemo(() => document.querySelector('.nb-mobile-subpages-slot'), []);
const { basicZIndex } = useBasicZIndex(); const parentZIndex = useZIndexContext();
// in nested popups, basicZIndex is an accumulated value to ensure that // in nested popups, basicZIndex is an accumulated value to ensure that
// the z-index of the current level is always higher than the previous level // the z-index of the current level is always higher than the previous level
const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT + (level || 1); const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT + (level || 1);
const footerSchema = fieldSchema.reduceProperties((buf, s) => { const footerSchema = fieldSchema.reduceProperties((buf, s) => {
if (s['x-component'] === footerNodeName) { if (s['x-component'] === footerNodeName) {
@ -118,7 +120,7 @@ export const MobileActionPage = ({ level, footerNodeName }) => {
} }
const actionPageNode = ( const actionPageNode = (
<BasicZIndexProvider basicZIndex={newZIndex}> <zIndexContext.Provider value={newZIndex}>
<div className={styles.container} style={zIndexStyle}> <div className={styles.container} style={zIndexStyle}>
<TabsContextProvider {...tabContext} tabBarExtraContent={<BackButtonUsedInSubPage />} tabBarGutter={48}> <TabsContextProvider {...tabContext} tabBarExtraContent={<BackButtonUsedInSubPage />} tabBarGutter={48}>
<SchemaComponent components={components} schema={fieldSchema} onlyRenderProperties /> <SchemaComponent components={components} schema={fieldSchema} onlyRenderProperties />
@ -136,7 +138,7 @@ export const MobileActionPage = ({ level, footerNodeName }) => {
</div> </div>
)} )}
</div> </div>
</BasicZIndexProvider> </zIndexContext.Provider>
); );
if (containerDOM) { if (containerDOM) {

View File

@ -0,0 +1,11 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
// minimum z-index increment
export const MIN_Z_INDEX_INCREMENT = 10;

View File

@ -18,12 +18,12 @@ import {
OpenModeProvider, OpenModeProvider,
useAssociationFieldModeContext, useAssociationFieldModeContext,
usePlugin, usePlugin,
zIndexContext,
} from '@nocobase/client'; } from '@nocobase/client';
import React from 'react'; import React from 'react';
import { isDesktop } from 'react-device-detect'; import { isDesktop } from 'react-device-detect';
import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer'; import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer';
import { BasicZIndexProvider } from '../adaptor-of-desktop/BasicZIndexProvider';
import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction'; import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction';
import { InternalPopoverNesterUsedInMobile } from '../adaptor-of-desktop/InternalPopoverNester'; import { InternalPopoverNesterUsedInMobile } from '../adaptor-of-desktop/InternalPopoverNester';
import { MobileActionPage } from '../adaptor-of-desktop/mobile-action-page/MobileActionPage'; import { MobileActionPage } from '../adaptor-of-desktop/mobile-action-page/MobileActionPage';
@ -102,9 +102,9 @@ export const Mobile = () => {
<ResetSchemaOptionsProvider> <ResetSchemaOptionsProvider>
<AssociationFieldModeProvider modeToComponent={modeToComponent}> <AssociationFieldModeProvider modeToComponent={modeToComponent}>
{/* the z-index of all popups and subpages will be based on this value */} {/* the z-index of all popups and subpages will be based on this value */}
<BasicZIndexProvider basicZIndex={1000}> <zIndexContext.Provider value={100}>
<MobileRouter /> <MobileRouter />
</BasicZIndexProvider> </zIndexContext.Provider>
</AssociationFieldModeProvider> </AssociationFieldModeProvider>
</ResetSchemaOptionsProvider> </ResetSchemaOptionsProvider>
</MobileAppProvider> </MobileAppProvider>