From 276f1796cc3820e1d48b53ecd5c4baee0d800c6b Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 5 Jun 2025 20:50:12 +0200 Subject: [PATCH] Implemented dropdown menu section label in filter and sort (#12453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR implements a new component `DropdownMenuSectionLabel`, to be used for indicating visible and hidden fields in the multiple dropdowns that use those two sections. After : Capture d’écran 2025-06-04 à 12 49 42 Capture d’écran 2025-06-04 à 12 49 20 Capture d’écran 2025-06-04 à 12 48 44 In this PR we also fix the scrolling behavior of those two sections so that it is more natural. The height mechanism will be properly refactored by this issue : https://github.com/twentyhq/twenty/issues/11766, in the mean time this temporary modification is working : https://github.com/user-attachments/assets/c7ddb424-66b9-41e3-a6a8-a29ece09d62e Some components that weren't used are also removed : `AdvancedFilterDropdownFieldSelectMenu`, `AdvancedFilterDropdownFieldSelectMenuItem` and `AdvancedFilterDropdownSubFieldSelectMenu` Fixes https://github.com/twentyhq/core-team-issues/issues/1000 --- .../AdvancedFilterDropdownFieldSelectMenu.tsx | 81 ------ ...ancedFilterDropdownFieldSelectMenuItem.tsx | 152 ----------- ...vancedFilterDropdownSubFieldSelectMenu.tsx | 257 ------------------ .../AdvancedFilterFieldSelectMenu.tsx | 89 +++--- .../components/ObjectSortDropdownButton.tsx | 96 ++++--- .../components/DropdownMenuItemsContainer.tsx | 8 +- .../components/DropdownMenuSectionLabel.tsx | 26 ++ .../scroll/components/ScrollWrapper.tsx | 7 +- .../ViewBarFilterDropdownFieldSelectMenu.tsx | 56 ++-- 9 files changed, 187 insertions(+), 585 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenu.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenuItem.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownSubFieldSelectMenu.tsx create mode 100644 packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuSectionLabel.tsx diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenu.tsx deleted file mode 100644 index 29a8a6545..000000000 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenu.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; - -import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; - -import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; -import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; - -import { AdvancedFilterDropdownFieldSelectMenuItem } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenuItem'; -import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/constants/FilterFieldListId'; -import { useFilterDropdownSelectableFieldMetadataItems } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdownSelectableFieldMetadataItems'; -import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; -import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { useLingui } from '@lingui/react/macro'; - -export const AdvancedFilterDropdownFieldSelectMenu = () => { - const setObjectFilterDropdownSearchInput = useSetRecoilComponentStateV2( - objectFilterDropdownSearchInputComponentState, - ); - - const objectFilterDropdownSearchInput = useRecoilComponentValueV2( - objectFilterDropdownSearchInputComponentState, - ); - - const { - selectableHiddenFieldMetadataItems, - selectableVisibleFieldMetadataItems, - } = useFilterDropdownSelectableFieldMetadataItems(); - - const shouldShowSeparator = - selectableVisibleFieldMetadataItems.length > 0 && - selectableHiddenFieldMetadataItems.length > 0; - - const { t } = useLingui(); - - const selectableFieldMetadataItemIds = [ - ...selectableVisibleFieldMetadataItems.map( - (fieldMetadataItem) => fieldMetadataItem.id, - ), - ...selectableHiddenFieldMetadataItems.map( - (fieldMetadataItem) => fieldMetadataItem.id, - ), - ]; - - return ( - <> - ) => - setObjectFilterDropdownSearchInput(event.target.value) - } - /> - - - {selectableVisibleFieldMetadataItems.map( - (visibleFieldMetadataItem) => ( - - ), - )} - {shouldShowSeparator && } - {selectableHiddenFieldMetadataItems.map((hiddenFieldMetadataItem) => ( - - ))} - - - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenuItem.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenuItem.tsx deleted file mode 100644 index 67a18e308..000000000 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownFieldSelectMenuItem.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; -import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; - -import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; -import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; -import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; - -import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; -import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/constants/FilterFieldListId'; -import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; -import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters'; -import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; -import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; -import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; -import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; -import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; -import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { isDefined } from 'twenty-shared/utils'; -import { useIcons } from 'twenty-ui/display'; -import { MenuItem } from 'twenty-ui/navigation'; - -export type AdvancedFilterDropdownFieldSelectMenuItemProps = { - fieldMetadataItemToSelect: FieldMetadataItem; -}; - -export const AdvancedFilterDropdownFieldSelectMenuItem = ({ - fieldMetadataItemToSelect, -}: AdvancedFilterDropdownFieldSelectMenuItemProps) => { - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( - fieldMetadataItemIdUsedInDropdownComponentState, - ); - - const [, setObjectFilterDropdownSubMenuFieldType] = useRecoilComponentStateV2( - objectFilterDropdownSubMenuFieldTypeComponentState, - ); - - const [, setObjectFilterDropdownIsSelectingCompositeField] = - useRecoilComponentStateV2( - objectFilterDropdownIsSelectingCompositeFieldComponentState, - ); - - const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( - objectFilterDropdownFilterIsSelectedComponentState, - ); - - const { resetSelectedItem } = useSelectableList(FILTER_FIELD_LIST_ID); - - const isSelectedItem = useRecoilComponentFamilyValueV2( - isSelectedItemIdComponentFamilySelector, - fieldMetadataItemToSelect.id, - ); - - const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( - selectedOperandInDropdownComponentState, - ); - - const setHotkeyScope = useSetHotkeyScope(); - - const currentRecordFilters = useRecoilComponentValueV2( - currentRecordFiltersComponentState, - ); - - const setObjectFilterDropdownCurrentRecordFilter = - useSetRecoilComponentStateV2( - objectFilterDropdownCurrentRecordFilterComponentState, - ); - - const handleSelectFilter = (fieldMetadataItem: FieldMetadataItem) => { - setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id); - - const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type); - - if (filterType === 'RELATION' || filterType === 'SELECT') { - setHotkeyScope(SingleRecordPickerHotkeyScope.SingleRecordPicker); - } - - const defaultOperand = getRecordFilterOperands({ - filterType, - })[0]; - - setObjectFilterDropdownFilterIsSelected(true); - - const duplicateFilterInCurrentRecordFilters = - findDuplicateRecordFilterInNonAdvancedRecordFilters({ - recordFilters: currentRecordFilters, - fieldMetadataItemId: fieldMetadataItem.id, - }); - - const filterIsAlreadyInCurrentRecordFilters = isDefined( - duplicateFilterInCurrentRecordFilters, - ); - - if (filterIsAlreadyInCurrentRecordFilters) { - setObjectFilterDropdownCurrentRecordFilter( - duplicateFilterInCurrentRecordFilters, - ); - - setSelectedOperandInDropdown( - duplicateFilterInCurrentRecordFilters.operand, - ); - } else { - setSelectedOperandInDropdown(defaultOperand); - } - }; - - const { getIcon } = useIcons(); - - const Icon = getIcon(fieldMetadataItemToSelect.icon); - - const shouldShowSubMenu = isCompositeFieldType( - fieldMetadataItemToSelect.type, - ); - - const handleClick = () => { - resetSelectedItem(); - - const filterType = getFilterTypeFromFieldType( - fieldMetadataItemToSelect.type, - ); - - if (isCompositeFieldType(filterType)) { - setObjectFilterDropdownSubMenuFieldType(filterType); - - setFieldMetadataItemIdUsedInDropdown(fieldMetadataItemToSelect.id); - setObjectFilterDropdownIsSelectingCompositeField(true); - } else { - handleSelectFilter(fieldMetadataItemToSelect); - } - }; - - return ( - - - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownSubFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownSubFieldSelectMenu.tsx deleted file mode 100644 index eb0f3adbc..000000000 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterDropdownSubFieldSelectMenu.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; -import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/constants/FilterFieldListId'; -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; -import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; -import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; -import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; -import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState'; -import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; -import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState'; -import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; -import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; -import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; -import { getCompositeSubFieldLabel } from '@/object-record/object-filter-dropdown/utils/getCompositeSubFieldLabel'; -import { getFilterableFieldTypeLabel } from '@/object-record/object-filter-dropdown/utils/getFilterableFieldTypeLabel'; -import { ICON_NAME_BY_SUB_FIELD } from '@/object-record/record-filter/constants/IconNameBySubField'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { areCompositeTypeSubFieldsFilterable } from '@/object-record/record-filter/utils/areCompositeTypeSubFieldsFilterable'; -import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters'; -import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; -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 { 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 { 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 { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -import { StyledInput } from '@/views/components/ViewBarFilterDropdownFieldSelectMenu'; -import { useState } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { IconApps, IconChevronLeft, useIcons } from 'twenty-ui/display'; -import { MenuItem } from 'twenty-ui/navigation'; - -export const AdvancedFilterDropdownSubFieldSelectMenu = () => { - const [searchText, setSearchText] = useState(''); - - const { getIcon } = useIcons(); - - const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( - fieldMetadataItemUsedInDropdownComponentSelector, - ); - - const setSubFieldNameUsedInDropdown = useSetRecoilComponentStateV2( - subFieldNameUsedInDropdownComponentState, - ); - - const [, setObjectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2( - objectFilterDropdownFilterIsSelectedComponentState, - ); - - const [, setObjectFilterDropdownIsSelectingCompositeField] = - useRecoilComponentStateV2( - objectFilterDropdownIsSelectingCompositeFieldComponentState, - ); - - const [ - objectFilterDropdownSubMenuFieldType, - setObjectFilterDropdownSubMenuFieldType, - ] = useRecoilComponentStateV2( - objectFilterDropdownSubMenuFieldTypeComponentState, - ); - - const setFieldMetadataItemIdUsedInDropdown = useSetRecoilComponentStateV2( - fieldMetadataItemIdUsedInDropdownComponentState, - ); - - const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( - selectedOperandInDropdownComponentState, - ); - - const setObjectFilterDropdownSearchInput = useSetRecoilComponentStateV2( - objectFilterDropdownSearchInputComponentState, - ); - - const currentRecordFilters = useRecoilComponentValueV2( - currentRecordFiltersComponentState, - ); - - const setObjectFilterDropdownCurrentRecordFilter = - useSetRecoilComponentStateV2( - objectFilterDropdownCurrentRecordFilterComponentState, - ); - - const handleSelectFilter = ( - fieldMetadataItem: FieldMetadataItem | null | undefined, - subFieldName?: CompositeFieldSubFieldName | null | undefined, - ) => { - if (!isDefined(fieldMetadataItem)) { - return; - } - - const type = getFilterTypeFromFieldType(fieldMetadataItem.type); - - const defaultOperand = getRecordFilterOperands({ - filterType: type, - subFieldName: subFieldName, - })[0]; - - setFieldMetadataItemIdUsedInDropdown(fieldMetadataItem.id); - - setSubFieldNameUsedInDropdown(subFieldName); - - setObjectFilterDropdownSearchInput(''); - - setObjectFilterDropdownFilterIsSelected(true); - - const duplicateFilterInCurrentRecordFilters = - findDuplicateRecordFilterInNonAdvancedRecordFilters({ - recordFilters: currentRecordFilters, - fieldMetadataItemId: fieldMetadataItem.id, - subFieldName, - }); - - const filterIsAlreadyInCurrentRecordFilters = isDefined( - duplicateFilterInCurrentRecordFilters, - ); - - if (filterIsAlreadyInCurrentRecordFilters) { - setObjectFilterDropdownCurrentRecordFilter( - duplicateFilterInCurrentRecordFilters, - ); - - setSelectedOperandInDropdown( - duplicateFilterInCurrentRecordFilters.operand, - ); - } else { - setSelectedOperandInDropdown(defaultOperand); - } - }; - - const handleSubMenuBack = () => { - setFieldMetadataItemIdUsedInDropdown(null); - setObjectFilterDropdownSubMenuFieldType(null); - setObjectFilterDropdownIsSelectingCompositeField(false); - setObjectFilterDropdownFilterIsSelected(false); - setSubFieldNameUsedInDropdown(null); - }; - - const selectedItemId = useRecoilComponentValueV2( - selectedItemIdComponentState, - FILTER_FIELD_LIST_ID, - ); - - if (!isDefined(objectFilterDropdownSubMenuFieldType)) { - return null; - } - - const options = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[ - objectFilterDropdownSubMenuFieldType - ].filterableSubFields - .sort((a, b) => a.localeCompare(b)) - .filter((item) => - item.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()), - ); - - const subFieldsAreFilterable = - isDefined(fieldMetadataItemUsedInDropdown) && - areCompositeTypeSubFieldsFilterable(fieldMetadataItemUsedInDropdown.type); - - const compositeFieldTypeFilterableByAnySubField = - isDefined(fieldMetadataItemUsedInDropdown) && - isCompositeTypeFilterableByAnySubField( - fieldMetadataItemUsedInDropdown.type, - ); - - return ( - <> - - } - > - {getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} - - ) => - setSearchText(event.target.value) - } - /> - - - {compositeFieldTypeFilterableByAnySubField ? ( - { - handleSelectFilter(fieldMetadataItemUsedInDropdown); - }} - > - { - handleSelectFilter(fieldMetadataItemUsedInDropdown); - }} - LeftIcon={IconApps} - text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`} - /> - - ) : ( - <> - )} - {subFieldsAreFilterable && - options.map((subFieldName, index) => ( - { - handleSelectFilter( - fieldMetadataItemUsedInDropdown, - subFieldName, - ); - }} - > - { - if (isDefined(fieldMetadataItemUsedInDropdown)) { - handleSelectFilter( - fieldMetadataItemUsedInDropdown, - subFieldName, - ); - } - }} - text={getCompositeSubFieldLabel( - objectFilterDropdownSubMenuFieldType, - subFieldName, - )} - LeftIcon={getIcon( - ICON_NAME_BY_SUB_FIELD[subFieldName] ?? - fieldMetadataItemUsedInDropdown?.icon, - )} - /> - - ))} - - - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx index e8f5d2f91..f0eff3bab 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx @@ -22,8 +22,10 @@ import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-rec 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 { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; +import { useLingui } from '@lingui/react/macro'; type AdvancedFilterFieldSelectMenuProps = { recordFilterId: string; @@ -119,6 +121,9 @@ export const AdvancedFilterFieldSelectMenu = ({ } }; + const shouldShowVisibleFields = visibleColumnsFieldMetadataItems.length > 0; + const shouldShowHiddenFields = hiddenColumnsFieldMetadataItems.length > 0; + const shouldShowSeparator = visibleColumnsFieldMetadataItems.length > 0 && hiddenColumnsFieldMetadataItems.length > 0; @@ -132,6 +137,8 @@ export const AdvancedFilterFieldSelectMenu = ({ ), ]; + const { t } = useLingui(); + return ( @@ -140,41 +147,53 @@ export const AdvancedFilterFieldSelectMenu = ({ selectableItemIdArray={selectableItemIdArray} selectableListInstanceId={advancedFilterFieldSelectDropdownId} > - - {visibleColumnsFieldMetadataItems.map( - (visibleFieldMetadataItem, index) => ( - { - handleFieldMetadataItemSelect(visibleFieldMetadataItem); - }} - > - - - ), - )} - {shouldShowSeparator && } - {hiddenColumnsFieldMetadataItems.map( - (hiddenFieldMetadataItem, index) => ( - { - handleFieldMetadataItemSelect(hiddenFieldMetadataItem); - }} - > - - - ), - )} - + {shouldShowVisibleFields && ( + <> + + + {visibleColumnsFieldMetadataItems.map( + (visibleFieldMetadataItem, index) => ( + { + handleFieldMetadataItemSelect(visibleFieldMetadataItem); + }} + > + + + ), + )} + + + )} + {shouldShowSeparator && } + {shouldShowHiddenFields && ( + <> + + + {hiddenColumnsFieldMetadataItems.map( + (hiddenFieldMetadataItem, index) => ( + { + handleFieldMetadataItemSelect(hiddenFieldMetadataItem); + }} + > + + + ), + )} + + + )} ); diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx index bcda7a0a1..5d3cf82fb 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx @@ -22,6 +22,7 @@ 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 { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; @@ -216,6 +217,9 @@ export const ObjectSortDropdownButton = ({ OBJECT_SORT_DROPDOWN_ID, ); + const shouldShowHiddenFields = hiddenFieldMetadataItems.length > 0; + const shouldShowVisibleFields = visibleFieldMetadataItems.length > 0; + return ( - - {visibleFieldMetadataItems.map( - (visibleFieldMetadataItem, index) => ( - handleAddSort(visibleFieldMetadataItem)} - > - handleAddSort(visibleFieldMetadataItem)} - LeftIcon={getIcon(visibleFieldMetadataItem.icon)} - text={visibleFieldMetadataItem.label} - /> - - ), - )} - {shouldShowSeparator && } - {hiddenFieldMetadataItems.map( - (hiddenFieldMetadataItem, index) => ( - handleAddSort(hiddenFieldMetadataItem)} - > - handleAddSort(hiddenFieldMetadataItem)} - LeftIcon={getIcon(hiddenFieldMetadataItem.icon)} - text={hiddenFieldMetadataItem.label} - /> - - ), - )} - + {shouldShowVisibleFields && ( + <> + + + {visibleFieldMetadataItems.map( + (visibleFieldMetadataItem, index) => ( + handleAddSort(visibleFieldMetadataItem)} + > + + handleAddSort(visibleFieldMetadataItem) + } + LeftIcon={getIcon(visibleFieldMetadataItem.icon)} + text={visibleFieldMetadataItem.label} + /> + + ), + )} + + + )} + {shouldShowSeparator && } + {shouldShowHiddenFields && ( + <> + + + {hiddenFieldMetadataItems.map( + (hiddenFieldMetadataItem, index) => ( + handleAddSort(hiddenFieldMetadataItem)} + > + handleAddSort(hiddenFieldMetadataItem)} + LeftIcon={getIcon(hiddenFieldMetadataItem.icon)} + text={hiddenFieldMetadataItem.label} + /> + + ), + )} + + + )} } diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx index 41074adfb..71193890a 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuItemsContainer.tsx @@ -49,12 +49,14 @@ export const DropdownMenuItemsContainer = ({ className, scrollable = true, width = 'auto', + scrollWrapperHeightAuto, }: { children: React.ReactNode; hasMaxHeight?: boolean; className?: string; scrollable?: boolean; width?: number | 'auto' | '100%'; + scrollWrapperHeightAuto?: boolean; }) => { const id = useId(); @@ -68,6 +70,7 @@ export const DropdownMenuItemsContainer = ({ {hasMaxHeight ? ( {children} @@ -80,7 +83,10 @@ export const DropdownMenuItemsContainer = ({ )} ) : ( - + theme.background.transparent.lighter}; + color: ${({ theme }) => theme.font.color.light}; + min-height: 20px; + width: auto; + font-size: ${({ theme }) => theme.font.size.xxs}; + display: flex; + align-items: center; + justify-content: flex-start; + padding-left: ${({ theme }) => theme.spacing(1)}; + user-select: none; +`; + +export type DropdownMenuSectionLabelProps = { + label: string; +}; + +export const DropdownMenuSectionLabel = ({ + label, +}: DropdownMenuSectionLabelProps) => { + return ( + {label} + ); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/scroll/components/ScrollWrapper.tsx b/packages/twenty-front/src/modules/ui/utilities/scroll/components/ScrollWrapper.tsx index 80a9edaae..f7010de79 100644 --- a/packages/twenty-front/src/modules/ui/utilities/scroll/components/ScrollWrapper.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/scroll/components/ScrollWrapper.tsx @@ -7,7 +7,7 @@ import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/sta import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; -const StyledScrollWrapper = styled.div` +const StyledScrollWrapper = styled.div<{ height: string }>` &.scroll-wrapper-x-enabled { overflow-x: overlay; } @@ -17,7 +17,7 @@ const StyledScrollWrapper = styled.div` overflow-x: hidden; overflow-y: hidden; width: 100%; - height: 100%; + height: ${({ height }) => height}; `; export type ScrollWrapperProps = { @@ -26,6 +26,7 @@ export type ScrollWrapperProps = { defaultEnableXScroll?: boolean; defaultEnableYScroll?: boolean; componentInstanceId: string; + heightAuto?: boolean; }; export const ScrollWrapper = ({ @@ -34,6 +35,7 @@ export const ScrollWrapper = ({ className, defaultEnableXScroll = true, defaultEnableYScroll = true, + heightAuto = false, }: ScrollWrapperProps) => { const setScrollTop = useSetRecoilComponentStateV2( scrollWrapperScrollTopComponentState, @@ -71,6 +73,7 @@ export const ScrollWrapper = ({ id={`scroll-wrapper-${componentInstanceId}`} className={className} onScroll={handleScroll} + height={heightAuto ? 'auto' : '100%'} > {children} diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx index c3e2980cb..7c82518c6 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx @@ -12,6 +12,7 @@ import { FILTER_FIELD_LIST_ID } from '@/object-record/object-filter-dropdown/con import { useFilterDropdownSelectableFieldMetadataItems } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdownSelectableFieldMetadataItems'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { ViewBarFilterDropdownBottomMenu } from '@/views/components/ViewBarFilterDropdownBottomMenu'; import { ViewBarFilterDropdownFieldSelectMenuItem } from '@/views/components/ViewBarFilterDropdownFieldSelectMenuItem'; @@ -73,6 +74,10 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => { selectableVisibleFieldMetadataItems.length > 0 || selectableHiddenFieldMetadataItems.length > 0; + const shouldShowVisibleFields = + selectableVisibleFieldMetadataItems.length > 0; + const shouldShowHiddenFields = selectableHiddenFieldMetadataItems.length > 0; + const { t } = useLingui(); return ( @@ -90,26 +95,37 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => { selectableItemIdArray={selectableFieldMetadataItemIds} selectableListInstanceId={FILTER_FIELD_LIST_ID} > - {hasSelectableItems && ( - - {selectableVisibleFieldMetadataItems.map( - (visibleFieldMetadataItem) => ( - - ), - )} - {shouldShowSeparator && } - {selectableHiddenFieldMetadataItems.map( - (hiddenFieldMetadataItem) => ( - - ), - )} - + {shouldShowVisibleFields && ( + <> + + + + {selectableVisibleFieldMetadataItems.map( + (visibleFieldMetadataItem) => ( + + ), + )} + + + )} + {shouldShowSeparator && } + {shouldShowHiddenFields && ( + <> + + + {selectableHiddenFieldMetadataItems.map( + (hiddenFieldMetadataItem) => ( + + ), + )} + + )} {hasSelectableItems && }