Refactored dropdown content and fixed all dropdown width bugs (#12334)

This PR refactors all the dropdown content wrapping mechanism across the
entire app.

It refactors the internals of the `Dropdown` component and introduces a
new generic `DropdownContent` component that is a generic wrapper used
for each dropdown.

## Why this PR ?

Because we’ve been experiencing continuous regressions for months on the
dropdown content width, with weird scrolling behaviors in some and not
in others, and every time a solution was found for a particular set of
dropdowns, it broke another set of dropdowns, which wasn’t noticed
because doing the QA of all dropdowns of the app is very difficult for
fixing an apparently small bug.

## Don’t we already have a `DropdownMenu` component ?

Indeed, this new `DropdownContent` is almost like `DropdownMenu` and
took inspiration from it but `DropdownContent` acts as a generic content
container that sets the width of the whole dropdown, whether we have a
menu or not.

## Why don’t we put it directly in Dropdown internals ?

Because the Dropdown component is using a complex logic with floating-ui
middleware to compute its position and size, and for this logic to work
correctly, it cannot be responsible for the “wanted” width of its
content, because the children components, which the dropdown is not
aware of, can request different widths after the dropdown has been
mounted.

A good example with multiple use cases inside the same dropdown can be
found in `AdvancedFilterDropdownFilterInput`

Thus, it is the responsibility of the content of the dropdown to
determine the width it wants to have.

## What is the difference with DropdownMenuItemsContainer ?

We can have multiple `DropdownMenuItemsContainer` in a dropdown,
alongside other components like `DropdownMenuSeparator` or
`DropdownMenuHeader`, and each of those components behaves differently
regarding to its width, paddings, etc. Therefore it is logical that the
`DropdownMenuItemsContainer` cannot be responsible for the whole
dropdown content width, and trying to do so has been the cause of many
regressions for months.

Now `DropdownMenuItemsContainer` is taking a width of `auto` by default,
which is the best to adapt to a parent which has a defined width.

## How do I set the width of my dropdown now ?

By passing a pixel width to the props `widthInPixels` of
`DropdownContent`, which only accepts numbers to avoid any confusion
with `auto` , `100%` or `160px` and other specific width variables.

The `dropdownWidth` props has been removed from `<Dropdown>` to avoid
any confusion.

Also the `DropdownMenuItemsContainer` is now using `auto` as its default
width to fill the available space inside `DropdownContent` .

It is highly recommended to use the enum `GenericDropdownContentWidt` to
define your width.

## Where to use this new `DropdownContent` component ?

There are two main use cases.

If the dropdown content is defined directly inline in the Dropdown
props, then it is recommended to use it here too.

On the other hand if the dropdown content is abstracted in another
component, it’s recommended to use this new component alongside the
others components like `DropdownMenuItemsContainer`.

A good rule of thumb is to place `DropdownContent` where
`DropdownMenuItemsContainer`, `DropdownMenuSearchInput`, etc. are
placed.

## What if I have a custom width ?

Just define a constant like `ICON_PICKER_DROPDOWN_CONTENT_WIDTH` and use
it with the props `widthInPixels` .

Otherwise there’s a `GenericDropdownContentWidth` enum. The default
value being `GenericDropdownContentWidth.Medium` (or 200px), which most
dropdowns use.

## QA 


Component | Comment
-- | --
AttachmentDropdown | Fixed overflowing (thanks to DropdownContent)
RecordIndexActionMenuDropdown |  
CommandMenuActionMenuDropdown |  
SupportDropdown | Fixed overflowing (thanks to DropdownContent)
MessageThreadSubscribersDropdownButton | Removed because unused
FavoriteFolderNavigationDrawerItemDropdown | Set width at Narrow
FavoriteFolderPicker |  
ViewPickerOptionDropdown |  
PageFavoriteFolderDropdown | Removed because unused
AdvancedFilterAddFilterRuleSelect |  
AdvancedFilterAddFilterRuleSelect |  
AdvancedFilterFieldSelectMenu |  
AdvancedFilterRecordFilterGroupOptionsDropdown |  
AdvancedFilterRecordFilterOperanceSelect | Set width at Narrow
AdvancedFilterLogicalOperatorDropdown | Set width at Narrow
AdvancedFilterRecordFilterOptionsDropdown |  
AdvancedFilterRootRecordFilterGroup | Fixed broken horizontal scrolling
behavior
AdvancedFilterSubFieldSelectMenu |  
AdvancedFilterDropdownFilterInput |  
ObjectFilterDropdownBooleanSelect |  
ObjectFilterDropdownCountrySelect | Fixed broken menu items container
ObjectFilterDropdownCurrencySelect | Set width to Large
ObjectFilterDropdownFilterInput |  
ObjectFilterDropdownOperandDropdown | Fixed width that was not fixed
ObjectFilterDropdownFilterInput | Fixed width that wasn’t the same for
EditableFilterChip
ObjectFilterDropdownOperandSelect | Refactored
ObjectOptionsDropdownRecordGroupFieldsContent | Added missing separator
ObjectOptionDropdownFieldsContent |  
ObjectOptionsDropdownHiddenFieldsContent |  
ObjectOptionsDropdownLayoutContent |  
ObjectOptionsDropdownLayoutOpenInContent |  
ObjectOptionsDropdownMenuContent |  
ObjectOptionsDropdownRecordGroupFieldsContent |  
ObjectOptionsDropdownRecordGroupsContent |  
ObjectOptionsDropdownRecordGroupSortContent |  
ObjectOptionsDropdownHiddenRecordGroupsContent | Removed unnecessary
DropdownMenuItemsContainer
RecordBoardColumnHeaderAggregateDropdown | Fixed overflowing (thanks to
DropdownContent)
RecordBoardColumnHeaderAggregateDropdownFieldsContent | Fixed
overflowing (thanks to DropdownContent)
RecordBoardColumnHeaderAggregateDropdownMenuContent | Fixed overflowing
(thanks to DropdownContent)
RecordBoardColumnHeaderAggregateDropdownOptionsContent | Fixed
overflowing (thanks to DropdownContent)
MultiItemFieldInput | Fixed overflowing (thanks to DropdownContent)
MultiItemFieldMenuItem |  
MultipleRecordPicker | Fixed overflowing (thanks to DropdownContent)
SingleRecordPicker |  
RecordTableColumnAggregateDropdownSubmenuContent |  
RecordTableColumnAggregateFooterMenuContent |  
RecordTableColumnHeadDropdownMenu | Fixed overflowing (thanks to
DropdownContent)
RecordTableHeaderPlusButtonContent |  
MultipleSelectDropdown | Broken width fixed
ObjectSortDropdownButton |  
RecordDetailRelationRecordsListItem |  
ConfigVariableDatabaseInput |  
ConfigVariableOptionsDropdownContent |  
SettingsObjectFieldActiveActionDropdown | Fixed overflowing (thanks to
DropdownContent)
SettingsObjectFieldDisabledActionDropdown | Set width at Narrow
SettingsObjectSummaryCard | Removed because unused
SettingsDataModelFieldSelectFormOptionRow |  
SettingsDataModelNewFieldBreadcrumbDropdown |  
SettingsObjectInactiveMenuDropDown |  
SettingsRoleAssignementWorkspaceMemberPickerDropdown |  
SettingsRolePermissionObjectLevelObjectPickerDropdownContent |  
SettingsSecurityApprovedAccessDomainRowDropdownMenu | Couldn’t test
SettingsSecuritySSORowDropdownMenu | Couldn’t test
SettingsAccountsRowDropdownMenu | Fixed overflowing (thanks to
DropdownContent)
SettingsIntegrationDatabaseConnectionSummaryCard | Couldn’t test
SettingsServerlessFunctionTablEnvironmentVariableTableRow | Deactivated
scope
MatchColumnSelectFieldSelectDropdownContent | Removed now unnecessary
width on DropdownMenuItemsContainer
MatchColumnSelectSubFieldSelectDropdownContent |  
SubMatchingSelectInput |  
CurrencyPickerDropdownSelect |  
IconPicker | Fixed overflowing (thanks to DropdownContent)
PhoneCountryPickerDropdownSelect |  
Select | Refactored to drilldown wanted width of content, in this case
it’s intended
ExpandedListDropdown |  
ShowPageAddButton | Removed because unused
MultiWorkspaceDropdownDefaultComponent |  
MultiWorkspaceDropdownThemesComponent |  
MultiWorkspaceDropdownWorkspacesListComponent |  
AdvancedFilterDropdownButton |  
EditableFilterChip |  
EditableFilterDropdownButton |  
UpdateViewButtonGroup |  
ViewBarFilterDropdown |  
ViewBarFilterDropdownFieldSelectMenu |  
ViewPickerContentCreateMode |  
ViewPickerContentEditMode |  
ViewPickerListContent |  
WorkflowEditTriggerDatabaseEventForm |  
WorkflowVariablesDropdownFieldItems |  
WorkflowVariablesDropdownObjectItems |  
WorkflowVariablesDropdownWorkflowStepItems |  
CommandMenuContextChipGroups |  
RecordBoardColumnDropdownMenu |  
MultiSelectInput |  
SelectInput |  
CustomSlashMenu |  
DropdownMenu | Removed and replaced by DropdownContent
OverlayContainer and around |  


<!-- notionvc: 1e23bdb8-2dda-4f8d-a64d-ecc829a768a2 -->

## Miscellaneous 

Side notes : 

- The `Select` component is now wrapping the `DropdownContent` because
it computes a dynamic width.
- The advanced filter dropdown has been fixed, it was broken when
resizing the window horizontally, we couldn’t scroll. This specific edge
case was taken into account when refactoring the whole dropdown content
system
- As discussed with Nitin, data-select-disable will probably be removed
entirely, so I let it as is, because right now it is not used by the
refactored d&d selection.
- Duplicate separators under DropdownMenuHeader have been removed.

Fixes : https://github.com/twentyhq/twenty/issues/12327
Fixes : https://github.com/twentyhq/core-team-issues/issues/951
This commit is contained in:
Lucas Bordeau
2025-05-27 19:44:13 +02:00
committed by GitHub
parent 5ce462b17e
commit 01b40e173b
116 changed files with 1243 additions and 1755 deletions

View File

@ -2,12 +2,12 @@ import { useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -102,7 +102,7 @@ export const MultiSelectInput = ({
selectableItemIdArray={optionIds}
hotkeyScope={hotkeyScope}
>
<DropdownMenu data-select-disable ref={containerRef}>
<DropdownContent ref={containerRef} selectDisabled>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(event) =>
@ -130,7 +130,7 @@ export const MultiSelectInput = ({
);
})}
</DropdownMenuItemsContainer>
</DropdownMenu>
</DropdownContent>
</SelectableList>
);
};

