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}
|
onClickOutside={handleClickOutside}
|
||||||
|
isDropdownInModal
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -69,6 +69,7 @@ export const SubMatchingSelectRowRightDropdown = <T extends string>({
|
|||||||
onOptionSelected={handleSelect}
|
onOptionSelected={handleSelect}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
isDropdownInModal
|
||||||
/>
|
/>
|
||||||
</StyledDropdownContainer>
|
</StyledDropdownContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,9 +15,10 @@
|
|||||||
export enum RootStackingContextZIndices {
|
export enum RootStackingContextZIndices {
|
||||||
CommandMenu = 21,
|
CommandMenu = 21,
|
||||||
CommandMenuButton = 22,
|
CommandMenuButton = 22,
|
||||||
|
DropdownPortalBelowModal = 38,
|
||||||
RootModalBackDrop = 39,
|
RootModalBackDrop = 39,
|
||||||
RootModal = 40,
|
RootModal = 40,
|
||||||
DropdownPortal = 50,
|
DropdownPortalAboveModal = 50,
|
||||||
Dialog = 9999,
|
Dialog = 9999,
|
||||||
SnackBar = 10002,
|
SnackBar = 10002,
|
||||||
NotFound = 10001,
|
NotFound = 10001,
|
||||||
|
|||||||
@ -58,6 +58,7 @@ export type DropdownProps = {
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onOpen?: () => void;
|
onOpen?: () => void;
|
||||||
excludedClickOutsideIds?: string[];
|
excludedClickOutsideIds?: string[];
|
||||||
|
isDropdownInModal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Dropdown = ({
|
export const Dropdown = ({
|
||||||
@ -74,6 +75,7 @@ export const Dropdown = ({
|
|||||||
onOpen,
|
onOpen,
|
||||||
clickableComponentWidth = 'auto',
|
clickableComponentWidth = 'auto',
|
||||||
excludedClickOutsideIds,
|
excludedClickOutsideIds,
|
||||||
|
isDropdownInModal = false,
|
||||||
}: DropdownProps) => {
|
}: DropdownProps) => {
|
||||||
const { isDropdownOpen, toggleDropdown } = useDropdown(dropdownId);
|
const { isDropdownOpen, toggleDropdown } = useDropdown(dropdownId);
|
||||||
|
|
||||||
@ -193,6 +195,7 @@ export const Dropdown = ({
|
|||||||
onClickOutside={onClickOutside}
|
onClickOutside={onClickOutside}
|
||||||
onHotkeyTriggered={toggleDropdown}
|
onHotkeyTriggered={toggleDropdown}
|
||||||
excludedClickOutsideIds={excludedClickOutsideIds}
|
excludedClickOutsideIds={excludedClickOutsideIds}
|
||||||
|
isDropdownInModal={isDropdownInModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<DropdownOnToggleEffect
|
<DropdownOnToggleEffect
|
||||||
|
|||||||
@ -7,6 +7,14 @@ import { useState } from 'react';
|
|||||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||||
|
|
||||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
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 { Avatar, IconChevronLeft } from 'twenty-ui/display';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
import {
|
import {
|
||||||
@ -15,6 +23,8 @@ import {
|
|||||||
MenuItemSelectAvatar,
|
MenuItemSelectAvatar,
|
||||||
} from 'twenty-ui/navigation';
|
} from 'twenty-ui/navigation';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
|
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
||||||
import { Dropdown } from '../Dropdown';
|
import { Dropdown } from '../Dropdown';
|
||||||
import { DropdownMenuHeader } from '../DropdownMenuHeader/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '../DropdownMenuHeader/DropdownMenuHeader';
|
||||||
import { DropdownMenuInput } from '../DropdownMenuInput';
|
import { DropdownMenuInput } from '../DropdownMenuInput';
|
||||||
@ -321,3 +331,96 @@ export const CheckableMenuItemWithAvatar: Story = {
|
|||||||
},
|
},
|
||||||
play: playInteraction,
|
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 { useRecoilValue } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
export const StyledDropdownContentContainer = styled.div`
|
export const StyledDropdownContentContainer = styled.div<{
|
||||||
|
isDropdownInModal?: boolean;
|
||||||
|
}>`
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: ${RootStackingContextZIndices.DropdownPortal};
|
z-index: ${({ isDropdownInModal }) =>
|
||||||
|
isDropdownInModal
|
||||||
|
? RootStackingContextZIndices.DropdownPortalAboveModal
|
||||||
|
: RootStackingContextZIndices.DropdownPortalBelowModal};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDropdownInsideContainer = styled.div`
|
const StyledDropdownInsideContainer = styled.div`
|
||||||
@ -50,6 +55,7 @@ export type DropdownInternalContainerProps = {
|
|||||||
dropdownComponents: React.ReactNode;
|
dropdownComponents: React.ReactNode;
|
||||||
parentDropdownId?: string;
|
parentDropdownId?: string;
|
||||||
excludedClickOutsideIds?: string[];
|
excludedClickOutsideIds?: string[];
|
||||||
|
isDropdownInModal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DropdownInternalContainer = ({
|
export const DropdownInternalContainer = ({
|
||||||
@ -63,6 +69,7 @@ export const DropdownInternalContainer = ({
|
|||||||
onHotkeyTriggered,
|
onHotkeyTriggered,
|
||||||
dropdownComponents,
|
dropdownComponents,
|
||||||
excludedClickOutsideIds,
|
excludedClickOutsideIds,
|
||||||
|
isDropdownInModal = false,
|
||||||
}: DropdownInternalContainerProps) => {
|
}: DropdownInternalContainerProps) => {
|
||||||
const { isDropdownOpen, closeDropdown, setDropdownPlacement } =
|
const { isDropdownOpen, closeDropdown, setDropdownPlacement } =
|
||||||
useDropdown(dropdownId);
|
useDropdown(dropdownId);
|
||||||
@ -140,6 +147,7 @@ export const DropdownInternalContainer = ({
|
|||||||
role="listbox"
|
role="listbox"
|
||||||
id={`${dropdownId}-options`}
|
id={`${dropdownId}-options`}
|
||||||
data-click-outside-id={excludedClickOutsideId}
|
data-click-outside-id={excludedClickOutsideId}
|
||||||
|
isDropdownInModal={isDropdownInModal}
|
||||||
>
|
>
|
||||||
<OverlayContainer>
|
<OverlayContainer>
|
||||||
<StyledDropdownInsideContainer id={dropdownId} data-select-disable>
|
<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 { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||||
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
|
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
|
||||||
|
import { SetRecoilState } from 'recoil';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
||||||
@ -13,7 +14,7 @@ import { sleep } from '~/utils/sleep';
|
|||||||
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
|
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
|
||||||
import { ConfirmationModal } from '../ConfirmationModal';
|
import { ConfirmationModal } from '../ConfirmationModal';
|
||||||
|
|
||||||
const initializeState = ({ set }: { set: (atom: any, value: any) => void }) => {
|
const initializeState = ({ set }: { set: SetRecoilState }) => {
|
||||||
set(
|
set(
|
||||||
isModalOpenedComponentState.atomFamily({
|
isModalOpenedComponentState.atomFamily({
|
||||||
instanceId: 'confirmation-modal',
|
instanceId: 'confirmation-modal',
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { focusStackState } from '@/ui/utilities/focus/states/focusStackState';
|
|||||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||||
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
|
import { internalHotkeysEnabledScopesState } from '@/ui/utilities/hotkey/states/internal/internalHotkeysEnabledScopesState';
|
||||||
|
import { SetRecoilState } from 'recoil';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
import { RootDecorator } from '~/testing/decorators/RootDecorator';
|
||||||
@ -13,7 +14,7 @@ import { sleep } from '~/utils/sleep';
|
|||||||
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
|
import { isModalOpenedComponentState } from '../../states/isModalOpenedComponentState';
|
||||||
import { Modal } from '../Modal';
|
import { Modal } from '../Modal';
|
||||||
|
|
||||||
const initializeState = ({ set }: { set: (atom: any, value: any) => void }) => {
|
const initializeState = ({ set }: { set: SetRecoilState }) => {
|
||||||
set(
|
set(
|
||||||
isModalOpenedComponentState.atomFamily({
|
isModalOpenedComponentState.atomFamily({
|
||||||
instanceId: 'modal-id',
|
instanceId: 'modal-id',
|
||||||
|
|||||||
Reference in New Issue
Block a user