diff --git a/.cursor/rules/code-style.mdc b/.cursor/rules/code-style.mdc
index b3df1da557..77473f09a0 100644
--- a/.cursor/rules/code-style.mdc
+++ b/.cursor/rules/code-style.mdc
@@ -80,6 +80,38 @@ const processUserData = (
};
```
+## Collection Transformations
+```typescript
+// ✅ Prefer array transformations for simple mapping, filtering, and classification
+const activeUsers = users.filter((user) => user.isActive === true);
+const userNames = users.map((user) => user.name);
+
+const { payingUsers, nonPayingUsers } = users.reduce<{
+ payingUsers: User[];
+ nonPayingUsers: User[];
+}>(
+ (accumulator, user) => {
+ if (user.isPaying === true) {
+ accumulator.payingUsers.push(user);
+ } else {
+ accumulator.nonPayingUsers.push(user);
+ }
+
+ return accumulator;
+ },
+ { payingUsers: [], nonPayingUsers: [] },
+);
+
+// ❌ Avoid manual loops when map, filter, or reduce keeps the code clear
+const payingUsers: User[] = [];
+
+for (const user of users) {
+ if (user.isPaying === true) {
+ payingUsers.push(user);
+ }
+}
+```
+
## Comments
```typescript
// ✅ Use short-form comments, NOT JSDoc blocks
diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx
index 5c66cdcf64..aa6faeeb22 100644
--- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx
+++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/chart-settings/ChartSettingItem.tsx
@@ -190,6 +190,7 @@ export const ChartSettingItem = ({
}
dropdownPlacement="bottom-end"
+ dropdownOffset={{ y: 4 }}
description={getChartSettingsValues(item.id) as string}
contextualTextPosition={'right'}
hasSubMenu
diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx
index 20e0369c48..4b7766010b 100644
--- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/ChartDataSourceDropdownContent.tsx
@@ -6,9 +6,10 @@ import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/
import { useResetChartDraftFiltersSettings } from '@/side-panel/pages/page-layout/hooks/useResetChartDraftFiltersSettings';
import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode';
-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 {
+ StyledPageLayoutDropdownContentContainer,
+ StyledPageLayoutDropdownMenuItemsContainer,
+} from '@/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
@@ -19,19 +20,13 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { t } from '@lingui/core/macro';
-import { Trans } from '@lingui/react/macro';
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
-import { IconChevronLeft, IconSettings } from 'twenty-ui/icon';
-import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
+import { MenuItemSelect } from 'twenty-ui/navigation';
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
-const ADVANCED_OBJECTS_MENU_ITEM_ID = 'advanced-objects';
-
export const ChartDataSourceDropdownContent = () => {
const [searchQuery, setSearchQuery] = useState('');
- const [isAdvancedObjectsMenuOpened, setIsAdvancedObjectsMenuOpened] =
- useState(false);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { pageLayoutId } = usePageLayoutIdFromContextStore();
@@ -51,21 +46,25 @@ export const ChartDataSourceDropdownContent = () => {
dropdownId,
);
- const objectsWithReadAccess = filterReadableActiveObjectMetadataItems(
- objectMetadataItems,
- objectPermissionsByObjectMetadataId,
- );
+ const searchableObjects = useMemo(() => {
+ const objectsWithReadAccess = filterReadableActiveObjectMetadataItems(
+ objectMetadataItems,
+ objectPermissionsByObjectMetadataId,
+ );
- const regularObjects = objectsWithReadAccess
- .filter((item) => !item.isSystem)
- .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
+ const regularObjects = objectsWithReadAccess
+ .filter((item) => item.isSystem === false)
+ .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
- const systemObjects = objectsWithReadAccess
- .filter((item) => item.isSystem)
- .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
+ const systemObjects = objectsWithReadAccess
+ .filter((item) => item.isSystem === true)
+ .sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
+
+ return [...regularObjects, ...systemObjects];
+ }, [objectMetadataItems, objectPermissionsByObjectMetadataId]);
const availableObjectMetadataItems = filterBySearchQuery({
- items: isAdvancedObjectsMenuOpened ? systemObjects : regularObjects,
+ items: searchableObjects,
searchQuery,
getSearchableValues: (item) => [item.labelPlural, item.namePlural],
});
@@ -104,30 +103,8 @@ export const ChartDataSourceDropdownContent = () => {
closeDropdown();
};
- const handleAdvancedObjectsClick = () => {
- setIsAdvancedObjectsMenuOpened(true);
- setSearchQuery('');
- };
-
- const handleBack = () => {
- setIsAdvancedObjectsMenuOpened(false);
- setSearchQuery('');
- };
-
return (
- <>
- {isAdvancedObjectsMenuOpened && (
-
- }
- >
- Advanced objects
-
- )}
+
{
value={searchQuery}
/>
-
+
objectMetadataItem.id,
- ),
- ...(!isAdvancedObjectsMenuOpened
- ? [ADVANCED_OBJECTS_MENU_ITEM_ID]
- : []),
- ]}
+ selectableItemIdArray={availableObjectMetadataItems.map(
+ (objectMetadataItem) => objectMetadataItem.id,
+ )}
>
{availableObjectMetadataItems.map((objectMetadataItem) => (
{
/>
))}
- {!isAdvancedObjectsMenuOpened && (
-
-
-
- )}
-
- >
+
+
);
};
diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx
index 1778703d3c..a7f6ec35af 100644
--- a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/FieldWidgetFieldDropdownContent.tsx
@@ -11,9 +11,10 @@ import {
import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore';
import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode';
-import { DropdownAdvancedSectionHeader } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionHeader';
-import { DropdownAdvancedSectionMenuItem } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionMenuItem';
-import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import {
+ StyledPageLayoutDropdownContentContainer,
+ StyledPageLayoutDropdownMenuItemsContainer,
+} from '@/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
@@ -24,7 +25,6 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { t } from '@lingui/core/macro';
-import { isNonEmptyString } from '@sniptt/guards';
import { useMemo, useState } from 'react';
import { useIcons } from 'twenty-ui/icon';
import { MenuItemSelect } from 'twenty-ui/navigation';
@@ -33,7 +33,6 @@ import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
export const FieldWidgetFieldDropdownContent = () => {
const [searchQuery, setSearchQuery] = useState('');
- const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
const { pageLayoutId, objectNameSingular } =
usePageLayoutIdFromContextStore();
@@ -51,25 +50,30 @@ export const FieldWidgetFieldDropdownContent = () => {
const { objectMetadataItems } = useObjectMetadataItems();
- const advancedFieldMetadataItems = useMemo(
+ const { advancedFieldMetadataItems, regularFieldMetadataItems } = useMemo(
() =>
- allFieldWidgetFieldMetadataItems.filter((fieldMetadataItem) =>
- isAdvancedRelationFieldMetadataItem(
- fieldMetadataItem,
- objectMetadataItems,
- ),
- ),
- [allFieldWidgetFieldMetadataItems, objectMetadataItems],
- );
-
- const regularFieldMetadataItems = useMemo(
- () =>
- allFieldWidgetFieldMetadataItems.filter(
- (fieldMetadataItem) =>
- !isAdvancedRelationFieldMetadataItem(
+ allFieldWidgetFieldMetadataItems.reduce<{
+ advancedFieldMetadataItems: typeof allFieldWidgetFieldMetadataItems;
+ regularFieldMetadataItems: typeof allFieldWidgetFieldMetadataItems;
+ }>(
+ (accumulator, fieldMetadataItem) => {
+ const isAdvancedField = isAdvancedRelationFieldMetadataItem(
fieldMetadataItem,
objectMetadataItems,
- ),
+ );
+
+ if (isAdvancedField) {
+ accumulator.advancedFieldMetadataItems.push(fieldMetadataItem);
+ } else {
+ accumulator.regularFieldMetadataItems.push(fieldMetadataItem);
+ }
+
+ return accumulator;
+ },
+ {
+ advancedFieldMetadataItems: [],
+ regularFieldMetadataItems: [],
+ },
),
[allFieldWidgetFieldMetadataItems, objectMetadataItems],
);
@@ -92,30 +96,17 @@ export const FieldWidgetFieldDropdownContent = () => {
const { getIcon } = useIcons();
+ const searchableFieldMetadataItems = [
+ ...regularFieldMetadataItems,
+ ...advancedFieldMetadataItems,
+ ];
+
const availableFields = filterBySearchQuery({
- items: isAdvancedOpen
- ? advancedFieldMetadataItems
- : regularFieldMetadataItems,
+ items: searchableFieldMetadataItems,
searchQuery,
getSearchableValues: (item) => [item.label],
});
- const shouldShowAdvanced =
- !isAdvancedOpen &&
- advancedFieldMetadataItems.length > 0 &&
- (!isNonEmptyString(searchQuery) ||
- searchQuery.toLowerCase().includes('advanced'));
-
- const handleOpenAdvanced = () => {
- setIsAdvancedOpen(true);
- setSearchQuery('');
- };
-
- const handleBackFromAdvanced = () => {
- setIsAdvancedOpen(false);
- setSearchQuery('');
- };
-
const { fieldMetadataItem: currentFieldMetadataItem } =
useFieldMetadataItemById(currentFieldMetadataId ?? '');
@@ -146,7 +137,7 @@ export const FieldWidgetFieldDropdownContent = () => {
},
});
- if (widgetInEditMode && selectedField) {
+ if (isDefined(widgetInEditMode) && isDefined(selectedField)) {
updatePageLayoutWidget(widgetInEditMode.id, {
title: selectedField.label,
});
@@ -156,10 +147,7 @@ export const FieldWidgetFieldDropdownContent = () => {
};
return (
- <>
- {isAdvancedOpen && (
-
- )}
+
{
value={searchQuery}
/>
-
+
{
))}
- {shouldShowAdvanced && (
-
- )}
-
- >
+
+
);
};
diff --git a/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer.tsx b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer.tsx
new file mode 100644
index 0000000000..17320e283c
--- /dev/null
+++ b/packages/twenty-front/src/modules/side-panel/pages/page-layout/components/dropdown-content/PageLayoutDropdownContentContainer.tsx
@@ -0,0 +1,19 @@
+import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { styled } from '@linaria/react';
+
+const PAGE_LAYOUT_DROPDOWN_CONTENT_MAX_HEIGHT = 340;
+
+export const StyledPageLayoutDropdownContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ max-height: ${PAGE_LAYOUT_DROPDOWN_CONTENT_MAX_HEIGHT}px;
+ min-height: 0;
+ overflow: hidden;
+`;
+
+export const StyledPageLayoutDropdownMenuItemsContainer = styled(
+ DropdownMenuItemsContainer,
+)`
+ flex: 1;
+ min-height: 0;
+`;
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx
index e8509a2317..1586bece65 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords.tsx
@@ -204,6 +204,7 @@ export const WorkflowEditActionFindRecords = ({
dropdownComponents={
!isFormDisabled && (
)
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx
index 159932a25d..44b46e6167 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowObjectDropdownContent.tsx
@@ -1,36 +1,38 @@
import { ObjectMetadataIcon } from '@/object-metadata/components/ObjectMetadataIcon';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
-import { DropdownAdvancedSectionHeader } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionHeader';
-import { DropdownAdvancedSectionMenuItem } from '@/ui/layout/dropdown/components/DropdownAdvancedSectionMenuItem';
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 { isNonEmptyString } from '@sniptt/guards';
+import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
+import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
+import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
+import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useState } from 'react';
import { MenuItem } from 'twenty-ui/navigation';
type WorkflowObjectDropdownContentProps = {
+ dropdownId: string;
onOptionClick: (value: string) => void;
- showAdvancedOption?: boolean;
};
export const WorkflowObjectDropdownContent = ({
+ dropdownId,
onOptionClick,
- showAdvancedOption = true,
}: WorkflowObjectDropdownContentProps) => {
const [searchInputValue, setSearchInputValue] = useState('');
- const [isSystemObjectsOpen, setIsSystemObjectsOpen] = useState(false);
const { objectMetadataItems } = useFilteredObjectMetadataItems();
const nonSystemObjectMetadataItems = objectMetadataItems.filter(
(objectMetadataItem) =>
- objectMetadataItem.isActive && !objectMetadataItem.isSystem,
+ objectMetadataItem.isActive === true &&
+ objectMetadataItem.isSystem === false,
);
const systemObjectMetadataItems = objectMetadataItems.filter(
(objectMetadataItem) =>
- objectMetadataItem.isActive && objectMetadataItem.isSystem,
+ objectMetadataItem.isActive === true &&
+ objectMetadataItem.isSystem === true,
);
const matchesSearchFilter = (
@@ -52,12 +54,6 @@ export const WorkflowObjectDropdownContent = ({
const searchInputLowerCase = searchInputValue.toLowerCase();
- const shouldShowAdvanced =
- showAdvancedOption &&
- !isSystemObjectsOpen &&
- (!isNonEmptyString(searchInputValue) ||
- searchInputLowerCase.includes('advanced'));
-
const filteredNonSystemObjects = nonSystemObjectMetadataItems.filter(
(objectMetadataItem) =>
matchesSearchFilter(objectMetadataItem, searchInputLowerCase),
@@ -68,19 +64,19 @@ export const WorkflowObjectDropdownContent = ({
matchesSearchFilter(objectMetadataItem, searchInputLowerCase),
);
- const filteredObjects = isSystemObjectsOpen
- ? filteredSystemObjects
- : filteredNonSystemObjects;
+ const filteredObjects = [
+ ...filteredNonSystemObjects,
+ ...filteredSystemObjects,
+ ];
- const handleSystemObjectsClick = () => {
- setIsSystemObjectsOpen(true);
- setSearchInputValue('');
- };
+ const selectableItemIdArray = filteredObjects.map(
+ (objectMetadataItem) => objectMetadataItem.nameSingular,
+ );
- const handleBack = () => {
- setIsSystemObjectsOpen(false);
- setSearchInputValue('');
- };
+ const selectedItemId = useAtomComponentStateValue(
+ selectedItemIdComponentState,
+ dropdownId,
+ );
const handleSearchInputChange = (
event: React.ChangeEvent,
@@ -88,17 +84,8 @@ export const WorkflowObjectDropdownContent = ({
setSearchInputValue(event.target.value);
};
- const handleAdvancedClick = () => {
- if (!isSystemObjectsOpen) {
- handleSystemObjectsClick();
- }
- };
-
return (
- {isSystemObjectsOpen && (
-
- )}
- {filteredObjects.map((objectMetadataItem) => (
-
);
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx
index 74f378c4d9..c11448d7e7 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/pick-record-action/components/WorkflowEditActionPickRecord.tsx
@@ -259,6 +259,7 @@ export const WorkflowEditActionPickRecord = ({
dropdownComponents={
!isFormDisabled && (
)
@@ -293,6 +294,7 @@ export const WorkflowEditActionPickRecord = ({
dropdownComponents={
!isFormDisabled && (
)
diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx
index e5c92ed199..11029db6cb 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx
@@ -4,13 +4,15 @@ import { type FieldMultiSelectValue } from '@/object-record/record-field/ui/type
import { SelectControl } from '@/ui/input/components/SelectControl';
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 { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
+import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
+import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
+import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
+import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { WorkflowFieldsMultiSelect } from '@/workflow/components/WorkflowEditUpdateEventFieldsMultiSelect';
import { type WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow';
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
@@ -19,11 +21,11 @@ import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/Workflo
import { WorkflowStepFilterBuilder } from '@/workflow/workflow-steps/filters/components/WorkflowStepFilterBuilder';
import { type FilterSettings } from '@/workflow/workflow-steps/filters/types/FilterSettings';
import { styled } from '@linaria/react';
-import { Trans, useLingui } from '@lingui/react/macro';
-import { useCallback, useMemo, useState } from 'react';
+import { useLingui } from '@lingui/react/macro';
+import { useMemo, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
-import { IconChevronLeft, IconSettings } from 'twenty-ui/icon';
+import { type SelectOption } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -39,13 +41,18 @@ const StyledRecordTypeSelectContainer = styled.div<{ fullWidth?: boolean }>`
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
`;
-const filterOptionsBySearch = (
+const filterOptionsBySearch = (
options: T[],
searchValue: string,
): T[] => {
- if (!searchValue) return options;
+ if (searchValue === '') return options;
+
+ const searchValueLowerCase = searchValue.toLowerCase();
+
return options.filter((option) =>
- option.label.toLowerCase().includes(searchValue.toLowerCase()),
+ [option.label, option.value].some((searchableValue) =>
+ searchableValue.toLowerCase().includes(searchValueLowerCase),
+ ),
);
};
@@ -70,7 +77,6 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
const { getSelectIconPropsFromObjectMetadataItem } =
useObjectMetadataSelectHelpers();
const [searchInputValue, setSearchInputValue] = useState('');
- const [isSystemObjectsOpen, setIsSystemObjectsOpen] = useState(false);
const dropdownId = 'workflow-edit-trigger-record-type';
const { closeDropdown } = useCloseDropdown();
@@ -84,29 +90,46 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
const isUpsertEvent = triggerEvent.event === 'upserted';
const isFieldFilteringSupported = isUpdateEvent || isUpsertEvent;
- const defaultSelectedOption = useMemo(
+ const defaultSelectedOption = useMemo>(
() => ({ label: t`Select an option`, value: '' }),
[t],
);
- const regularObjects = objectMetadataItems
- .filter((item) => item.isActive && !item.isSystem)
- .map((item) => ({
- label: item.labelPlural,
- value: item.nameSingular,
- ...getSelectIconPropsFromObjectMetadataItem(item),
- }));
+ const { regularObjects, systemObjects } = useMemo(() => {
+ return objectMetadataItems.reduce<{
+ regularObjects: SelectOption[];
+ systemObjects: SelectOption[];
+ }>(
+ (accumulator, item) => {
+ if (item.isActive === false) {
+ return accumulator;
+ }
- const systemObjects = objectMetadataItems
- .filter((item) => item.isActive && item.isSystem)
- .map((item) => ({
- label: item.labelPlural,
- value: item.nameSingular,
- ...getSelectIconPropsFromObjectMetadataItem(item),
- }));
+ const option = {
+ label: item.labelPlural,
+ value: item.nameSingular,
+ ...getSelectIconPropsFromObjectMetadataItem(item),
+ };
+
+ if (item.isSystem === true) {
+ accumulator.systemObjects.push(option);
+ } else {
+ accumulator.regularObjects.push(option);
+ }
+
+ return accumulator;
+ },
+ { regularObjects: [], systemObjects: [] },
+ );
+ }, [getSelectIconPropsFromObjectMetadataItem, objectMetadataItems]);
+
+ const selectableOptions = useMemo(
+ () => [...regularObjects, ...systemObjects],
+ [regularObjects, systemObjects],
+ );
const selectedOption =
- [...regularObjects, ...systemObjects].find(
+ selectableOptions.find(
(option) => option.value === triggerEvent?.objectType,
) || defaultSelectedOption;
@@ -114,14 +137,19 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
(item) => item.nameSingular === selectedOption.value,
);
- const filteredRegularObjects = useMemo(
- () => filterOptionsBySearch(regularObjects, searchInputValue),
- [regularObjects, searchInputValue],
+ const filteredObjects = useMemo(
+ () => [
+ ...filterOptionsBySearch(regularObjects, searchInputValue),
+ ...filterOptionsBySearch(systemObjects, searchInputValue),
+ ],
+ [regularObjects, searchInputValue, systemObjects],
);
- const filteredSystemObjects = useMemo(
- () => filterOptionsBySearch(systemObjects, searchInputValue),
- [systemObjects, searchInputValue],
+ const selectableItemIdArray = filteredObjects.map((option) => option.value);
+
+ const selectedItemId = useAtomComponentStateValue(
+ selectedItemIdComponentState,
+ dropdownId,
);
const handleOptionClick = (value: string) => {
@@ -174,30 +202,13 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
});
};
- const handleSystemObjectsClick = () => {
- setIsSystemObjectsOpen(true);
- setSearchInputValue('');
- };
-
- const handleBack = () => {
- setIsSystemObjectsOpen(false);
- setSearchInputValue('');
- };
-
- const handleSearchInputChange = useCallback(
- (event: React.ChangeEvent) => {
- setSearchInputValue(event.target.value);
- },
- [],
- );
-
return (
<>
{t`Record Type`}
- {!triggerOptions.readonly &&
- (isSystemObjectsOpen ? (
-
-
- }
+ {!triggerOptions.readonly && (
+
+
+ setSearchInputValue(event.target.value)
+ }
+ />
+
+
+
- Advanced
-
-
-
-
- {filteredSystemObjects.map((option) => (
- (
+ handleOptionClick(option.value)}
- />
+ itemId={option.value}
+ onEnter={() => handleOptionClick(option.value)}
+ >
+ handleOptionClick(option.value)}
+ />
+
))}
-
-
- ) : (
-
-
-
-
- {filteredRegularObjects.map((option) => (
- handleOptionClick(option.value)}
- />
- ))}
- {(!searchInputValue ||
- 'advanced'.includes(
- searchInputValue.toLowerCase(),
- )) && (
-
- )}
-
-
- ))}
+
+
+
+ )}
>
}
dropdownOffset={{ y: 4 }}