Fix dropdown z index (#12442)
Fixes https://github.com/twentyhq/twenty/issues/11942 This PR creates two different dropdown z indexes, one for normal dropdowns, and one for the dropdowns inside modals.
This commit is contained in:
@ -137,6 +137,7 @@ export const MatchColumnToFieldSelect = ({
|
||||
)
|
||||
}
|
||||
onClickOutside={handleClickOutside}
|
||||
isDropdownInModal
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -69,6 +69,7 @@ export const SubMatchingSelectRowRightDropdown = <T extends string>({
|
||||
onOptionSelected={handleSelect}
|
||||
/>
|
||||
}
|
||||
isDropdownInModal
|
||||
/>
|
||||
</StyledDropdownContainer>
|
||||
);
|
||||
|
||||
@ -15,9 +15,10 @@
|
||||
export enum RootStackingContextZIndices {
|
||||
CommandMenu = 21,
|
||||
CommandMenuButton = 22,
|
||||
DropdownPortalBelowModal = 38,
|
||||
RootModalBackDrop = 39,
|
||||
RootModal = 40,
|
||||
DropdownPortal = 50,
|
||||
DropdownPortalAboveModal = 50,
|
||||
Dialog = 9999,
|
||||
SnackBar = 10002,
|
||||
NotFound = 10001,
|
||||
|
||||
@ -58,6 +58,7 @@ export type DropdownProps = {
|
||||
onClose?: () => void;
|
||||
onOpen?: () => void;
|
||||
excludedClickOutsideIds?: string[];
|
||||
isDropdownInModal?: boolean;
|
||||
};
|
||||
|
||||
export const Dropdown = ({
|
||||
@ -74,6 +75,7 @@ export const Dropdown = ({
|
||||
onOpen,
|
||||
clickableComponentWidth = 'auto',
|
||||
excludedClickOutsideIds,
|
||||
isDropdownInModal = false,
|
||||
}: DropdownProps) => {
|
||||
const { isDropdownOpen, toggleDropdown } = useDropdown(dropdownId);
|
||||
|
||||
@ -193,6 +195,7 @@ export const Dropdown = ({
|
||||
onClickOutside={onClickOutside}
|
||||
onHotkeyTriggered={toggleDropdown}
|
||||
excludedClickOutsideIds={excludedClickOutsideIds}
|
||||
isDropdownInModal={isDropdownInModal}
|
||||
/>
|
||||
)}
|
||||
<DropdownOnToggleEffect
|
||||
|
||||
@ -7,6 +7,14 @@ import { useState } from 'react';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { ModalHotkeyScope } from '@/ui/layout/modal/components/types/ModalHotkeyScope';
|
||||
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
|
||||
import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
|
||||
import { SetRecoilState } from 'recoil';
|
||||
import { Avatar, IconChevronLeft } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import {
|
||||
@ -15,6 +23,8 @@ import {
|
||||
MenuItemSelectAvatar,
|
||||
} from 'twenty-ui/navigation';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
||||
import { Dropdown } from '../Dropdown';
|
||||
import { DropdownMenuHeader } from '../DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuInput } from '../DropdownMenuInput';
|
||||
@ -321,3 +331,96 @@ export const CheckableMenuItemWithAvatar: Story = {
|
||||
},
|
||||
play: playInteraction,
|
||||
};
|
||||
|
||||
const modalId = 'dropdown-modal-test';
|
||||
|
||||
const ModalWithDropdown = () => {
|
||||
return (
|
||||
<>
|
||||
<Modal modalId={modalId} size="medium" padding="medium" isClosable={true}>
|
||||
<Modal.Header>Modal with Dropdown Test</Modal.Header>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
This modal contains a dropdown that should appear above the modal
|
||||
(higher z-index).
|
||||
</p>
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<Dropdown
|
||||
clickableComponent={
|
||||
<Button
|
||||
dataTestId="dropdown-button"
|
||||
title="Open Dropdown in Modal"
|
||||
/>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: 'modal-dropdown' }}
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
dropdownId="modal-dropdown-test"
|
||||
isDropdownInModal={true}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<div data-testid="dropdown-content">
|
||||
<FakeSelectableMenuItemList hasAvatar />
|
||||
</div>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const initializeModalState = ({ set }: { set: SetRecoilState }) => {
|
||||
set(
|
||||
isModalOpenedComponentState.atomFamily({
|
||||
instanceId: modalId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
set(currentHotkeyScopeState, {
|
||||
scope: ModalHotkeyScope.ModalFocus,
|
||||
customScopes: {
|
||||
commandMenu: true,
|
||||
goto: false,
|
||||
keyboardShortcutMenu: false,
|
||||
},
|
||||
});
|
||||
|
||||
set(internalHotkeysEnabledScopesState, [ModalHotkeyScope.ModalFocus]);
|
||||
|
||||
set(focusStackState, [
|
||||
{
|
||||
focusId: modalId,
|
||||
componentInstance: {
|
||||
componentType: FocusComponentType.MODAL,
|
||||
componentInstanceId: modalId,
|
||||
},
|
||||
globalHotkeysConfig: {
|
||||
enableGlobalHotkeysWithModifiers: true,
|
||||
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
export const DropdownInsideModal: Story = {
|
||||
decorators: [I18nFrontDecorator, RootDecorator, ComponentDecorator],
|
||||
parameters: {
|
||||
initializeState: initializeModalState,
|
||||
disableHotkeyInitialization: true,
|
||||
},
|
||||
render: () => <ModalWithDropdown />,
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
|
||||
const dropdownButton = await canvas.findByTestId('dropdown-button');
|
||||
|
||||
await userEvent.click(dropdownButton);
|
||||
|
||||
const dropdownContent = await canvas.findByTestId('dropdown-content');
|
||||
|
||||
expect(dropdownContent).toBeVisible();
|
||||
},
|
||||
};
|
||||
|
||||
@ -22,9 +22,14 @@ import { Keys } from 'react-hotkeys-hook';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
export const StyledDropdownContentContainer = styled.div`
|
||||
export const StyledDropdownContentContainer = styled.div<{
|
||||
isDropdownInModal?: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
z-index: ${RootStackingContextZIndices.DropdownPortal};
|
||||
z-index: ${({ isDropdownInModal }) =>
|
||||
isDropdownInModal
|
||||
? RootStackingContextZIndices.DropdownPortalAboveModal
|
||||
: RootStackingContextZIndices.DropdownPortalBelowModal};
|
||||
`;
|
||||
|
||||
const StyledDropdownInsideContainer = styled.div`
|
||||
@ -50,6 +55,7 @@ export type DropdownInternalContainerProps = {
|
||||
dropdownComponents: React.ReactNode;
|
||||
parentDropdownId?: string;
|
||||
excludedClickOutsideIds?: string[];
|
||||
isDropdownInModal?: boolean;
|
||||
};
|
||||
|
||||
export const DropdownInternalContainer = ({
|
||||
@ -63,6 +69,7 @@ export const DropdownInternalContainer = ({
|
||||
onHotkeyTriggered,
|
||||
dropdownComponents,
|
||||
excludedClickOutsideIds,
|
||||
isDropdownInModal = false,
|
||||
}: DropdownInternalContainerProps) => {
|
||||
const { isDropdownOpen, closeDropdown, setDropdownPlacement } =
|
||||
useDropdown(dropdownId);
|
||||
@ -140,6 +147,7 @@ export const DropdownInternalContainer = ({
|
||||
role="listbox"
|
||||
id={`${dropdownId}-options`}
|
||||
data-click-outside-id={excludedClickOutsideId}
|
||||
isDropdownInModal={isDropdownInModal}
|
||||
>
|
||||
<OverlayContainer>
|
||||
<StyledDropdownInsideContainer id={dropdownId} data-select-disable>
|
||||
|
||||
@ -6,6 +6,7 @@ import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
|
||||
import { SetRecoilState } from 'recoil';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
||||
@ -13,7 +14,7 @@ import { sleep } from '~/utils/sleep';
|
||||
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
|
||||
import { ConfirmationModal } from '../ConfirmationModal';
|
||||
|
||||
const initializeState = ({ set }: { set: (atom: any, value: any) => void }) => {
|
||||
const initializeState = ({ set }: { set: SetRecoilState }) => {
|
||||
set(
|
||||
isModalOpenedComponentState.atomFamily({
|
||||
instanceId: 'confirmation-modal',
|
||||
|
||||
@ -6,6 +6,7 @@ import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
|
||||
import { SetRecoilState } from 'recoil';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
||||
@ -13,7 +14,7 @@ import { sleep } from '~/utils/sleep';
|
||||
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
|
||||
import { Modal } from '../Modal';
|
||||
|
||||
const initializeState = ({ set }: { set: (atom: any, value: any) => void }) => {
|
||||
const initializeState = ({ set }: { set: SetRecoilState }) => {
|
||||
set(
|
||||
isModalOpenedComponentState.atomFamily({
|
||||
instanceId: 'modal-id',
|
||||
|
||||
Reference in New Issue
Block a user