View File

@ -2,7 +2,6 @@ import styled from '@emotion/styled';
import { useMemo, useState } from 'react';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
@ -11,6 +10,8 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { arrayToChunks } from '~/utils/array/arrayToChunks';
import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -179,14 +180,13 @@ export const IconPicker = ({
size={size}
/>
}
dropdownWidth={176}
dropdownComponents={
<SelectableList
selectableListInstanceId="icon-list"
selectableItemIdMatrix={iconKeys2d}
hotkeyScope={IconPickerHotkeyScope.IconPicker}
>
<DropdownMenu width={176}>
<DropdownContent widthInPixels={ICON_PICKER_DROPDOWN_CONTENT_WIDTH}>
<SelectableList
selectableListInstanceId="icon-list"
selectableItemIdMatrix={iconKeys2d}
hotkeyScope={IconPickerHotkeyScope.IconPicker}
>
<DropdownMenuSearchInput
placeholder={t`Search icon`}
autoFocus
@ -220,8 +220,8 @@ export const IconPicker = ({
</StyledMenuIconItemsContainer>
</DropdownMenuItemsContainer>
</div>
</DropdownMenu>
</SelectableList>
</SelectableList>
</DropdownContent>
}
onClickOutside={onClickOutside}
onClose={() => {

View File

@ -8,6 +8,8 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@ -67,7 +69,7 @@ export const Select = <Value extends SelectValue>({
disabled: disabledFromProps,
selectSizeVariant,
dropdownId,
dropdownWidth = 176,
dropdownWidth = GenericDropdownContentWidth.Medium,
dropdownWidthAuto = false,
emptyOption,
fullWidth,
@ -139,7 +141,6 @@ export const Select = <Value extends SelectValue>({
) : (
<Dropdown
dropdownId={dropdownId}
dropdownWidth={dropDownMenuWidth}
dropdownPlacement="bottom-start"
dropdownOffset={dropdownOffset}
clickableComponent={
@ -151,7 +152,7 @@ export const Select = <Value extends SelectValue>({
/>
}
dropdownComponents={
<>
<DropdownContent widthInPixels={dropDownMenuWidth}>
{!!withSearchInput && (
<DropdownMenuSearchInput
autoFocus
@ -163,7 +164,7 @@ export const Select = <Value extends SelectValue>({
<DropdownMenuSeparator />
)}
{!!filteredOptions.length && (
<DropdownMenuItemsContainer hasMaxHeight width={'auto'}>
<DropdownMenuItemsContainer hasMaxHeight>
<SelectableList
hotkeyScope={SelectHotkeyScope.Select}
selectableListInstanceId={dropdownId}
@ -208,7 +209,7 @@ export const Select = <Value extends SelectValue>({
/>
</DropdownMenuItemsContainer>
)}
</>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
/>

View File

@ -1,4 +1,4 @@
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
@ -96,7 +96,7 @@ export const SelectInput = ({
);
return (
<DropdownMenu ref={containerRef} data-select-disable>
<DropdownContent ref={containerRef} selectDisabled>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
@ -129,6 +129,6 @@ export const SelectInput = ({
);
})}
</DropdownMenuItemsContainer>
</DropdownMenu>
</DropdownContent>
);
};

View File

@ -0,0 +1 @@
export const ICON_PICKER_DROPDOWN_CONTENT_WIDTH = 176;

View File

@ -1,12 +1,12 @@
import { useMemo, useState } from 'react';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { CURRENCIES } from '@/settings/data-model/constants/Currencies';
import { Currency } from '@/ui/input/components/internal/types/Currency';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { MenuItem, MenuItemSelectAvatar } from 'twenty-ui/navigation';
export const CurrencyPickerDropdownSelect = ({
@ -31,7 +31,7 @@ export const CurrencyPickerDropdownSelect = ({
);
return (
<DropdownMenu>
<DropdownContent>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(event) => setSearchFilter(event.target.value)}
@ -65,6 +65,6 @@ export const CurrencyPickerDropdownSelect = ({
</>
)}
</DropdownMenuItemsContainer>
</DropdownMenu>
</DropdownContent>
);
};

View File

@ -6,6 +6,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import 'react-phone-number-input/style.css';
import { MenuItem, MenuItemSelectAvatar } from 'twenty-ui/navigation';
@ -46,7 +47,7 @@ export const PhoneCountryPickerDropdownSelect = ({
);
return (
<>
<DropdownContent>
<DropdownMenuSearchInput
value={searchFilter}
onChange={(event) => setSearchFilter(event.currentTarget.value)}
@ -90,6 +91,6 @@ export const PhoneCountryPickerDropdownSelect = ({
</>
)}
</DropdownMenuItemsContainer>
</>
</DropdownContent>
);
};

View File

@ -1,7 +1,7 @@
import { SuggestionMenuProps } from '@blocknote/react';
import styled from '@emotion/styled';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { autoUpdate, useFloating } from '@floating-ui/react';
@ -46,7 +46,7 @@ export const CustomSlashMenu = (props: CustomSlashMenuProps) => {
>
<OverlayContainer ref={refs.setFloating} style={floatingStyles}>
<StyledInnerContainer>
<DropdownMenu style={{ zIndex: 2001 }}>
<DropdownContent>
<DropdownMenuItemsContainer>
{props.items.map((item, index) => (
<MenuItemSuggestion
@ -58,7 +58,7 @@ export const CustomSlashMenu = (props: CustomSlashMenuProps) => {
/>
))}
</DropdownMenuItemsContainer>
</DropdownMenu>
</DropdownContent>
</StyledInnerContainer>
</OverlayContainer>
</motion.div>,

View File

@ -1,5 +1,5 @@
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownOnToggleEffect } from '@/ui/layout/dropdown/components/DropdownOnToggleEffect';
import { DropdownInternalContainer } from '@/ui/layout/dropdown/components/internal/DropdownInternalContainer';
import { DROPDOWN_RESIZE_MIN_HEIGHT } from '@/ui/layout/dropdown/constants/DropdownResizeMinHeight';
import { DROPDOWN_RESIZE_MIN_WIDTH } from '@/ui/layout/dropdown/constants/DropdownResizeMinWidth';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext';
@ -52,18 +52,17 @@ export type DropdownProps = {
dropdownHotkeyScope: HotkeyScope;
dropdownId: string;
dropdownPlacement?: Placement;
dropdownWidth?: Width;
dropdownOffset?: DropdownOffset;
dropdownStrategy?: 'fixed' | 'absolute';
onClickOutside?: () => void;
onClose?: () => void;
onOpen?: () => void;
excludedClickOutsideIds?: string[];
};
export const Dropdown = ({
clickableComponent,
dropdownComponents,
dropdownWidth,
hotkey,
dropdownId,
dropdownHotkeyScope,
@ -74,6 +73,7 @@ export const Dropdown = ({
onClose,
onOpen,
clickableComponentWidth = 'auto',
excludedClickOutsideIds,
}: DropdownProps) => {
const { isDropdownOpen, toggleDropdown } = useDropdown(dropdownId);
@ -182,9 +182,8 @@ export const Dropdown = ({
<StyledDropdownFallbackAnchor ref={refs.setReference} />
)}
{isDropdownOpen && (
<DropdownContent
<DropdownInternalContainer
floatingStyles={floatingStyles}
dropdownWidth={dropdownWidth}
dropdownComponents={dropdownComponents}
dropdownId={dropdownId}
dropdownPlacement={placement}
@ -193,6 +192,7 @@ export const Dropdown = ({
hotkey={hotkey}
onClickOutside={onClickOutside}
onHotkeyTriggered={toggleDropdown}
excludedClickOutsideIds={excludedClickOutsideIds}
/>
)}
<DropdownOnToggleEffect

View File

@ -1,149 +1,37 @@
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useInternalHotkeyScopeManagement } from '@/ui/layout/dropdown/hooks/useInternalHotkeyScopeManagement';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { dropdownMaxHeightComponentState } from '@/ui/layout/dropdown/states/internal/dropdownMaxHeightComponentState';
import { dropdownMaxWidthComponentState } from '@/ui/layout/dropdown/states/internal/dropdownMaxWidthComponentState';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import styled from '@emotion/styled';
import {
FloatingPortal,
Placement,
UseFloatingReturn,
} from '@floating-ui/react';
import { useContext, useEffect } from 'react';
import { Keys } from 'react-hotkeys-hook';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { Ref, forwardRef } from 'react';
export const StyledDropdownContentContainer = styled.div`
const StyledInternalBaseDropdownContent = styled.div<{
widthInPixels: number;
}>`
display: flex;
z-index: ${RootStackingContextZIndices.DropdownPortal};
flex-direction: column;
height: 100%;
width: ${({ widthInPixels }) => widthInPixels}px;
`;
export type DropdownContentProps = {
dropdownId: string;
dropdownPlacement: Placement;
floatingUiRefs: UseFloatingReturn['refs'];
onClickOutside?: () => void;
hotkeyScope: HotkeyScope;
floatingStyles: UseFloatingReturn['floatingStyles'];
hotkey?: {
key: Keys;
scope: string;
};
onHotkeyTriggered?: () => void;
dropdownWidth?: `${string}px` | `${number}%` | 'auto' | number;
dropdownComponents: React.ReactNode;
parentDropdownId?: string;
};
export const DropdownContent = ({
dropdownId,
dropdownPlacement,
floatingUiRefs,
onClickOutside,
hotkeyScope,
floatingStyles,
hotkey,
onHotkeyTriggered,
dropdownWidth,
dropdownComponents,
}: DropdownContentProps) => {
const { isDropdownOpen, closeDropdown, setDropdownPlacement } =
useDropdown(dropdownId);
const activeDropdownFocusId = useRecoilValue(activeDropdownFocusIdState);
const dropdownMaxHeight = useRecoilComponentValueV2(
dropdownMaxHeightComponentState,
dropdownId,
);
const dropdownMaxWidth = useRecoilComponentValueV2(
dropdownMaxWidthComponentState,
dropdownId,
);
useEffect(() => {
setDropdownPlacement(dropdownPlacement);
}, [dropdownPlacement, setDropdownPlacement]);
useListenClickOutside({
refs: [floatingUiRefs.floating, floatingUiRefs.domReference],
listenerId: dropdownId,
callback: (event) => {
if (activeDropdownFocusId !== dropdownId) return;
if (isDropdownOpen) {
event.stopImmediatePropagation();
event.preventDefault();
closeDropdown();
}
onClickOutside?.();
},
});
useInternalHotkeyScopeManagement({
dropdownScopeId: dropdownId,
dropdownHotkeyScopeFromParent: hotkeyScope,
});
useScopedHotkeys(
[Key.Escape],
() => {
if (activeDropdownFocusId !== dropdownId) return;
if (isDropdownOpen) {
closeDropdown();
}
},
hotkeyScope?.scope,
[closeDropdown, isDropdownOpen],
);
const dropdownMenuStyles = {
...floatingStyles,
maxHeight: dropdownMaxHeight,
maxWidth: dropdownMaxWidth,
};
const { excludedClickOutsideId } = useContext(ClickOutsideListenerContext);
return (
<>
{hotkey && onHotkeyTriggered && (
<HotkeyEffect hotkey={hotkey} onHotkeyTriggered={onHotkeyTriggered} />
)}
<FloatingPortal>
<StyledDropdownContentContainer
ref={floatingUiRefs.setFloating}
style={dropdownMenuStyles}
role="listbox"
id={`${dropdownId}-options`}
data-click-outside-id={excludedClickOutsideId}
>
<OverlayContainer>
<DropdownMenu
id={dropdownId}
width={dropdownWidth}
data-select-disable
>
{dropdownComponents}
</DropdownMenu>
</OverlayContainer>
</StyledDropdownContentContainer>
</FloatingPortal>
</>
);
};
export const DropdownContent = forwardRef(
(
{
children,
widthInPixels = GenericDropdownContentWidth.Medium,
selectDisabled = false,
}: React.PropsWithChildren<{
widthInPixels?: number;
selectDisabled?: boolean;
}>,
ref: Ref<HTMLDivElement>,
) => {
return (
<StyledInternalBaseDropdownContent
data-select-disable={selectDisabled}
widthInPixels={widthInPixels}
ref={ref}
>
{children}
</StyledInternalBaseDropdownContent>
);
},
);

View File

@ -1,19 +0,0 @@
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils';
const StyledDropdownMenu = styled.div<{
width?: `${string}px` | `${number}%` | 'auto' | number;
}>`
display: flex;
flex-direction: column;
height: 100%;
width: ${({ width }) =>
isDefined(width)
? typeof width === 'number'
? `${width}px`
: width
: 'auto'};
`;
export const DropdownMenu = StyledDropdownMenu;

View File

@ -12,6 +12,9 @@ const StyledHeader = styled.li`
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
padding: ${({ theme }) => theme.spacing(1)};
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
height: ${({ theme }) => theme.spacing(6)};
user-select: none;

View File

@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
const StyledDropdownMenuItemsExternalContainer = styled.div<{
hasMaxHeight?: boolean;
width: number | 'auto';
width: number | 'auto' | '100%';
}>`
--padding: ${({ theme }) => theme.spacing(1)};
@ -19,10 +19,13 @@ const StyledDropdownMenuItemsExternalContainer = styled.div<{
padding: var(--padding);
${({ width }) =>
isDefined(width) &&
css`
width: ${width}px;
`}
isDefined(width) && width === '100%'
? css`
width: 100%;
`
: css`
width: ${width}px;
`}
`;
const StyledDropdownMenuItemsInternalContainer = styled.div`
@ -31,6 +34,7 @@ const StyledDropdownMenuItemsInternalContainer = styled.div`
flex-direction: column;
gap: 2px;
height: 100%;
width: 100%;
`;
@ -39,20 +43,18 @@ const StyledScrollWrapper = styled(ScrollWrapper)`
width: 100%;
`;
// TODO: refactor this, the dropdown should handle the max height behavior + scroll with the size middleware
// We should instead create a DropdownMenuItemsContainerScrollable or take for granted that it is the default behavior
export const DropdownMenuItemsContainer = ({
children,
hasMaxHeight,
className,
width = 200,
scrollable = true,
width = 'auto',
}: {
children: React.ReactNode;
hasMaxHeight?: boolean;
className?: string;
scrollable?: boolean;
width?: number | 'auto';
width?: number | 'auto' | '100%';
}) => {
const id = useId();

View File

@ -32,7 +32,6 @@ const meta: Meta<typeof Dropdown> = {
dropdownHotkeyScope: { scope: 'testDropdownMenu' },
dropdownOffset: { x: 0, y: 8 },
dropdownId: 'test-dropdown-id',
dropdownWidth: '200px',
},
argTypes: {
clickableComponent: { control: false },

View File

@ -2,10 +2,10 @@ import { Meta, StoryObj } from '@storybook/react';
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { AVATAR_URL_MOCK, ComponentDecorator } from 'twenty-ui/testing';
import {
Avatar,
IconChevronLeft,
@ -13,6 +13,7 @@ import {
IconPlus,
} from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { AVATAR_URL_MOCK, ComponentDecorator } from 'twenty-ui/testing';
const meta: Meta<typeof DropdownMenuHeader> = {
title: 'UI/Layout/Dropdown/DropdownMenuHeader',
@ -64,9 +65,11 @@ export const ContextDropdownAndAvatar: Story = {
dropdownId={'story-dropdown-id-context-menu'}
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
dropdownComponents={
<DropdownMenuItemsContainer>
<MenuItem LeftIcon={IconPlus} text={`Create Workspace`} />
</DropdownMenuItemsContainer>
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItem LeftIcon={IconPlus} text={`Create Workspace`} />
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
),

View File

@ -0,0 +1,153 @@
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useInternalHotkeyScopeManagement } from '@/ui/layout/dropdown/hooks/useInternalHotkeyScopeManagement';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { dropdownMaxHeightComponentState } from '@/ui/layout/dropdown/states/internal/dropdownMaxHeightComponentState';
import { dropdownMaxWidthComponentState } from '@/ui/layout/dropdown/states/internal/dropdownMaxWidthComponentState';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import {
FloatingPortal,
Placement,
UseFloatingReturn,
} from '@floating-ui/react';
import { useContext, useEffect } from 'react';
import { Keys } from 'react-hotkeys-hook';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
export const StyledDropdownContentContainer = styled.div`
display: flex;
z-index: ${RootStackingContextZIndices.DropdownPortal};
`;
const StyledDropdownInsideContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
`;
export type DropdownInternalContainerProps = {
dropdownId: string;
dropdownPlacement: Placement;
floatingUiRefs: UseFloatingReturn['refs'];
onClickOutside?: () => void;
hotkeyScope: HotkeyScope;
floatingStyles: UseFloatingReturn['floatingStyles'];
hotkey?: {
key: Keys;
scope: string;
};
onHotkeyTriggered?: () => void;
dropdownComponents: React.ReactNode;
parentDropdownId?: string;
excludedClickOutsideIds?: string[];
};
export const DropdownInternalContainer = ({
dropdownId,
dropdownPlacement,
floatingUiRefs,
onClickOutside,
hotkeyScope,
floatingStyles,
hotkey,
onHotkeyTriggered,
dropdownComponents,
excludedClickOutsideIds,
}: DropdownInternalContainerProps) => {
const { isDropdownOpen, closeDropdown, setDropdownPlacement } =
useDropdown(dropdownId);
const activeDropdownFocusId = useRecoilValue(activeDropdownFocusIdState);
const dropdownMaxHeight = useRecoilComponentValueV2(
dropdownMaxHeightComponentState,
dropdownId,
);
const dropdownMaxWidth = useRecoilComponentValueV2(
dropdownMaxWidthComponentState,
dropdownId,
);
useEffect(() => {
setDropdownPlacement(dropdownPlacement);
}, [dropdownPlacement, setDropdownPlacement]);
useListenClickOutside({
refs: [floatingUiRefs.floating, floatingUiRefs.domReference],
listenerId: dropdownId,
callback: (event) => {
if (activeDropdownFocusId !== dropdownId) return;
if (isDropdownOpen) {
event.stopImmediatePropagation();
event.preventDefault();
closeDropdown();
}
onClickOutside?.();
},
excludedClickOutsideIds,
});
useInternalHotkeyScopeManagement({
dropdownScopeId: dropdownId,
dropdownHotkeyScopeFromParent: hotkeyScope,
});
useScopedHotkeys(
[Key.Escape],
() => {
if (activeDropdownFocusId !== dropdownId) return;
if (isDropdownOpen) {
closeDropdown();
}
},
hotkeyScope?.scope,
[closeDropdown, isDropdownOpen],
);
const dropdownMenuStyles = {
...floatingStyles,
maxHeight: dropdownMaxHeight,
maxWidth: dropdownMaxWidth,
};
const { excludedClickOutsideId } = useContext(ClickOutsideListenerContext);
return (
<>
{hotkey && onHotkeyTriggered && (
<HotkeyEffect hotkey={hotkey} onHotkeyTriggered={onHotkeyTriggered} />
)}
<FloatingPortal>
<StyledDropdownContentContainer
ref={floatingUiRefs.setFloating}
style={dropdownMenuStyles}
role="listbox"
id={`${dropdownId}-options`}
data-click-outside-id={excludedClickOutsideId}
>
<OverlayContainer>
<StyledDropdownInsideContainer id={dropdownId} data-select-disable>
{dropdownComponents}
</StyledDropdownInsideContainer>
</OverlayContainer>
</StyledDropdownContentContainer>
</FloatingPortal>
</>
);
};

View File

@ -0,0 +1,6 @@
export enum GenericDropdownContentWidth {
Narrow = 160,
Medium = 200,
Large = 240,
ExtraLarge = 320,
}

View File

@ -1,12 +1,11 @@
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { StyledDropdownContentContainer } from '@/ui/layout/dropdown/components/internal/DropdownInternalContainer';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import styled from '@emotion/styled';
import { FloatingPortal, offset, shift, useFloating } from '@floating-ui/react';
import { ReactNode } from 'react';
import { StyledDropdownContentContainer } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
type ExpandedListDropdownProps = {
anchorElement?: HTMLElement;
children: ReactNode;
@ -40,6 +39,10 @@ export const ExpandedListDropdown = ({
listenerId: 'expandable-list',
});
const dropdownContentWidth = anchorElement
? Math.max(220, anchorElement.getBoundingClientRect().width)
: undefined;
return (
<FloatingPortal>
<StyledDropdownContentContainer
@ -47,17 +50,11 @@ export const ExpandedListDropdown = ({
style={floatingStyles}
>
<OverlayContainer>
<DropdownMenu
width={
anchorElement
? Math.max(220, anchorElement.getBoundingClientRect().width)
: undefined
}
>
<DropdownContent widthInPixels={dropdownContentWidth}>
<StyledExpandedListContainer>
{children}
</StyledExpandedListContainer>
</DropdownMenu>
</DropdownContent>
</OverlayContainer>
</StyledDropdownContentContainer>
</FloatingPortal>

View File

@ -9,7 +9,6 @@ export const OverlayContainer = styled.div<{
display: flex;
backdrop-filter: ${({ theme }) => theme.blur.medium};
width: fit-content;
border-radius: ${({ theme, borderRadius }) =>
theme.border.radius[borderRadius ?? 'md']};

View File

@ -1,101 +0,0 @@
import styled from '@emotion/styled';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID } from '@/ui/layout/show-page/constants/ShowPageAddButtonDropdownId';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { Dropdown } from '../../dropdown/components/Dropdown';
import { Button } from 'twenty-ui/input';
import { IconCheckbox, IconNotes, IconPlus } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
const StyledContainer = styled.div`
z-index: 1;
`;
export const ShowPageAddButton = ({
activityTargetObject,
}: {
activityTargetObject: ActivityTargetableObject;
}) => {
const { closeDropdown } = useDropdown(SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID);
const openNote = useOpenCreateActivityDrawer({
activityObjectNameSingular: CoreObjectNameSingular.Note,
});
const openTask = useOpenCreateActivityDrawer({
activityObjectNameSingular: CoreObjectNameSingular.Task,
});
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const handleSelect = (objectNameSingular: CoreObjectNameSingular) => {
if (objectNameSingular === CoreObjectNameSingular.Note) {
openNote({
targetableObjects: [activityTargetObject],
});
} else if (objectNameSingular === CoreObjectNameSingular.Task) {
openTask({
targetableObjects: [activityTargetObject],
});
}
closeDropdown();
};
if (
activityTargetObject.targetObjectNameSingular ===
CoreObjectNameSingular.Task ||
activityTargetObject.targetObjectNameSingular ===
CoreObjectNameSingular.Note ||
isWorkflowSubObjectMetadata(activityTargetObject.targetObjectNameSingular)
) {
return;
}
if (hasObjectReadOnlyPermission) {
return null;
}
return (
<StyledContainer>
<Dropdown
dropdownId={SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID}
clickableComponent={
<Button
Icon={IconPlus}
dataTestId="add-button"
size="small"
variant="secondary"
accent="default"
title="New note/task"
ariaLabel="New note/task"
/>
}
dropdownComponents={
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => handleSelect(CoreObjectNameSingular.Note)}
accent="default"
LeftIcon={IconNotes}
text="Note"
/>
<MenuItem
onClick={() => handleSelect(CoreObjectNameSingular.Task)}
accent="default"
LeftIcon={IconCheckbox}
text="Task"
/>
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: PageHotkeyScope.ShowPage }}
/>
</StyledContainer>
);
};

View File

@ -11,6 +11,7 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -92,7 +93,7 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
};
return (
<>
<DropdownContent>
<DropdownMenuHeader
StartComponent={
<DropdownMenuHeaderLeftComponent
@ -116,20 +117,21 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
dropdownId={'multi-workspace-dropdown-context-menu'}
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
dropdownComponents={
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconPlus}
text={t`Create Workspace`}
onClick={createWorkspace}
/>
</DropdownMenuItemsContainer>
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconPlus}
text={t`Create Workspace`}
onClick={createWorkspace}
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
}
>
{currentWorkspace?.displayName}
</DropdownMenuHeader>
<DropdownMenuSeparator />
{workspaces.length > 1 && (
<>
<StyledDropdownMenuItemsContainer>
@ -193,6 +195,6 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
</UndecoratedLink>
<MenuItem LeftIcon={IconLogout} text={t`Log out`} onClick={signOut} />
</DropdownMenuItemsContainer>
</>
</DropdownContent>
);
};

View File

@ -1,3 +1,4 @@
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -7,7 +8,6 @@ import { useLingui } from '@lingui/react/macro';
import { useSetRecoilState } from 'recoil';
import { IconCheck, IconChevronLeft } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
export const MultiWorkspaceDropdownThemesComponents = () => {
const { t } = useLingui();
@ -19,7 +19,7 @@ export const MultiWorkspaceDropdownThemesComponents = () => {
);
return (
<>
<DropdownContent>
<DropdownMenuHeader
StartComponent={
<DropdownMenuHeaderLeftComponent
@ -30,7 +30,6 @@ export const MultiWorkspaceDropdownThemesComponents = () => {
>
{t`Theme`}
</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
{colorSchemeList.map((theme) => (
<MenuItem
@ -43,6 +42,6 @@ export const MultiWorkspaceDropdownThemesComponents = () => {
/>
))}
</DropdownMenuItemsContainer>
</>
</DropdownContent>
);
};

View File

@ -2,6 +2,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { Workspaces, workspacesState } from '@/auth/states/workspaces';
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -32,7 +33,7 @@ export const MultiWorkspaceDropdownWorkspacesListComponents = () => {
const [searchValue, setSearchValue] = useState('');
return (
<>
<DropdownContent>
<DropdownMenuHeader
StartComponent={
<DropdownMenuHeaderLeftComponent
@ -43,7 +44,6 @@ export const MultiWorkspaceDropdownWorkspacesListComponents = () => {
>
{t`Other workspaces`}
</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuSearchInput
placeholder={t`Search`}
autoFocus
@ -83,6 +83,6 @@ export const MultiWorkspaceDropdownWorkspacesListComponents = () => {
</UndecoratedLink>
))}
</DropdownMenuItemsContainer>
</>
</DropdownContent>
);
};