This is an automated email from the ASF dual-hosted git repository. rusackas pushed a commit to branch fix/unsaved-changes-modal-zindex in repository https://gitbox.apache.org/repos/asf/superset.git
commit ff424c55c8f08aa9d8f6e5b3cdf27f116bc5916a Author: rusackas <[email protected]> AuthorDate: Wed Jul 29 00:04:31 2026 -0700 fix(core): let UnsavedChangesModal use Ant Design's automatic z-index stacking UNSAVED_CHANGES_MODAL_Z_INDEX was a hardcoded literal meant to keep this modal above other open modals (e.g. a draggable View query modal). Ant Design already auto-increments z-index for every newly opened Modal off theme.zIndexPopupBase, so any manual override just needs to be bumped again whenever something else's computed z-index creeps past it, which is exactly what happened (#42510). Audited every other zIndex/z-index usage in the frontend: this was the only place hardcoding a z-index override on a Modal-class component. Everything else either doesn't need one, or already derives it from theme.zIndexPopupBase/theme.zIndexBase for a real, local reason (sticky headers inside modal content, the native Fullscreen API's interaction with portaled antd components, custom fixed-position overlays that sit outside Ant Design's own stacking system entirely). None of those are part of this bug and are left untouched. Since this modal is always opened on top of whatever it's interrupting, dropping the override (the constant, the prop, and the pass-through) lets it stack correctly with no number to maintain. --- .../UnsavedChangesModal.test.tsx | 37 ++++++++++++++++++++++ .../src/components/UnsavedChangesModal/index.tsx | 7 ---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/UnsavedChangesModal.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/UnsavedChangesModal.test.tsx index 995bd704d6a..fac9a2b1bb2 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/UnsavedChangesModal.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/UnsavedChangesModal.test.tsx @@ -17,6 +17,7 @@ * under the License. */ import { render, screen, userEvent } from '@superset-ui/core/spec'; +import { Modal } from '@superset-ui/core/components'; import { UnsavedChangesModal } from '.'; test('should render nothing if showModal is false', () => { @@ -94,3 +95,39 @@ test('should only call handleSave when clicking the Save button', async () => { expect(mockOnHide).not.toHaveBeenCalled(); expect(mockOnConfirmNavigation).not.toHaveBeenCalled(); }); + +test('renders above an already-open modal without a hardcoded z-index', () => { + // Regression test for a bug where this modal could render BEHIND another + // already-open modal (e.g. a draggable "View query" modal), because its + // z-index was pinned to a hardcoded constant instead of relying on Ant + // Design's automatic z-index stacking. Since this modal is always opened + // on top of whatever it's interrupting, it should always come out ahead + // with no manual override at all. + render( + <> + <Modal show title="Other open modal" onHide={() => {}}> + <div>Other modal content</div> + </Modal> + <UnsavedChangesModal + showModal + onHide={() => {}} + handleSave={() => {}} + onConfirmNavigation={() => {}} + /> + </>, + ); + + const otherDialog = screen.getByRole('dialog', { + name: /other open modal/i, + }); + const unsavedChangesDialog = screen.getByRole('dialog', { + name: /unsaved changes/i, + }); + + const otherZIndex = Number(getComputedStyle(otherDialog).zIndex); + const unsavedChangesZIndex = Number( + getComputedStyle(unsavedChangesDialog).zIndex, + ); + + expect(unsavedChangesZIndex).toBeGreaterThan(otherZIndex); +}); diff --git a/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/index.tsx index bfbbfc8a17d..cca9c7573df 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/index.tsx @@ -20,10 +20,6 @@ import { t } from '@apache-superset/core/translation'; import { Icons, Modal, Typography, Button } from '@superset-ui/core/components'; import type { FC, ReactElement } from 'react'; -// Ant Design's default modal zIndex is 1000. Using a higher value ensures -// this dialog always renders above other open modals (e.g. a draggable View SQL modal). -const UNSAVED_CHANGES_MODAL_Z_INDEX = 1100; - export type UnsavedChangesModalProps = { showModal: boolean; onHide: () => void; @@ -31,7 +27,6 @@ export type UnsavedChangesModalProps = { onConfirmNavigation: () => void; title?: string; body?: string; - zIndex?: number; }; export const UnsavedChangesModal: FC<UnsavedChangesModalProps> = ({ @@ -41,7 +36,6 @@ export const UnsavedChangesModal: FC<UnsavedChangesModalProps> = ({ onConfirmNavigation, title = 'Unsaved Changes', body = "If you don't save, changes will be lost.", - zIndex = UNSAVED_CHANGES_MODAL_Z_INDEX, }: UnsavedChangesModalProps): ReactElement => ( <Modal centered @@ -49,7 +43,6 @@ export const UnsavedChangesModal: FC<UnsavedChangesModalProps> = ({ onHide={onHide} show={showModal} width="444px" - zIndex={zIndex} title={ <> <Icons.WarningOutlined iconSize="m" style={{ marginRight: 8 }} />
