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:
@ -5,6 +5,7 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/context
|
||||
import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope';
|
||||
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
@ -72,19 +73,21 @@ export const CommandMenuActionMenuDropdown = () => {
|
||||
setSelectedItemId(selectableItemIdArray[0]);
|
||||
}}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={actionMenuId}
|
||||
hotkeyScope={
|
||||
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown
|
||||
}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{recordSelectionActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
selectableListInstanceId={actionMenuId}
|
||||
hotkeyScope={
|
||||
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown
|
||||
}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{recordSelectionActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -9,6 +9,7 @@ import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDro
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
@ -82,39 +83,41 @@ export const RecordIndexActionMenuDropdown = () => {
|
||||
y: actionMenuDropdownPosition.y ?? 0,
|
||||
}}
|
||||
dropdownComponents={
|
||||
<StyledDropdownMenuContainer
|
||||
data-click-outside-id={ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
hotkeyScope={ActionMenuDropdownHotkeyScope.ActionMenuDropdown}
|
||||
selectableItemIdArray={selectedItemIdArray}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
{recordIndexActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
))}
|
||||
<SelectableListItem
|
||||
itemId="more-actions"
|
||||
key="more-actions"
|
||||
onEnter={() => {
|
||||
closeDropdown(dropdownId);
|
||||
openCommandMenu();
|
||||
}}
|
||||
<DropdownContent>
|
||||
<StyledDropdownMenuContainer
|
||||
data-click-outside-id={ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
hotkeyScope={ActionMenuDropdownHotkeyScope.ActionMenuDropdown}
|
||||
selectableItemIdArray={selectedItemIdArray}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
<MenuItem
|
||||
LeftIcon={IconLayoutSidebarRightExpand}
|
||||
onClick={() => {
|
||||
{recordIndexActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
))}
|
||||
<SelectableListItem
|
||||
itemId="more-actions"
|
||||
key="more-actions"
|
||||
onEnter={() => {
|
||||
closeDropdown(dropdownId);
|
||||
openCommandMenu();
|
||||
}}
|
||||
focused={selectedItemId === 'more-actions'}
|
||||
text="More actions"
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledDropdownMenuContainer>
|
||||
>
|
||||
<MenuItem
|
||||
LeftIcon={IconLayoutSidebarRightExpand}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
openCommandMenu();
|
||||
}}
|
||||
focused={selectedItemId === 'more-actions'}
|
||||
text="More actions"
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledDropdownMenuContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
import { MessageThreadSubscribersDropdownButton } from '@/activities/emails/components/MessageThreadSubscribersDropdownButton';
|
||||
import { MessageThread } from '@/activities/emails/types/MessageThread';
|
||||
|
||||
export const EmailThreadMembersChip = ({
|
||||
messageThread,
|
||||
}: {
|
||||
messageThread: MessageThread;
|
||||
}) => {
|
||||
const subscribers = messageThread.subscribers ?? [];
|
||||
|
||||
return (
|
||||
<MessageThreadSubscribersDropdownButton
|
||||
messageThreadSubscribers={subscribers}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,75 +0,0 @@
|
||||
import { MessageThreadSubscriber } from '@/activities/emails/types/MessageThreadSubscriber';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useContext } from 'react';
|
||||
import { Chip, ChipVariant } from 'twenty-ui/components';
|
||||
import { Avatar, AvatarGroup, IconChevronDown } from 'twenty-ui/display';
|
||||
import { ThemeContext } from 'twenty-ui/theme';
|
||||
|
||||
const MAX_NUMBER_OF_AVATARS = 3;
|
||||
|
||||
export const MessageThreadSubscribersChip = ({
|
||||
messageThreadSubscribers,
|
||||
}: {
|
||||
messageThreadSubscribers: MessageThreadSubscriber[];
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const numberOfMessageThreadSubscribers = messageThreadSubscribers.length;
|
||||
|
||||
const isOnlyOneSubscriber = numberOfMessageThreadSubscribers === 1;
|
||||
|
||||
const isPrivateThread = isOnlyOneSubscriber;
|
||||
|
||||
const privateLabel = 'Private';
|
||||
|
||||
const susbcriberAvatarUrls = messageThreadSubscribers
|
||||
.map((member) => member.workspaceMember.avatarUrl)
|
||||
.filter(isNonEmptyString);
|
||||
|
||||
const firstAvatarUrl = susbcriberAvatarUrls[0];
|
||||
const firstAvatarColorSeed = messageThreadSubscribers?.[0].workspaceMember.id;
|
||||
const firstAvatarPlaceholder =
|
||||
messageThreadSubscribers?.[0].workspaceMember.name.firstName;
|
||||
|
||||
const subscriberNames = messageThreadSubscribers.map(
|
||||
(member) => member.workspaceMember?.name.firstName,
|
||||
);
|
||||
|
||||
const moreAvatarsLabel =
|
||||
numberOfMessageThreadSubscribers > MAX_NUMBER_OF_AVATARS
|
||||
? `+${numberOfMessageThreadSubscribers - MAX_NUMBER_OF_AVATARS}`
|
||||
: null;
|
||||
|
||||
const label = isPrivateThread ? privateLabel : (moreAvatarsLabel ?? '');
|
||||
|
||||
return (
|
||||
<Chip
|
||||
label={label}
|
||||
variant={ChipVariant.Highlighted}
|
||||
leftComponent={
|
||||
isOnlyOneSubscriber ? (
|
||||
<Avatar
|
||||
avatarUrl={firstAvatarUrl}
|
||||
placeholderColorSeed={firstAvatarColorSeed}
|
||||
placeholder={firstAvatarPlaceholder}
|
||||
size="md"
|
||||
type={'rounded'}
|
||||
/>
|
||||
) : (
|
||||
<AvatarGroup
|
||||
avatars={subscriberNames.map((name, index) => (
|
||||
<Avatar
|
||||
key={name}
|
||||
avatarUrl={susbcriberAvatarUrls[index] ?? ''}
|
||||
placeholder={name}
|
||||
type="rounded"
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
rightComponent={() => <IconChevronDown size={theme.icon.size.sm} />}
|
||||
clickable
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,107 +0,0 @@
|
||||
import { offset } from '@floating-ui/react';
|
||||
|
||||
import { MessageThreadSubscriberDropdownAddSubscriber } from '@/activities/emails/components/MessageThreadSubscriberDropdownAddSubscriber';
|
||||
import { MessageThreadSubscribersChip } from '@/activities/emails/components/MessageThreadSubscribersChip';
|
||||
import { MessageThreadSubscriber } from '@/activities/emails/types/MessageThreadSubscriber';
|
||||
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 { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
|
||||
import { useState } from 'react';
|
||||
import { IconMinus, IconPlus } from 'twenty-ui/display';
|
||||
import { MenuItem, MenuItemAvatar } from 'twenty-ui/navigation';
|
||||
|
||||
export const MESSAGE_THREAD_SUBSCRIBER_DROPDOWN_ID =
|
||||
'message-thread-subscriber';
|
||||
|
||||
export const MessageThreadSubscribersDropdownButton = ({
|
||||
messageThreadSubscribers,
|
||||
}: {
|
||||
messageThreadSubscribers: MessageThreadSubscriber[];
|
||||
}) => {
|
||||
const [isAddingSubscriber, setIsAddingSubscriber] = useState(false);
|
||||
|
||||
const { closeDropdown } = useDropdown(MESSAGE_THREAD_SUBSCRIBER_DROPDOWN_ID);
|
||||
|
||||
const mockSubscribers = [
|
||||
...messageThreadSubscribers,
|
||||
...messageThreadSubscribers,
|
||||
...messageThreadSubscribers,
|
||||
...messageThreadSubscribers,
|
||||
];
|
||||
|
||||
// TODO: implement
|
||||
const handleAddSubscriberClick = () => {
|
||||
setIsAddingSubscriber(true);
|
||||
};
|
||||
|
||||
// TODO: implement
|
||||
const handleRemoveSubscriber = (_subscriber: MessageThreadSubscriber) => {
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
useListenRightDrawerClose(() => {
|
||||
closeDropdown();
|
||||
});
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={MESSAGE_THREAD_SUBSCRIBER_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
<MessageThreadSubscribersChip
|
||||
messageThreadSubscribers={mockSubscribers}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenu width="160px" z-index={offset(1)}>
|
||||
{isAddingSubscriber ? (
|
||||
<MessageThreadSubscriberDropdownAddSubscriber
|
||||
existingSubscribers={messageThreadSubscribers}
|
||||
/>
|
||||
) : (
|
||||
<DropdownMenuItemsContainer>
|
||||
{messageThreadSubscribers?.map((subscriber) => (
|
||||
<MenuItemAvatar
|
||||
key={subscriber.workspaceMember.id}
|
||||
testId="menu-item"
|
||||
onClick={() => {
|
||||
handleRemoveSubscriber(subscriber);
|
||||
}}
|
||||
text={
|
||||
subscriber.workspaceMember.name.firstName +
|
||||
' ' +
|
||||
subscriber.workspaceMember.name.lastName
|
||||
}
|
||||
avatar={{
|
||||
placeholder: subscriber.workspaceMember.name.firstName,
|
||||
avatarUrl: subscriber.workspaceMember.avatarUrl,
|
||||
placeholderColorSeed: subscriber.workspaceMember.id,
|
||||
size: 'md',
|
||||
type: 'rounded',
|
||||
}}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconMinus,
|
||||
onClick: () => {
|
||||
handleRemoveSubscriber(subscriber);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<MenuItem
|
||||
LeftIcon={IconPlus}
|
||||
onClick={handleAddSubscriberClick}
|
||||
text="Add subscriber"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: MESSAGE_THREAD_SUBSCRIBER_DROPDOWN_ID }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import {
|
||||
IconDotsVertical,
|
||||
@ -48,26 +50,27 @@ export const AttachmentDropdown = ({
|
||||
clickableComponent={
|
||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Download"
|
||||
LeftIcon={IconDownload}
|
||||
onClick={handleDownload}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Rename"
|
||||
LeftIcon={IconPencil}
|
||||
onClick={handleRename}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Download"
|
||||
LeftIcon={IconDownload}
|
||||
onClick={handleDownload}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Rename"
|
||||
LeftIcon={IconPencil}
|
||||
onClick={handleRename}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
/>
|
||||
|
||||
@ -4,11 +4,11 @@ import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { useHandleResendEmailVerificationToken } from '@/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { AnimatedEaseIn } from 'twenty-ui/utilities';
|
||||
import { IconMail } from 'twenty-ui/display';
|
||||
import { Loader } from 'twenty-ui/feedback';
|
||||
import { MainButton } from 'twenty-ui/input';
|
||||
import { RGBA } from 'twenty-ui/theme';
|
||||
import { AnimatedEaseIn } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledMailContainer = styled.div`
|
||||
align-items: center;
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import {
|
||||
CommandMenuContextChip,
|
||||
CommandMenuContextChipProps,
|
||||
} from './CommandMenuContextChip';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
export const CommandMenuContextChipGroups = ({
|
||||
contextChips,
|
||||
@ -53,19 +54,23 @@ export const CommandMenuContextChipGroups = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{firstChips.map((chip, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
LeftComponent={chip.Icons}
|
||||
text={chip.text}
|
||||
onClick={() => {
|
||||
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||
chip.onClick?.();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{firstChips.map((chip, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
LeftComponent={chip.Icons}
|
||||
text={chip.text}
|
||||
onClick={() => {
|
||||
closeDropdown(
|
||||
COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID,
|
||||
);
|
||||
chip.onClick?.();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: AppHotkeyScope.CommandMenu,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { FavoriteFolderHotkeyScope } from '@/favorites/constants/FavoriteFolderRightIconDropdownHotkeyScope';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { IconDotsVertical, IconPencil, IconTrash } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
@ -40,20 +42,22 @@ export const FavoriteFolderNavigationDrawerItemDropdown = ({
|
||||
}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconPencil}
|
||||
onClick={handleRename}
|
||||
accent="default"
|
||||
text="Rename"
|
||||
/>
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
onClick={handleDelete}
|
||||
accent="danger"
|
||||
text="Delete"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconPencil}
|
||||
onClick={handleRename}
|
||||
accent="default"
|
||||
text="Rename"
|
||||
/>
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
onClick={handleDelete}
|
||||
accent="danger"
|
||||
text="Delete"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { PageFavoriteButton } from '@/favorites/components/PageFavoriteButton';
|
||||
import { FavoriteFolderPicker } from '@/favorites/favorite-folder-picker/components/FavoriteFolderPicker';
|
||||
import { FavoriteFolderPickerEffect } from '@/favorites/favorite-folder-picker/components/FavoriteFolderPickerEffect';
|
||||
import { FavoriteFolderPickerComponentInstanceContext } from '@/favorites/favorite-folder-picker/scopes/FavoriteFolderPickerScope';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
|
||||
type PageFavoriteFoldersDropdownProps = {
|
||||
dropdownId: string;
|
||||
isFavorite: boolean;
|
||||
record?: ObjectRecord;
|
||||
objectNameSingular: string;
|
||||
};
|
||||
|
||||
export const PageFavoriteFoldersDropdown = ({
|
||||
dropdownId,
|
||||
isFavorite,
|
||||
record,
|
||||
objectNameSingular,
|
||||
}: PageFavoriteFoldersDropdownProps) => {
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
|
||||
return (
|
||||
<FavoriteFolderPickerComponentInstanceContext
|
||||
favoriteFoldersScopeId={dropdownId}
|
||||
>
|
||||
<DropdownScope dropdownScopeId={dropdownId}>
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
dropdownPlacement="bottom-start"
|
||||
clickableComponent={<PageFavoriteButton isFavorite={isFavorite} />}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<FavoriteFolderPickerEffect record={record} />
|
||||
<FavoriteFolderPicker
|
||||
onSubmit={closeDropdown}
|
||||
record={record}
|
||||
objectNameSingular={objectNameSingular}
|
||||
dropdownId={dropdownId}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
/>
|
||||
</DropdownScope>
|
||||
</FavoriteFolderPickerComponentInstanceContext>
|
||||
);
|
||||
};
|
||||
@ -6,7 +6,7 @@ import { FavoriteFolderPickerInstanceContext } from '@/favorites/favorite-folder
|
||||
import { favoriteFolderSearchFilterComponentState } from '@/favorites/favorite-folder-picker/states/favoriteFoldersSearchFilterComponentState';
|
||||
import { isFavoriteFolderCreatingState } from '@/favorites/states/isFavoriteFolderCreatingState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
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 { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -90,7 +90,7 @@ export const FavoriteFolderPicker = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownContent>
|
||||
<FavoriteFolderPickerSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
@ -101,6 +101,6 @@ export const FavoriteFolderPicker = ({
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<FavoriteFolderPickerFooter dropdownId={dropdownId} />
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,6 +11,7 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
@ -151,20 +152,22 @@ export const AdvancedFilterAddFilterRuleSelect = ({
|
||||
<LightButton Icon={IconPlus} title="Add filter rule" />
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconPlus}
|
||||
text="Add rule"
|
||||
onClick={handleAddFilter}
|
||||
/>
|
||||
{isFilterRuleGroupOptionVisible && (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconLibraryPlus}
|
||||
text="Add rule group"
|
||||
onClick={handleAddFilterGroup}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add rule"
|
||||
onClick={handleAddFilter}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
{isFilterRuleGroupOptionVisible && (
|
||||
<MenuItem
|
||||
LeftIcon={IconLibraryPlus}
|
||||
text="Add rule group"
|
||||
onClick={handleAddFilterGroup}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={{ y: 8, x: 0 }}
|
||||
|
||||
@ -16,6 +16,7 @@ import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object
|
||||
import { isExpectedSubFieldName } from '@/object-record/object-filter-dropdown/utils/isExpectedSubFieldName';
|
||||
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
@ -52,28 +53,24 @@ export const AdvancedFilterDropdownFilterInput = ({
|
||||
<ObjectFilterDropdownDateInput />
|
||||
)}
|
||||
{filterType === 'RELATION' && (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilter.id} />
|
||||
</>
|
||||
</DropdownContent>
|
||||
)}
|
||||
{filterType === 'ACTOR' &&
|
||||
(isActorSourceCompositeFilter ? (
|
||||
<>
|
||||
<ObjectFilterDropdownSourceSelect />
|
||||
</>
|
||||
<ObjectFilterDropdownSourceSelect />
|
||||
) : (
|
||||
<>
|
||||
<ObjectFilterDropdownTextInput />
|
||||
</>
|
||||
<ObjectFilterDropdownTextInput />
|
||||
))}
|
||||
{['SELECT', 'MULTI_SELECT'].includes(filterType) && (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownOptionSelect />
|
||||
</>
|
||||
</DropdownContent>
|
||||
)}
|
||||
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
|
||||
{filterType === 'CURRENCY' &&
|
||||
@ -82,9 +79,7 @@ export const AdvancedFilterDropdownFilterInput = ({
|
||||
'currencyCode',
|
||||
recordFilter.subFieldName,
|
||||
) ? (
|
||||
<>
|
||||
<ObjectFilterDropdownCurrencySelect />
|
||||
</>
|
||||
<ObjectFilterDropdownCurrencySelect />
|
||||
) : (
|
||||
<></>
|
||||
))}
|
||||
|
||||
@ -21,6 +21,7 @@ import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/o
|
||||
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
@ -132,7 +133,7 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<AdvancedFilterFieldSelectSearchInput />
|
||||
<SelectableList
|
||||
hotkeyScope={advancedFilterFieldSelectDropdownId}
|
||||
@ -175,6 +176,6 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/
|
||||
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
|
||||
type AdvancedFilterLogicalOperatorDropdownProps = {
|
||||
recordFilterGroup: RecordFilterGroup;
|
||||
@ -27,6 +28,7 @@ export const AdvancedFilterLogicalOperatorDropdown = ({
|
||||
return (
|
||||
<Select
|
||||
fullWidth
|
||||
dropdownWidth={GenericDropdownContentWidth.Narrow}
|
||||
dropdownId={`advanced-filter-logical-operator-${recordFilterGroup.id}`}
|
||||
value={recordFilterGroup.logicalOperator}
|
||||
onChange={handleChange}
|
||||
|
||||
@ -4,10 +4,11 @@ import { useRemoveRootRecordFilterGroupIfEmpty } from '@/object-record/record-fi
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { IconButton } from 'twenty-ui/input';
|
||||
import { IconDotsVertical, IconTrash } from 'twenty-ui/display';
|
||||
import { IconButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
type AdvancedFilterRecordFilterGroupOptionsDropdownProps = {
|
||||
@ -53,14 +54,16 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Remove rule group"
|
||||
onClick={handleRemove}
|
||||
LeftIcon={IconTrash}
|
||||
accent="danger"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Remove rule group"
|
||||
onClick={handleRemove}
|
||||
LeftIcon={IconTrash}
|
||||
accent="danger"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={{ y: 2, x: 0 }}
|
||||
|
||||
@ -6,7 +6,9 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
@ -94,38 +96,39 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer width="auto">
|
||||
<SelectableList
|
||||
hotkeyScope={dropdownId}
|
||||
selectableItemIdArray={operandsForFilterType.map(
|
||||
(operand) => operand,
|
||||
)}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<SelectableListItem
|
||||
itemId={filterOperand}
|
||||
key={`select-filter-operand-${index}`}
|
||||
onEnter={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === filterOperand}
|
||||
onClick={() => {
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
hotkeyScope={dropdownId}
|
||||
selectableItemIdArray={operandsForFilterType.map(
|
||||
(operand) => operand,
|
||||
)}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<SelectableListItem
|
||||
itemId={filterOperand}
|
||||
key={`select-filter-operand-${index}`}
|
||||
onEnter={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === filterOperand}
|
||||
onClick={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownWidth={200}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -7,12 +7,13 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
|
||||
|
||||
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconButton } from 'twenty-ui/input';
|
||||
import { IconDotsVertical, IconTrash } from 'twenty-ui/display';
|
||||
import { IconButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
type AdvancedFilterRecordFilterOptionsDropdownProps = {
|
||||
@ -73,14 +74,16 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Remove rule"
|
||||
onClick={handleRemove}
|
||||
LeftIcon={IconTrash}
|
||||
accent="danger"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Remove rule"
|
||||
onClick={handleRemove}
|
||||
LeftIcon={IconTrash}
|
||||
accent="danger"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
||||
|
||||
@ -2,9 +2,11 @@ import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filt
|
||||
|
||||
import { AdvancedFilterRecordFilterGroupRow } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupRow';
|
||||
import { AdvancedFilterRecordFilterRow } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow';
|
||||
import { ADVANCED_FILTER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/advanced-filter/constants/AdvancedFilterDropdownContentWidth';
|
||||
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
|
||||
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
|
||||
import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
@ -18,7 +20,6 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
min-width: 650px;
|
||||
`;
|
||||
|
||||
export const AdvancedFilterRootRecordFilterGroup = () => {
|
||||
@ -37,31 +38,33 @@ export const AdvancedFilterRootRecordFilterGroup = () => {
|
||||
|
||||
return (
|
||||
<ScrollWrapper componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}>
|
||||
<StyledContainer>
|
||||
{childRecordFiltersAndRecordFilterGroups.map(
|
||||
(recordFilterGroupChild, recordFilterGroupChildIndex) =>
|
||||
isRecordFilterGroupChildARecordFilterGroup(
|
||||
recordFilterGroupChild,
|
||||
) ? (
|
||||
<AdvancedFilterRecordFilterGroupRow
|
||||
key={recordFilterGroupChild.id}
|
||||
parentRecordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilterGroup={recordFilterGroupChild}
|
||||
recordFilterGroupIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
) : (
|
||||
<AdvancedFilterRecordFilterRow
|
||||
key={recordFilterGroupChild.id}
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilter={recordFilterGroupChild}
|
||||
recordFilterIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<AdvancedFilterAddFilterRuleSelect
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
/>
|
||||
</StyledContainer>
|
||||
<DropdownContent widthInPixels={ADVANCED_FILTER_DROPDOWN_CONTENT_WIDTH}>
|
||||
<StyledContainer>
|
||||
{childRecordFiltersAndRecordFilterGroups.map(
|
||||
(recordFilterGroupChild, recordFilterGroupChildIndex) =>
|
||||
isRecordFilterGroupChildARecordFilterGroup(
|
||||
recordFilterGroupChild,
|
||||
) ? (
|
||||
<AdvancedFilterRecordFilterGroupRow
|
||||
key={recordFilterGroupChild.id}
|
||||
parentRecordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilterGroup={recordFilterGroupChild}
|
||||
recordFilterGroupIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
) : (
|
||||
<AdvancedFilterRecordFilterRow
|
||||
key={recordFilterGroupChild.id}
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilter={recordFilterGroupChild}
|
||||
recordFilterIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<AdvancedFilterAddFilterRuleSelect
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</DropdownContent>
|
||||
</ScrollWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -16,6 +16,7 @@ import { areCompositeTypeSubFieldsFilterable } from '@/object-record/record-filt
|
||||
import { isCompositeTypeFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeFilterableByAnySubField';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||
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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
@ -110,7 +111,7 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -184,6 +185,6 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
||||
))}
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -133,7 +133,6 @@ export const AdvancedFilterValueInput = ({
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={dropdownContentOffset}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownWidth={280}
|
||||
onClose={handleFilterValueDropdownClose}
|
||||
onOpen={handleFilterValueDropdownOpen}
|
||||
/>
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const ADVANCED_FILTER_DROPDOWN_CONTENT_WIDTH = 650;
|
||||
@ -5,6 +5,7 @@ import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-
|
||||
import { useObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useObjectFilterDropdownFilterValue';
|
||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
||||
import { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
@ -51,27 +52,29 @@ export const ObjectFilterDropdownBooleanSelect = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectableList
|
||||
selectableListInstanceId="boolean-select"
|
||||
selectableItemIdArray={options.map((option) => option.toString())}
|
||||
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight width="auto">
|
||||
{options.map((option) => (
|
||||
<StyledBooleanSelectContainer
|
||||
key={String(option)}
|
||||
onClick={() => handleOptionSelect(option)}
|
||||
selected={objectFilterDropdownFilterValue === option.toString()}
|
||||
>
|
||||
<BooleanDisplay value={option} />
|
||||
{objectFilterDropdownFilterValue === option.toString() && (
|
||||
<StyledIconCheckContainer>
|
||||
<IconCheck color={theme.grayScale.gray50} size={16} />
|
||||
</StyledIconCheckContainer>
|
||||
)}
|
||||
</StyledBooleanSelectContainer>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
<DropdownContent>
|
||||
<SelectableList
|
||||
selectableListInstanceId="boolean-select"
|
||||
selectableItemIdArray={options.map((option) => option.toString())}
|
||||
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight width="auto">
|
||||
{options.map((option) => (
|
||||
<StyledBooleanSelectContainer
|
||||
key={String(option)}
|
||||
onClick={() => handleOptionSelect(option)}
|
||||
selected={objectFilterDropdownFilterValue === option.toString()}
|
||||
>
|
||||
<BooleanDisplay value={option} />
|
||||
{objectFilterDropdownFilterValue === option.toString() && (
|
||||
<StyledIconCheckContainer>
|
||||
<IconCheck color={theme.grayScale.gray50} size={16} />
|
||||
</StyledIconCheckContainer>
|
||||
)}
|
||||
</StyledBooleanSelectContainer>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import { getCountryFlagMenuItemAvatar } from '@/object-record/object-filter-drop
|
||||
import { turnCountryIntoSelectableItem } from '@/object-record/object-filter-dropdown/utils/turnCountryIntoSelectableItem';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||
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';
|
||||
@ -18,13 +19,7 @@ import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
|
||||
export const EMPTY_FILTER_VALUE = '[]';
|
||||
export const MAX_ITEMS_TO_DISPLAY = 5;
|
||||
|
||||
type ObjectFilterDropdownCountrySelectProps = {
|
||||
dropdownWidth?: number;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownCountrySelect = ({
|
||||
dropdownWidth,
|
||||
}: ObjectFilterDropdownCountrySelectProps) => {
|
||||
export const ObjectFilterDropdownCountrySelect = () => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const objectFilterDropdownCurrentRecordFilter = useRecoilComponentValueV2(
|
||||
@ -101,7 +96,7 @@ export const ObjectFilterDropdownCountrySelect = ({
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
@ -112,7 +107,7 @@ export const ObjectFilterDropdownCountrySelect = ({
|
||||
}}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight width={dropdownWidth ?? 200}>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{filteredSelectedItems?.map((item) => {
|
||||
return (
|
||||
<MenuItemMultiSelectAvatar
|
||||
@ -141,6 +136,6 @@ export const ObjectFilterDropdownCountrySelect = ({
|
||||
})}
|
||||
{showNoResult && <MenuItem text={t`No results`} />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,9 +5,11 @@ import { turnCurrencyIntoSelectableItem } from '@/object-record/object-filter-dr
|
||||
import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import { CURRENCIES } from '@/settings/data-model/constants/Currencies';
|
||||
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';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
@ -95,7 +97,7 @@ export const ObjectFilterDropdownCurrencySelect = () => {
|
||||
searchText !== '';
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Large}>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
@ -153,6 +155,6 @@ export const ObjectFilterDropdownCurrencySelect = () => {
|
||||
})}
|
||||
{showNoResult && <MenuItem text={t`No results`} />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -9,17 +9,16 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
||||
import { ObjectFilterDropdownCurrencySelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownCurrencySelect';
|
||||
import { ObjectFilterDropdownOperandDropdown } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown';
|
||||
import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
|
||||
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||
import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth';
|
||||
import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes';
|
||||
import { TEXT_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/TextFilterTypes';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { isExpectedSubFieldName } from '@/object-record/object-filter-dropdown/utils/isExpectedSubFieldName';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type ObjectFilterDropdownFilterInputProps = {
|
||||
@ -36,11 +35,6 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const subFieldNameUsedInDropdown = useRecoilComponentValueV2(
|
||||
subFieldNameUsedInDropdownComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
filterDropdownId,
|
||||
@ -69,74 +63,52 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const isNotASubFieldFilter = !isDefined(subFieldNameUsedInDropdown);
|
||||
const isDateFilter = DATE_FILTER_TYPES.includes(filterType);
|
||||
const isOnlyOperand = !isConfigurable;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isConfigurable && selectedOperandInDropdown && (
|
||||
<>
|
||||
{TEXT_FILTER_TYPES.includes(filterType) && (
|
||||
<ObjectFilterDropdownTextInput />
|
||||
)}
|
||||
{NUMBER_FILTER_TYPES.includes(filterType) && (
|
||||
<ObjectFilterDropdownNumberInput />
|
||||
)}
|
||||
{filterType === 'RATING' && <ObjectFilterDropdownRatingInput />}
|
||||
{DATE_FILTER_TYPES.includes(filterType) && (
|
||||
<ObjectFilterDropdownDateInput />
|
||||
)}
|
||||
{filterType === 'RELATION' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect
|
||||
recordFilterId={recordFilterId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{filterType === 'ACTOR' && (
|
||||
<>
|
||||
<ObjectFilterDropdownTextInput />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'ADDRESS' &&
|
||||
(isNotASubFieldFilter ? (
|
||||
<>
|
||||
<ObjectFilterDropdownTextInput />
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
))}
|
||||
{filterType === 'CURRENCY' &&
|
||||
(isExpectedSubFieldName(
|
||||
FieldMetadataType.CURRENCY,
|
||||
'currencyCode',
|
||||
subFieldNameUsedInDropdown,
|
||||
) ? (
|
||||
<>
|
||||
<ObjectFilterDropdownCurrencySelect />
|
||||
</>
|
||||
) : isExpectedSubFieldName(
|
||||
FieldMetadataType.CURRENCY,
|
||||
'amountMicros',
|
||||
subFieldNameUsedInDropdown,
|
||||
) ? (
|
||||
<>
|
||||
<ObjectFilterDropdownNumberInput />
|
||||
</>
|
||||
) : (
|
||||
<ObjectFilterDropdownNumberInput />
|
||||
))}
|
||||
{['SELECT', 'MULTI_SELECT'].includes(filterType) && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownOptionSelect />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
if (isOnlyOperand) {
|
||||
return (
|
||||
<DropdownContent>
|
||||
<ObjectFilterDropdownOperandDropdown />
|
||||
</DropdownContent>
|
||||
);
|
||||
} else if (isDateFilter) {
|
||||
return (
|
||||
<DropdownContent widthInPixels={DATE_PICKER_DROPDOWN_CONTENT_WIDTH}>
|
||||
<ObjectFilterDropdownOperandDropdown />
|
||||
<ObjectFilterDropdownDateInput />
|
||||
</DropdownContent>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DropdownContent>
|
||||
<ObjectFilterDropdownOperandDropdown />
|
||||
{TEXT_FILTER_TYPES.includes(filterType) && (
|
||||
<ObjectFilterDropdownTextInput />
|
||||
)}
|
||||
{NUMBER_FILTER_TYPES.includes(filterType) && (
|
||||
<ObjectFilterDropdownNumberInput />
|
||||
)}
|
||||
{filterType === 'RATING' && <ObjectFilterDropdownRatingInput />}
|
||||
{filterType === 'RELATION' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilterId} />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'ACTOR' && <ObjectFilterDropdownTextInput />}
|
||||
{filterType === 'ADDRESS' && <ObjectFilterDropdownTextInput />}
|
||||
{filterType === 'CURRENCY' && <ObjectFilterDropdownNumberInput />}
|
||||
{['SELECT', 'MULTI_SELECT'].includes(filterType) && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownOptionSelect />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
|
||||
</DropdownContent>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -5,16 +5,20 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object-
|
||||
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { getOperandLabel } from '../utils/getOperandLabel';
|
||||
import { IconChevronDown } from 'twenty-ui/display';
|
||||
import { getOperandLabel } from '../utils/getOperandLabel';
|
||||
|
||||
const StyledDropdownMenuHeader = styled(DropdownMenuHeader)`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const OPERAND_DROPDOWN_CLICK_OUTSIDE_ID =
|
||||
'object-filter-dropdown-operand-dropdown-click-outside-id';
|
||||
|
||||
export const ObjectFilterDropdownOperandDropdown = ({
|
||||
filterDropdownId,
|
||||
}: {
|
||||
@ -29,21 +33,25 @@ export const ObjectFilterDropdownOperandDropdown = ({
|
||||
const dropdownId = `${filterDropdownId}-operand-dropdown`;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={
|
||||
<StyledDropdownMenuHeader
|
||||
key={'selected-filter-operand'}
|
||||
EndComponent={<IconChevronDown />}
|
||||
>
|
||||
{getOperandLabel(selectedOperandInDropdown)}
|
||||
</StyledDropdownMenuHeader>
|
||||
}
|
||||
dropdownComponents={<ObjectFilterDropdownOperandSelect />}
|
||||
dropdownHotkeyScope={{
|
||||
scope: FiltersHotkeyScope.ObjectFilterDropdownOperandDropdown,
|
||||
}}
|
||||
dropdownOffset={{ x: parseInt(theme.spacing(2), 10) }}
|
||||
/>
|
||||
<ClickOutsideListenerContext.Provider
|
||||
value={{ excludedClickOutsideId: OPERAND_DROPDOWN_CLICK_OUTSIDE_ID }}
|
||||
>
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={
|
||||
<StyledDropdownMenuHeader
|
||||
key={'selected-filter-operand'}
|
||||
EndComponent={<IconChevronDown />}
|
||||
>
|
||||
{getOperandLabel(selectedOperandInDropdown)}
|
||||
</StyledDropdownMenuHeader>
|
||||
}
|
||||
dropdownComponents={<ObjectFilterDropdownOperandSelect />}
|
||||
dropdownHotkeyScope={{
|
||||
scope: FiltersHotkeyScope.ObjectFilterDropdownOperandDropdown,
|
||||
}}
|
||||
dropdownOffset={{ x: parseInt(theme.spacing(2), 10) }}
|
||||
/>
|
||||
</ClickOutsideListenerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filt
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -45,17 +46,19 @@ export const ObjectFilterDropdownOperandSelect = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<MenuItem
|
||||
key={`select-filter-operand-${index}`}
|
||||
onClick={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
closeDropdown();
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
))}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
{operandsForFilterType.map((filterOperand, index) => (
|
||||
<MenuItem
|
||||
key={`select-filter-operand-${index}`}
|
||||
onClick={() => {
|
||||
handleOperandChange(filterOperand);
|
||||
closeDropdown();
|
||||
}}
|
||||
text={getOperandLabel(filterOperand)}
|
||||
/>
|
||||
))}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { ObjectFilterDropdownOperandDropdown } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown';
|
||||
|
||||
type ObjectFilterOperandSelectAndInputProps = {
|
||||
filterDropdownId?: string;
|
||||
};
|
||||
|
||||
export const ObjectFilterOperandSelectAndInput = ({
|
||||
filterDropdownId,
|
||||
}: ObjectFilterOperandSelectAndInputProps) => {
|
||||
return (
|
||||
<>
|
||||
<ObjectFilterDropdownOperandDropdown
|
||||
filterDropdownId={filterDropdownId}
|
||||
/>
|
||||
<ObjectFilterDropdownFilterInput filterDropdownId={filterDropdownId} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export const DATE_PICKER_DROPDOWN_CONTENT_WIDTH = 280;
|
||||
@ -1,13 +1,14 @@
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useObjectOptionsForTable } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForTable';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
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 { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { IconChevronLeft, IconEyeOff } from 'twenty-ui/display';
|
||||
import { MenuItemNavigate } from 'twenty-ui/navigation';
|
||||
|
||||
@ -51,7 +52,7 @@ export const ObjectOptionsDropdownFieldsContent = () => {
|
||||
: handleColumnVisibilityChange;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -79,6 +80,6 @@ export const ObjectOptionsDropdownFieldsContent = () => {
|
||||
text={t`Hidden Fields`}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,17 +7,18 @@ import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdow
|
||||
import { useObjectOptionsForTable } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForTable';
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
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 { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { IconChevronLeft, IconSettings } from 'twenty-ui/display';
|
||||
import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
export const ObjectOptionsDropdownHiddenFieldsContent = () => {
|
||||
const { t } = useLingui();
|
||||
@ -61,7 +62,7 @@ export const ObjectOptionsDropdownHiddenFieldsContent = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -94,6 +95,6 @@ export const ObjectOptionsDropdownHiddenFieldsContent = () => {
|
||||
<MenuItem LeftIcon={IconSettings} text={t`Edit Fields`} />
|
||||
</DropdownMenuItemsContainer>
|
||||
</UndecoratedLink>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,7 +8,9 @@ import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/use
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
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 { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
@ -16,10 +18,9 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { IconChevronLeft, IconSettings } from 'twenty-ui/display';
|
||||
import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
||||
const { t } = useLingui();
|
||||
@ -68,20 +69,17 @@ export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
||||
}, [hiddenRecordGroupIds, currentContentId, onContentChange]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={() => onContentChange('recordGroups')}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
Hidden {recordGroupFieldMetadata?.label}
|
||||
</DropdownMenuHeader>
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={() => onContentChange('recordGroups')}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
Hidden {recordGroupFieldMetadata?.label}
|
||||
</DropdownMenuHeader>
|
||||
<RecordGroupsVisibilityDropdownSection
|
||||
title={`Hidden ${recordGroupFieldMetadata?.label}`}
|
||||
recordGroupIds={hiddenRecordGroupIds}
|
||||
@ -102,6 +100,6 @@ export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
||||
<MenuItem LeftIcon={IconSettings} text={t`Edit field values`} />
|
||||
</DropdownMenuItemsContainer>
|
||||
</UndecoratedLink>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-opti
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
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 ObjectOptionsDropdownLayoutContent = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -234,6 +235,6 @@ export const ObjectOptionsDropdownLayoutContent = () => {
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
|
||||
import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
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';
|
||||
@ -38,7 +39,7 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -105,6 +106,6 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@ import { recordGroupFieldMetadataComponentState } from '@/object-record/record-g
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
@ -93,7 +94,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
{currentView && (
|
||||
<ObjectOptionsDropdownMenuViewName currentView={currentView} />
|
||||
)}
|
||||
@ -121,7 +122,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
</SelectableListItem>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<SelectableListItem
|
||||
itemId="Fields"
|
||||
@ -136,7 +136,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
hasSubMenu
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
<div id="group-by-menu-item">
|
||||
<SelectableListItem
|
||||
itemId="Group"
|
||||
@ -175,7 +174,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
/>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<SelectableListItem
|
||||
itemId="Copy link to view"
|
||||
onEnter={() => {
|
||||
@ -228,6 +226,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@ import { recordGroupFieldMetadataComponentState } from '@/object-record/record-g
|
||||
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
|
||||
import { useHandleRecordGroupField } from '@/object-record/record-index/hooks/useHandleRecordGroupField';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
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';
|
||||
@ -103,7 +104,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
}, [hiddenRecordGroupIds, currentContentId, onContentChange]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -124,6 +125,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) => setRecordGroupFieldSearchInput(event.target.value)}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
{viewType === ViewType.Table && (
|
||||
<MenuItemSelect
|
||||
@ -154,6 +156,6 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
<MenuItem LeftIcon={IconSettings} text={t`Create select field`} />
|
||||
</UndecoratedLink>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-gr
|
||||
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
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';
|
||||
@ -58,7 +59,7 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -123,6 +124,6 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@ import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/re
|
||||
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
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';
|
||||
@ -94,7 +95,7 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -192,6 +193,6 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -10,19 +10,19 @@ import { RecordFiltersComponentInstanceContext } from '@/object-record/record-fi
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useEffect } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
|
||||
const instanceId = 'entity-options-scope';
|
||||
|
||||
@ -111,9 +111,9 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
|
||||
dropdownId: OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
}}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownContent>
|
||||
<Story />
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
</ObjectOptionsDropdownContext.Provider>
|
||||
</RecordIndexContextProvider>
|
||||
);
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
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 { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
@ -232,88 +233,91 @@ export const ObjectSortDropdownButton = ({
|
||||
</StyledHeaderDropdownButton>
|
||||
}
|
||||
dropdownComponents={
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
|
||||
hotkeyScope={hotkeyScope.scope}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{isRecordSortDirectionMenuUnfolded && (
|
||||
<StyledSelectedSortDirectionContainer>
|
||||
<DropdownMenuItemsContainer>
|
||||
{RECORD_SORT_DIRECTIONS.map((sortDirection, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
focused={selectedItemId === sortDirection}
|
||||
onClick={() => handleSortDirectionClick(sortDirection)}
|
||||
text={
|
||||
sortDirection === 'asc' ? t`Ascending` : t`Descending`
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledSelectedSortDirectionContainer>
|
||||
)}
|
||||
<DropdownMenuHeader
|
||||
onClick={() =>
|
||||
setIsRecordSortDirectionMenuUnfolded(
|
||||
!isRecordSortDirectionMenuUnfolded,
|
||||
)
|
||||
}
|
||||
EndComponent={
|
||||
<StyledDropdownMenuHeaderEndComponent>
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</StyledDropdownMenuHeaderEndComponent>
|
||||
}
|
||||
<DropdownContent>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
|
||||
hotkeyScope={hotkeyScope.scope}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{selectedRecordSortDirection === 'asc'
|
||||
? t`Ascending`
|
||||
: t`Descending`}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<StyledInput
|
||||
autoFocus
|
||||
value={objectSortDropdownSearchInput}
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) =>
|
||||
setObjectSortDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{visibleFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<SelectableListItem
|
||||
key={visibleFieldMetadataItem.id}
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === visibleFieldMetadataItem.id}
|
||||
testId={`visible-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
LeftIcon={getIcon(visibleFieldMetadataItem.icon)}
|
||||
text={visibleFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
),
|
||||
{isRecordSortDirectionMenuUnfolded && (
|
||||
<StyledSelectedSortDirectionContainer>
|
||||
<DropdownMenuItemsContainer>
|
||||
{RECORD_SORT_DIRECTIONS.map((sortDirection, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
focused={selectedItemId === sortDirection}
|
||||
onClick={() => handleSortDirectionClick(sortDirection)}
|
||||
text={
|
||||
sortDirection === 'asc' ? t`Ascending` : t`Descending`
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledSelectedSortDirectionContainer>
|
||||
)}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenFieldMetadataItems.map((hiddenFieldMetadataItem, index) => (
|
||||
<SelectableListItem
|
||||
key={hiddenFieldMetadataItem.id}
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === hiddenFieldMetadataItem.id}
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
LeftIcon={getIcon(hiddenFieldMetadataItem.icon)}
|
||||
text={hiddenFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
<DropdownMenuHeader
|
||||
onClick={() =>
|
||||
setIsRecordSortDirectionMenuUnfolded(
|
||||
!isRecordSortDirectionMenuUnfolded,
|
||||
)
|
||||
}
|
||||
EndComponent={
|
||||
<StyledDropdownMenuHeaderEndComponent>
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</StyledDropdownMenuHeaderEndComponent>
|
||||
}
|
||||
>
|
||||
{selectedRecordSortDirection === 'asc'
|
||||
? t`Ascending`
|
||||
: t`Descending`}
|
||||
</DropdownMenuHeader>
|
||||
<StyledInput
|
||||
autoFocus
|
||||
value={objectSortDropdownSearchInput}
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) =>
|
||||
setObjectSortDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{visibleFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<SelectableListItem
|
||||
key={visibleFieldMetadataItem.id}
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === visibleFieldMetadataItem.id}
|
||||
testId={`visible-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(visibleFieldMetadataItem)}
|
||||
LeftIcon={getIcon(visibleFieldMetadataItem.icon)}
|
||||
text={visibleFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{hiddenFieldMetadataItems.map(
|
||||
(hiddenFieldMetadataItem, index) => (
|
||||
<SelectableListItem
|
||||
key={hiddenFieldMetadataItem.id}
|
||||
itemId={hiddenFieldMetadataItem.id}
|
||||
onEnter={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
>
|
||||
<MenuItem
|
||||
focused={selectedItemId === hiddenFieldMetadataItem.id}
|
||||
testId={`hidden-select-sort-${index}`}
|
||||
onClick={() => handleAddSort(hiddenFieldMetadataItem)}
|
||||
LeftIcon={getIcon(hiddenFieldMetadataItem.icon)}
|
||||
text={hiddenFieldMetadataItem.label}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</DropdownContent>
|
||||
}
|
||||
onClose={handleDropdownButtonClose}
|
||||
/>
|
||||
|
||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { useRecordGroupActions } from '@/object-record/record-group/hooks/useRecordGroupActions';
|
||||
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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
@ -22,7 +22,6 @@ type RecordBoardColumnDropdownMenuProps = {
|
||||
stageId: string;
|
||||
};
|
||||
|
||||
// TODO: unify and use Dropdown component
|
||||
export const RecordBoardColumnDropdownMenu = ({
|
||||
onClose,
|
||||
}: RecordBoardColumnDropdownMenuProps) => {
|
||||
@ -45,7 +44,7 @@ export const RecordBoardColumnDropdownMenu = ({
|
||||
return (
|
||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||
<OverlayContainer>
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownContent selectDisabled>
|
||||
<DropdownMenuItemsContainer>
|
||||
{recordGroupActions.map((action) => (
|
||||
<MenuItem
|
||||
@ -59,7 +58,7 @@ export const RecordBoardColumnDropdownMenu = ({
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
</OverlayContainer>
|
||||
</StyledMenuContainer>
|
||||
);
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { DROPDOWN_OFFSET_Y } from '@/dropdown/constants/DropdownOffsetY';
|
||||
import { DROPDOWN_WIDTH } from '@/dropdown/constants/DropdownWidth';
|
||||
import { useCurrentContentId } from '@/dropdown/hooks/useCurrentContentId';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext } from '@/object-record/record-board/contexts/RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext';
|
||||
@ -46,7 +45,6 @@ export const RecordBoardColumnHeaderAggregateDropdown = ({
|
||||
dropdownHotkeyScope={{
|
||||
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
|
||||
}}
|
||||
dropdownWidth={DROPDOWN_WIDTH}
|
||||
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
|
||||
clickableComponent={
|
||||
<RecordBoardColumnHeaderAggregateDropdownButton
|
||||
|
||||
@ -4,12 +4,13 @@ import { aggregateOperationComponentState } from '@/object-record/record-board/r
|
||||
import { availableFieldIdsForAggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/availableFieldIdsForAggregateOperationComponentState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
Icon123,
|
||||
@ -48,7 +49,7 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
||||
|
||||
if (!isDefined(aggregateOperation)) return <></>;
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -94,6 +95,6 @@ export const RecordBoardColumnHeaderAggregateDropdownFieldsContent = () => {
|
||||
);
|
||||
})}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
} from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
|
||||
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
@ -29,7 +30,7 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
@ -60,6 +61,6 @@ export const RecordBoardColumnHeaderAggregateDropdownMenuContent = () => {
|
||||
hasSubMenu
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -12,7 +12,9 @@ import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/Agg
|
||||
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation';
|
||||
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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
@ -20,7 +22,6 @@ import { useUpdateViewAggregate } from '@/views/hooks/useUpdateViewAggregate';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { IconCheck, IconChevronLeft } from 'twenty-ui/display';
|
||||
|
||||
export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
|
||||
@ -58,7 +59,7 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -121,6 +122,6 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
|
||||
),
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
} from '@/object-record/record-field/meta-types/input/components/MultiItemBaseInput';
|
||||
import { FieldInputClickOutsideEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { PhoneRecord } from '@/object-record/record-field/types/FieldMetadata';
|
||||
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 { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@ -181,7 +181,7 @@ export const MultiItemFieldInput = <T,>({
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu ref={containerRef} width={200}>
|
||||
<DropdownContent ref={containerRef}>
|
||||
{!!items.length && (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
@ -232,6 +232,6 @@ export const MultiItemFieldInput = <T,>({
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItemWithOptionDropdown } from '@/ui/navigation/menu-item/components/MenuItemWithOptionDropdown';
|
||||
@ -73,26 +74,28 @@ export const MultiItemFieldMenuItem = <T,>({
|
||||
RightIcon={!isHovered && showPrimaryIcon ? IconBookmark : null}
|
||||
dropdownId={dropdownId}
|
||||
dropdownContent={
|
||||
<DropdownMenuItemsContainer>
|
||||
{showSetAsPrimaryButton && (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{showSetAsPrimaryButton && (
|
||||
<MenuItem
|
||||
LeftIcon={IconBookmarkPlus}
|
||||
text="Set as Primary"
|
||||
onClick={handleSetAsPrimaryClick}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
LeftIcon={IconBookmarkPlus}
|
||||
text="Set as Primary"
|
||||
onClick={handleSetAsPrimaryClick}
|
||||
LeftIcon={IconPencil}
|
||||
text="Edit"
|
||||
onClick={handleEditClick}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
LeftIcon={IconPencil}
|
||||
text="Edit"
|
||||
onClick={handleEditClick}
|
||||
/>
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
onClick={handleDeleteClick}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
onClick={handleDeleteClick}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,99 +0,0 @@
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { availableRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/availableRecordGroupIdsComponentSelector';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
type RecordIndexAddRecordInGroupDropdownProps = {
|
||||
dropdownId: string;
|
||||
clickableComponent: React.ReactNode;
|
||||
};
|
||||
|
||||
export const RecordIndexAddRecordInGroupDropdown = ({
|
||||
dropdownId,
|
||||
clickableComponent,
|
||||
}: RecordIndexAddRecordInGroupDropdownProps) => {
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
|
||||
const recordGroupIds = useRecoilComponentValueV2(
|
||||
availableRecordGroupIdsComponentSelector,
|
||||
);
|
||||
|
||||
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
||||
recordGroupFieldMetadataComponentState,
|
||||
);
|
||||
|
||||
const isRecordGroupTableSectionToggledState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
isRecordGroupTableSectionToggledComponentState,
|
||||
);
|
||||
|
||||
const selectFieldMetadataItem = objectMetadataItem.fields.find(
|
||||
(field) => field.id === recordGroupFieldMetadata?.id,
|
||||
);
|
||||
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const handleCreateNewTableRecordInGroup = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(recordGroup: RecordGroupDefinition) => {
|
||||
set(isRecordGroupTableSectionToggledState(recordGroup.id), true);
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(null);
|
||||
if (!selectFieldMetadataItem) {
|
||||
return;
|
||||
}
|
||||
createNewIndexRecord({
|
||||
[selectFieldMetadataItem.name]: recordGroup.value,
|
||||
});
|
||||
closeDropdown();
|
||||
},
|
||||
[
|
||||
isRecordGroupTableSectionToggledState,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
selectFieldMetadataItem,
|
||||
createNewIndexRecord,
|
||||
closeDropdown,
|
||||
],
|
||||
);
|
||||
|
||||
if (!selectFieldMetadataItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownWidth="200px"
|
||||
dropdownPlacement="bottom-start"
|
||||
clickableComponent={clickableComponent}
|
||||
dropdownId={dropdownId}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{recordGroupIds.map((recordGroupId) => (
|
||||
<RecordIndexPageKanbanAddMenuItem
|
||||
key={recordGroupId}
|
||||
columnId={recordGroupId}
|
||||
onItemClick={handleCreateNewTableRecordInGroup}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -9,7 +9,7 @@ import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||
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 { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
@ -110,7 +110,7 @@ export const MultipleRecordPicker = ({
|
||||
containerRef={containerRef}
|
||||
onClickOutside={onClickOutside}
|
||||
/>
|
||||
<DropdownMenu ref={containerRef} data-select-disable width={200}>
|
||||
<DropdownContent ref={containerRef}>
|
||||
{layoutDirection === 'search-bar-on-bottom' && (
|
||||
<>
|
||||
{createNewButtonSection}
|
||||
@ -124,7 +124,7 @@ export const MultipleRecordPicker = ({
|
||||
{createNewButtonSection}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
</MultipleRecordPickerComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
SingleRecordPickerMenuItemsWithSearchProps,
|
||||
} from '@/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItemsWithSearch';
|
||||
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@ -49,7 +49,7 @@ export const SingleRecordPicker = ({
|
||||
<SingleRecordPickerComponentInstanceContext.Provider
|
||||
value={{ instanceId: componentInstanceId }}
|
||||
>
|
||||
<DropdownMenu ref={containerRef} data-select-disable>
|
||||
<DropdownContent ref={containerRef}>
|
||||
<SingleRecordPickerMenuItemsWithSearch
|
||||
{...{
|
||||
EmptyIcon,
|
||||
@ -62,7 +62,7 @@ export const SingleRecordPicker = ({
|
||||
layoutDirection,
|
||||
}}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
</SingleRecordPickerComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -29,6 +29,7 @@ import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/ge
|
||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
@ -246,21 +247,23 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconUnlink}
|
||||
text="Detach"
|
||||
onClick={handleDetach}
|
||||
/>
|
||||
{!isAccountOwnerRelation && (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
onClick={handleDelete}
|
||||
LeftIcon={IconUnlink}
|
||||
text="Detach"
|
||||
onClick={handleDetach}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
{!isAccountOwnerRelation && (
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownScopeId }}
|
||||
/>
|
||||
|
||||
@ -2,13 +2,14 @@ import { RecordTableColumnAggregateFooterAggregateOperationMenuItems } from '@/o
|
||||
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
|
||||
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useContext } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { IconChevronLeft } from 'twenty-ui/display';
|
||||
|
||||
export const RecordTableColumnAggregateFooterDropdownSubmenuContent = ({
|
||||
@ -32,7 +33,7 @@ export const RecordTableColumnAggregateFooterDropdownSubmenuContent = ({
|
||||
TableOptionsHotkeyScope.Dropdown,
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -48,6 +49,6 @@ export const RecordTableColumnAggregateFooterDropdownSubmenuContent = ({
|
||||
aggregateOperations={aggregateOperations}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,16 +5,17 @@ import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record
|
||||
import { useViewFieldAggregateOperation } from '@/object-record/record-table/record-table-footer/hooks/useViewFieldAggregateOperation';
|
||||
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
|
||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { IconCheck } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const RecordTableColumnAggregateFooterMenuContent = () => {
|
||||
const {
|
||||
@ -64,7 +65,7 @@ export const RecordTableColumnAggregateFooterMenuContent = () => {
|
||||
} = useViewFieldAggregateOperation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
@ -116,6 +117,6 @@ export const RecordTableColumnAggregateFooterMenuContent = () => {
|
||||
aria-selected={!isDefined(currentViewFieldAggregateOperation)}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useOpenRecordFilterChipFromTableHeader } from '@/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader';
|
||||
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
@ -107,43 +108,45 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
||||
const canHide = column.isLabelIdentifier !== true;
|
||||
|
||||
return (
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
{isFilterable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconFilter}
|
||||
onClick={handleFilterClick}
|
||||
text={t`Filter`}
|
||||
/>
|
||||
)}
|
||||
{isSortable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconSortDescending}
|
||||
onClick={handleSortClick}
|
||||
text={t`Sort`}
|
||||
/>
|
||||
)}
|
||||
{showSeparator && <DropdownMenuSeparator />}
|
||||
{canMoveLeft && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowLeft}
|
||||
onClick={handleColumnMoveLeft}
|
||||
text={t`Move left`}
|
||||
/>
|
||||
)}
|
||||
{canMoveRight && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowRight}
|
||||
onClick={handleColumnMoveRight}
|
||||
text={t`Move right`}
|
||||
/>
|
||||
)}
|
||||
{canHide && (
|
||||
<MenuItem
|
||||
LeftIcon={IconEyeOff}
|
||||
onClick={handleColumnVisibility}
|
||||
text={t`Hide`}
|
||||
/>
|
||||
)}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
{isFilterable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconFilter}
|
||||
onClick={handleFilterClick}
|
||||
text={t`Filter`}
|
||||
/>
|
||||
)}
|
||||
{isSortable && (
|
||||
<MenuItem
|
||||
LeftIcon={IconSortDescending}
|
||||
onClick={handleSortClick}
|
||||
text={t`Sort`}
|
||||
/>
|
||||
)}
|
||||
{showSeparator && <DropdownMenuSeparator />}
|
||||
{canMoveLeft && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowLeft}
|
||||
onClick={handleColumnMoveLeft}
|
||||
text={t`Move left`}
|
||||
/>
|
||||
)}
|
||||
{canMoveRight && (
|
||||
<MenuItem
|
||||
LeftIcon={IconArrowRight}
|
||||
onClick={handleColumnMoveRight}
|
||||
text={t`Move right`}
|
||||
/>
|
||||
)}
|
||||
{canHide && (
|
||||
<MenuItem
|
||||
LeftIcon={IconEyeOff}
|
||||
onClick={handleColumnVisibility}
|
||||
text={t`Hide`}
|
||||
/>
|
||||
)}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,15 +8,16 @@ import { useTableColumns } from '@/object-record/record-table/hooks/useTableColu
|
||||
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { IconSettings, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
export const RecordTableHeaderPlusButtonContent = () => {
|
||||
const { t } = useLingui();
|
||||
@ -45,7 +46,7 @@ export const RecordTableHeaderPlusButtonContent = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenTableColumns.map((column) => (
|
||||
<MenuItem
|
||||
@ -70,6 +71,6 @@ export const RecordTableHeaderPlusButtonContent = () => {
|
||||
<MenuItem LeftIcon={IconSettings} text={t`Customize fields`} />
|
||||
</UndecoratedLink>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { Key } from 'ts-key-enum';
|
||||
import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
@ -80,47 +81,49 @@ export const MultipleSelectDropdown = ({
|
||||
const selectableItemIds = itemsInDropdown.map((item) => item.id);
|
||||
|
||||
return (
|
||||
<SelectableList
|
||||
selectableListInstanceId={selectableListId}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight width="auto">
|
||||
{itemsInDropdown?.map((item) => {
|
||||
return (
|
||||
<SelectableListItem
|
||||
itemId={item.id}
|
||||
onEnter={() => {
|
||||
resetSelectedItem();
|
||||
handleItemSelectChange(item, !item.isSelected);
|
||||
}}
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={item.id}
|
||||
selected={item.isSelected}
|
||||
isKeySelected={item.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
<DropdownContent>
|
||||
<SelectableList
|
||||
selectableListInstanceId={selectableListId}
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={hotkeyScope}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight width="auto">
|
||||
{itemsInDropdown?.map((item) => {
|
||||
return (
|
||||
<SelectableListItem
|
||||
itemId={item.id}
|
||||
onEnter={() => {
|
||||
resetSelectedItem();
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
handleItemSelectChange(item, !item.isSelected);
|
||||
}}
|
||||
avatar={
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
LeftIcon={item.AvatarIcon}
|
||||
avatarType={item.avatarType}
|
||||
isIconInverted={item.isIconInverted}
|
||||
placeholderColorSeed={item.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
})}
|
||||
{showNoResult && <MenuItem text="No results" />}
|
||||
{loadingItems && <DropdownMenuSkeletonItem />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={item.id}
|
||||
selected={item.isSelected}
|
||||
isKeySelected={item.id === selectedItemId}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
resetSelectedItem();
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
}}
|
||||
avatar={
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
LeftIcon={item.AvatarIcon}
|
||||
avatarType={item.avatarType}
|
||||
isIconInverted={item.isIconInverted}
|
||||
placeholderColorSeed={item.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
);
|
||||
})}
|
||||
{showNoResult && <MenuItem text="No results" />}
|
||||
{loadingItems && <DropdownMenuSkeletonItem />}
|
||||
</DropdownMenuItemsContainer>
|
||||
</SelectableList>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
|
||||
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
@ -54,45 +55,46 @@ export const SettingsAccountsRowDropdownMenu = ({
|
||||
clickableComponent={
|
||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconMail}
|
||||
text={t`Emails settings`}
|
||||
onClick={() => {
|
||||
navigate(SettingsPath.AccountsEmails);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
LeftIcon={IconCalendarEvent}
|
||||
text={t`Calendar settings`}
|
||||
onClick={() => {
|
||||
navigate(SettingsPath.AccountsCalendars);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
{account.authFailedAt && (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconRefresh}
|
||||
text={t`Reconnect`}
|
||||
LeftIcon={IconMail}
|
||||
text={t`Emails settings`}
|
||||
onClick={() => {
|
||||
triggerApisOAuth(account.provider);
|
||||
navigate(SettingsPath.AccountsEmails);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Remove account`}
|
||||
onClick={() => {
|
||||
closeDropdown();
|
||||
openModal(DELETE_ACCOUNT_MODAL_ID);
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconCalendarEvent}
|
||||
text={t`Calendar settings`}
|
||||
onClick={() => {
|
||||
navigate(SettingsPath.AccountsCalendars);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
{account.authFailedAt && (
|
||||
<MenuItem
|
||||
LeftIcon={IconRefresh}
|
||||
text={t`Reconnect`}
|
||||
onClick={() => {
|
||||
triggerApisOAuth(account.provider);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Remove account`}
|
||||
onClick={() => {
|
||||
closeDropdown();
|
||||
openModal(DELETE_ACCOUNT_MODAL_ID);
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
|
||||
@ -4,6 +4,7 @@ import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { ConfigVariableValue } from 'twenty-shared/types';
|
||||
import { MenuItemMultiSelect } from 'twenty-ui/navigation';
|
||||
@ -115,19 +116,21 @@ export const ConfigVariableDatabaseInput = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{selectOptions.map((option) => (
|
||||
<MenuItemMultiSelect
|
||||
key={option.value}
|
||||
text={option.label}
|
||||
selected={isValueSelected(option.value)}
|
||||
className="config-variable-array-menu-item-multi-select"
|
||||
onSelectChange={() =>
|
||||
handleMultiSelectChange(option.value)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{selectOptions.map((option) => (
|
||||
<MenuItemMultiSelect
|
||||
key={option.value}
|
||||
text={option.label}
|
||||
selected={isValueSelected(option.value)}
|
||||
className="config-variable-array-menu-item-multi-select"
|
||||
onSelectChange={() =>
|
||||
handleMultiSelectChange(option.value)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@ -3,6 +3,7 @@ import { ConfigVariableSourceOptions } from '@/settings/admin-panel/config-varia
|
||||
import { ConfigVariableFilterCategory } from '@/settings/admin-panel/config-variables/types/ConfigVariableFilterCategory';
|
||||
import { ConfigVariableGroupFilter } from '@/settings/admin-panel/config-variables/types/ConfigVariableGroupFilter';
|
||||
import { ConfigVariableSourceFilter } from '@/settings/admin-panel/config-variables/types/ConfigVariableSourceFilter';
|
||||
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';
|
||||
@ -48,7 +49,7 @@ export const ConfigVariableOptionsDropdownContent = ({
|
||||
|
||||
if (!selectedCategory) {
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelectTag
|
||||
text={t`Source`}
|
||||
@ -85,12 +86,12 @@ export const ConfigVariableOptionsDropdownContent = ({
|
||||
onClick={() => onShowHiddenChange(!showHiddenGroupVariables)}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -136,6 +137,6 @@ export const ConfigVariableOptionsDropdownContent = ({
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconChevronDown } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -110,7 +110,7 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
|
||||
</StyledButtonContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenu>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledMenuItemWrapper>
|
||||
<StyledMenuItem
|
||||
@ -128,7 +128,7 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
|
||||
/>
|
||||
</StyledMenuItemWrapper>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
/>
|
||||
|
||||
@ -3,13 +3,12 @@ import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsW
|
||||
import { OPTION_VALUE_MAXIMUM_LENGTH } from '@/settings/data-model/constants/OptionValueMaximumLength';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||
import {
|
||||
ColorSample,
|
||||
IconCheck,
|
||||
@ -19,8 +18,9 @@ import {
|
||||
IconX,
|
||||
} from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { MAIN_COLOR_NAMES } from 'twenty-ui/theme';
|
||||
import { MenuItem, MenuItemSelectColor } from 'twenty-ui/navigation';
|
||||
import { MAIN_COLOR_NAMES } from 'twenty-ui/theme';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||
|
||||
type SettingsDataModelFieldSelectFormOptionRowProps = {
|
||||
className?: string;
|
||||
@ -120,19 +120,21 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
dropdownHotkeyScope={{ scope: SELECT_COLOR_DROPDOWN_ID }}
|
||||
clickableComponent={<StyledColorSample colorName={option.color} />}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{MAIN_COLOR_NAMES.map((colorName) => (
|
||||
<MenuItemSelectColor
|
||||
key={colorName}
|
||||
onClick={() => {
|
||||
onChange({ ...option, color: colorName });
|
||||
closeColorDropdown();
|
||||
}}
|
||||
color={colorName}
|
||||
selected={colorName === option.color}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{MAIN_COLOR_NAMES.map((colorName) => (
|
||||
<MenuItemSelectColor
|
||||
key={colorName}
|
||||
onClick={() => {
|
||||
onChange({ ...option, color: colorName });
|
||||
closeColorDropdown();
|
||||
}}
|
||||
color={colorName}
|
||||
selected={colorName === option.color}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
<StyledOptionInput
|
||||
@ -163,7 +165,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
<StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} />
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenu>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{isDefault ? (
|
||||
<MenuItem
|
||||
@ -196,7 +198,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</StyledRow>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import {
|
||||
IconArchive,
|
||||
@ -55,29 +57,30 @@ export const SettingsObjectFieldActiveActionDropdown = ({
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={isCustomField ? 'Edit' : 'View'}
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
{!!onSetAsLabelIdentifier && (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Set as record text"
|
||||
LeftIcon={IconTextSize}
|
||||
onClick={handleSetAsLabelIdentifier}
|
||||
text={isCustomField ? 'Edit' : 'View'}
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
)}
|
||||
{!!onDeactivate && (
|
||||
<MenuItem
|
||||
text="Deactivate"
|
||||
LeftIcon={IconArchive}
|
||||
onClick={handleDeactivate}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
{!!onSetAsLabelIdentifier && (
|
||||
<MenuItem
|
||||
text="Set as record text"
|
||||
LeftIcon={IconTextSize}
|
||||
onClick={handleSetAsLabelIdentifier}
|
||||
/>
|
||||
)}
|
||||
{!!onDeactivate && (
|
||||
<MenuItem
|
||||
text="Deactivate"
|
||||
LeftIcon={IconArchive}
|
||||
onClick={handleDeactivate}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
/>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
@ -60,28 +62,29 @@ export const SettingsObjectFieldInactiveActionDropdown = ({
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={isCustomField ? t`Edit` : t`View`}
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<MenuItem
|
||||
text={t`Activate`}
|
||||
LeftIcon={IconArchiveOff}
|
||||
onClick={handleActivate}
|
||||
/>
|
||||
{isDeletable && (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={t`Delete`}
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
onClick={handleDelete}
|
||||
text={isCustomField ? t`Edit` : t`View`}
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={t`Activate`}
|
||||
LeftIcon={IconArchiveOff}
|
||||
onClick={handleActivate}
|
||||
/>
|
||||
{isDeletable && (
|
||||
<MenuItem
|
||||
text={t`Delete`}
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownId,
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
|
||||
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag';
|
||||
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import {
|
||||
IconArchive,
|
||||
IconDotsVertical,
|
||||
IconPencil,
|
||||
useIcons,
|
||||
} from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
type SettingsObjectSummaryCardProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
iconKey?: string;
|
||||
name: string;
|
||||
onDeactivate: () => void;
|
||||
onEdit: () => void;
|
||||
};
|
||||
|
||||
const StyledObjectTypeTag = styled(SettingsDataModelObjectTypeTag)`
|
||||
box-sizing: border-box;
|
||||
height: ${({ theme }) => theme.spacing(6)};
|
||||
`;
|
||||
|
||||
const dropdownId = 'settings-object-edit-about-menu-dropdown';
|
||||
|
||||
export const SettingsObjectSummaryCard = ({
|
||||
objectMetadataItem,
|
||||
iconKey = '',
|
||||
name,
|
||||
onDeactivate,
|
||||
onEdit,
|
||||
}: SettingsObjectSummaryCardProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const Icon = getIcon(iconKey);
|
||||
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
|
||||
const handleEdit = () => {
|
||||
onEdit();
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleDeactivate = () => {
|
||||
onDeactivate();
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
|
||||
|
||||
return (
|
||||
<SettingsSummaryCard
|
||||
title={
|
||||
<>
|
||||
{!!Icon && <Icon size={theme.icon.size.md} />}
|
||||
{name}
|
||||
</>
|
||||
}
|
||||
rightComponent={
|
||||
<>
|
||||
<StyledObjectTypeTag objectTypeLabel={objectTypeLabel} />
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={
|
||||
<LightIconButton
|
||||
aria-label="Object Options"
|
||||
Icon={IconDotsVertical}
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Edit"
|
||||
LeftIcon={IconPencil}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Deactivate"
|
||||
LeftIcon={IconArchive}
|
||||
onClick={handleDeactivate}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownId,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { IconArchiveOff, IconDotsVertical, IconTrash } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
@ -42,23 +44,24 @@ export const SettingsObjectInactiveMenuDropDown = ({
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Activate"
|
||||
LeftIcon={IconArchiveOff}
|
||||
onClick={handleActivate}
|
||||
/>
|
||||
{isCustomObject && (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Delete"
|
||||
LeftIcon={IconTrash}
|
||||
accent="danger"
|
||||
onClick={handleDelete}
|
||||
text="Activate"
|
||||
LeftIcon={IconArchiveOff}
|
||||
onClick={handleActivate}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
{isCustomObject && (
|
||||
<MenuItem
|
||||
text="Delete"
|
||||
LeftIcon={IconTrash}
|
||||
accent="danger"
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
/>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
|
||||
import { SettingsIntegrationDatabaseConnectionSyncStatus } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSyncStatus';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconDotsVertical, IconPencil, IconTrash } from 'twenty-ui/display';
|
||||
@ -58,16 +59,18 @@ export const SettingsIntegrationDatabaseConnectionSummaryCard = ({
|
||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Remove"
|
||||
onClick={onRemove}
|
||||
/>
|
||||
<UndecoratedLink to="./edit">
|
||||
<MenuItem LeftIcon={IconPencil} text="Edit" />
|
||||
</UndecoratedLink>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Remove"
|
||||
onClick={onRemove}
|
||||
/>
|
||||
<UndecoratedLink to="./edit">
|
||||
<MenuItem LeftIcon={IconPencil} text="Edit" />
|
||||
</UndecoratedLink>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords';
|
||||
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent';
|
||||
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';
|
||||
@ -38,7 +38,7 @@ export const SettingsRoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownContent>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={handleSearchFilterChange}
|
||||
@ -53,6 +53,6 @@ export const SettingsRoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
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';
|
||||
@ -37,7 +37,7 @@ export const SettingsRolePermissionsObjectLevelObjectPickerDropdownContent = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownContent>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={handleSearchFilterChange}
|
||||
@ -54,6 +54,6 @@ export const SettingsRolePermissionsObjectLevelObjectPickerDropdownContent = ({
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdent
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
@ -72,28 +73,29 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
||||
clickableComponent={
|
||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
accent="default"
|
||||
LeftIcon={IconArchive}
|
||||
text={SSOIdp.status === 'Active' ? t`Deactivate` : t`Activate`}
|
||||
onClick={() => {
|
||||
toggleSSOIdentityProviderStatus(SSOIdp.id);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Delete`}
|
||||
onClick={() => {
|
||||
handleDeleteSSOIdentityProvider(SSOIdp.id);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
accent="default"
|
||||
LeftIcon={IconArchive}
|
||||
text={SSOIdp.status === 'Active' ? t`Deactivate` : t`Activate`}
|
||||
onClick={() => {
|
||||
toggleSSOIdentityProviderStatus(SSOIdp.id);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Delete`}
|
||||
onClick={() => {
|
||||
handleDeleteSSOIdentityProvider(SSOIdp.id);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@ import { approvedAccessDomainsState } from '@/settings/security/states/ApprovedA
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { UnwrapRecoilValue, useSetRecoilState } from 'recoil';
|
||||
@ -61,19 +62,20 @@ export const SettingsSecurityApprovedAccessDomainRowDropdownMenu = ({
|
||||
clickableComponent={
|
||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||
}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
onClick={() => {
|
||||
handleDeleteApprovedAccessDomain();
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
onClick={() => {
|
||||
handleDeleteApprovedAccessDomain();
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { EnvironmentVariable } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
@ -109,24 +110,26 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={'Edit'}
|
||||
LeftIcon={IconPencil}
|
||||
onClick={() => {
|
||||
setEditMode(true);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text={'Delete'}
|
||||
LeftIcon={IconTrash}
|
||||
onClick={() => {
|
||||
onDelete();
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={'Edit'}
|
||||
LeftIcon={IconPencil}
|
||||
onClick={() => {
|
||||
setEditMode(true);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text={'Delete'}
|
||||
LeftIcon={IconTrash}
|
||||
onClick={() => {
|
||||
onDelete();
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropDownId }}
|
||||
/>
|
||||
|
||||
@ -2,6 +2,7 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey';
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
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';
|
||||
@ -59,7 +60,7 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -76,7 +77,7 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight width={200}>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<MenuItemSelect
|
||||
selected={selectedValue?.value === DO_NOT_IMPORT_OPTION_KEY}
|
||||
onClick={onDoNotImportSelect}
|
||||
@ -98,6 +99,6 @@ export const MatchColumnSelectFieldSelectDropdownContent = ({
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
|
||||
import { getSubFieldOptionKey } from '@/object-record/spreadsheet-import/utils/getSubFieldOptionKey';
|
||||
import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs';
|
||||
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';
|
||||
@ -68,7 +69,7 @@ export const MatchColumnSelectSubFieldSelectDropdownContent = ({
|
||||
.filter((subFieldName) => subFieldName.includes(searchFilter));
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
@ -85,7 +86,7 @@ export const MatchColumnSelectSubFieldSelectDropdownContent = ({
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight width={200}>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{subFieldNamesThatExistInOptions.map((subFieldName) => (
|
||||
<MenuItem
|
||||
key={subFieldName}
|
||||
@ -104,6 +105,6 @@ export const MatchColumnSelectSubFieldSelectDropdownContent = ({
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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';
|
||||
@ -48,7 +48,7 @@ export const SubMatchingSelectInput = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu ref={containerRef} data-select-disable>
|
||||
<DropdownContent ref={containerRef}>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(e) => setSearchFilter(e.target.value)}
|
||||
@ -67,6 +67,6 @@ export const SubMatchingSelectInput = ({
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,8 +4,8 @@ import { SupportButtonSkeletonLoader } from '@/support/components/SupportButtonS
|
||||
import { useSupportChat } from '@/support/hooks/useSupportChat';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Button, LightIconButton } from 'twenty-ui/input';
|
||||
import { IconHelpCircle } from 'twenty-ui/display';
|
||||
import { Button, LightIconButton } from 'twenty-ui/input';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { SupportButton } from '@/support/components/SupportButton';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { IconHelpCircle, IconMessage } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
@ -26,20 +28,21 @@ export const SupportDropdown = () => {
|
||||
dropdownPlacement="top-start"
|
||||
dropdownOffset={{ x: 0, y: -28 }}
|
||||
clickableComponent={<SupportButton />}
|
||||
dropdownWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Talk to us"
|
||||
LeftIcon={IconMessage}
|
||||
onClick={handleTalkToUs}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Documentation"
|
||||
LeftIcon={IconHelpCircle}
|
||||
onClick={handleUserGuide}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Talk to us"
|
||||
LeftIcon={IconMessage}
|
||||
onClick={handleTalkToUs}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Documentation"
|
||||
LeftIcon={IconHelpCircle}
|
||||
onClick={handleUserGuide}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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={() => {
|
||||
|
||||
@ -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 }}
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const ICON_PICKER_DROPDOWN_CONTENT_WIDTH = 176;
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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>
|
||||
}
|
||||
/>
|
||||
),
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
export enum GenericDropdownContentWidth {
|
||||
Narrow = 160,
|
||||
Medium = 200,
|
||||
Large = 240,
|
||||
ExtraLarge = 320,
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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']};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user