Implemented dropdown menu section label in filter and sort (#12453)

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 : 

<img width="247" alt="Capture d’écran 2025-06-04 à 12 49 42"
src="https://github.com/user-attachments/assets/759c48ca-c54d-4921-bea6-cbfe7a2d244d"
/>
<img width="252" alt="Capture d’écran 2025-06-04 à 12 49 20"
src="https://github.com/user-attachments/assets/72cd63d0-e5d6-4000-897d-c16efd8396c9"
/>
<img width="359" alt="Capture d’écran 2025-06-04 à 12 48 44"
src="https://github.com/user-attachments/assets/d7c41039-dc15-46d7-be89-33a39e226fb2"
/>

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
This commit is contained in:
Lucas Bordeau
2025-06-05 20:50:12 +02:00
committed by GitHub
parent 26bd16a2cb
commit 276f1796cc
9 changed files with 187 additions and 585 deletions

View File

@ -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 (
<>
<DropdownMenuSearchInput
value={objectFilterDropdownSearchInput}
autoFocus
placeholder={t`Search fields`}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setObjectFilterDropdownSearchInput(event.target.value)
}
/>
<SelectableList
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
selectableItemIdArray={selectableFieldMetadataItemIds}
selectableListInstanceId={FILTER_FIELD_LIST_ID}
>
<DropdownMenuItemsContainer>
{selectableVisibleFieldMetadataItems.map(
(visibleFieldMetadataItem) => (
<AdvancedFilterDropdownFieldSelectMenuItem
key={visibleFieldMetadataItem.id}
fieldMetadataItemToSelect={visibleFieldMetadataItem}
/>
),
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{selectableHiddenFieldMetadataItems.map((hiddenFieldMetadataItem) => (
<AdvancedFilterDropdownFieldSelectMenuItem
key={hiddenFieldMetadataItem.id}
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
/>
))}
</DropdownMenuItemsContainer>
</SelectableList>
</>
);
};

View File

@ -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 (
<SelectableListItem
itemId={fieldMetadataItemToSelect.id}
onEnter={handleClick}
>
<MenuItem
focused={isSelectedItem}
onClick={handleClick}
LeftIcon={Icon}
text={fieldMetadataItemToSelect.label}
hasSubMenu={shouldShowSubMenu}
/>
</SelectableListItem>
);
};

View File

@ -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 (
<>
<DropdownMenuHeader
StartComponent={
<DropdownMenuHeaderLeftComponent
onClick={handleSubMenuBack}
Icon={IconChevronLeft}
/>
}
>
{getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)}
</DropdownMenuHeader>
<StyledInput
value={searchText}
autoFocus
placeholder="Search fields"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSearchText(event.target.value)
}
/>
<DropdownMenuItemsContainer>
<SelectableList
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
selectableItemIdArray={['-1', ...options]}
selectableListInstanceId={FILTER_FIELD_LIST_ID}
>
{compositeFieldTypeFilterableByAnySubField ? (
<SelectableListItem
itemId={'-1'}
key={`select-filter-${-1}`}
onEnter={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
>
<MenuItem
key={`select-filter-${-1}`}
testId={`select-filter-${-1}`}
onClick={() => {
handleSelectFilter(fieldMetadataItemUsedInDropdown);
}}
LeftIcon={IconApps}
text={`Any ${getFilterableFieldTypeLabel(objectFilterDropdownSubMenuFieldType)} field`}
/>
</SelectableListItem>
) : (
<></>
)}
{subFieldsAreFilterable &&
options.map((subFieldName, index) => (
<SelectableListItem
itemId={subFieldName}
key={`select-filter-${index}`}
onEnter={() => {
handleSelectFilter(
fieldMetadataItemUsedInDropdown,
subFieldName,
);
}}
>
<MenuItem
focused={selectedItemId === subFieldName}
key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => {
if (isDefined(fieldMetadataItemUsedInDropdown)) {
handleSelectFilter(
fieldMetadataItemUsedInDropdown,
subFieldName,
);
}
}}
text={getCompositeSubFieldLabel(
objectFilterDropdownSubMenuFieldType,
subFieldName,
)}
LeftIcon={getIcon(
ICON_NAME_BY_SUB_FIELD[subFieldName] ??
fieldMetadataItemUsedInDropdown?.icon,
)}
/>
</SelectableListItem>
))}
</SelectableList>
</DropdownMenuItemsContainer>
</>
);
};

View File

@ -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 (
<DropdownContent>
<AdvancedFilterFieldSelectSearchInput />
@ -140,41 +147,53 @@ export const AdvancedFilterFieldSelectMenu = ({
selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
>
<DropdownMenuItemsContainer>
{visibleColumnsFieldMetadataItems.map(
(visibleFieldMetadataItem, index) => (
<SelectableListItem
itemId={visibleFieldMetadataItem.id}
key={`visible-select-filter-${index}`}
onEnter={() => {
handleFieldMetadataItemSelect(visibleFieldMetadataItem);
}}
>
<ObjectFilterDropdownFilterSelectMenuItemV2
fieldMetadataItemToSelect={visibleFieldMetadataItem}
onClick={handleFieldMetadataItemSelect}
/>
</SelectableListItem>
),
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{hiddenColumnsFieldMetadataItems.map(
(hiddenFieldMetadataItem, index) => (
<SelectableListItem
itemId={hiddenFieldMetadataItem.id}
key={`hidden-select-filter-${index}`}
onEnter={() => {
handleFieldMetadataItemSelect(hiddenFieldMetadataItem);
}}
>
<ObjectFilterDropdownFilterSelectMenuItemV2
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
onClick={handleFieldMetadataItemSelect}
/>
</SelectableListItem>
),
)}
</DropdownMenuItemsContainer>
{shouldShowVisibleFields && (
<>
<DropdownMenuSectionLabel label={t`Visible fields`} />
<DropdownMenuItemsContainer scrollWrapperHeightAuto>
{visibleColumnsFieldMetadataItems.map(
(visibleFieldMetadataItem, index) => (
<SelectableListItem
itemId={visibleFieldMetadataItem.id}
key={`visible-select-filter-${index}`}
onEnter={() => {
handleFieldMetadataItemSelect(visibleFieldMetadataItem);
}}
>
<ObjectFilterDropdownFilterSelectMenuItemV2
fieldMetadataItemToSelect={visibleFieldMetadataItem}
onClick={handleFieldMetadataItemSelect}
/>
</SelectableListItem>
),
)}
</DropdownMenuItemsContainer>
</>
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{shouldShowHiddenFields && (
<>
<DropdownMenuSectionLabel label={t`Hidden fields`} />
<DropdownMenuItemsContainer scrollWrapperHeightAuto>
{hiddenColumnsFieldMetadataItems.map(
(hiddenFieldMetadataItem, index) => (
<SelectableListItem
itemId={hiddenFieldMetadataItem.id}
key={`hidden-select-filter-${index}`}
onEnter={() => {
handleFieldMetadataItemSelect(hiddenFieldMetadataItem);
}}
>
<ObjectFilterDropdownFilterSelectMenuItemV2
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
onClick={handleFieldMetadataItemSelect}
/>
</SelectableListItem>
),
)}
</DropdownMenuItemsContainer>
</>
)}
</SelectableList>
</DropdownContent>
);

View File

@ -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 (
<Dropdown
dropdownId={OBJECT_SORT_DROPDOWN_ID}
@ -279,43 +283,61 @@ export const ObjectSortDropdownButton = ({
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>
{shouldShowVisibleFields && (
<>
<DropdownMenuSectionLabel label={t`Visible fields`} />
<DropdownMenuItemsContainer scrollWrapperHeightAuto>
{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>
),
)}
</DropdownMenuItemsContainer>
</>
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{shouldShowHiddenFields && (
<>
<DropdownMenuSectionLabel label={t`Hidden fields`} />
<DropdownMenuItemsContainer scrollWrapperHeightAuto>
{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>
}

View File

@ -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 ? (
<StyledScrollWrapper
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
heightAuto={scrollWrapperHeightAuto}
>
<StyledDropdownMenuItemsInternalContainer>
{children}
@ -80,7 +83,10 @@ export const DropdownMenuItemsContainer = ({
)}
</StyledDropdownMenuItemsExternalContainer>
) : (
<ScrollWrapper componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}>
<ScrollWrapper
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
heightAuto={scrollWrapperHeightAuto}
>
<StyledDropdownMenuItemsExternalContainer
hasMaxHeight={hasMaxHeight}
className={className}

View File

@ -0,0 +1,26 @@
import styled from '@emotion/styled';
const StyledDropdownMenuSectionLabel = styled.div`
background-color: ${({ theme }) => 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 (
<StyledDropdownMenuSectionLabel>{label}</StyledDropdownMenuSectionLabel>
);
};

View File

@ -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}
</StyledScrollWrapper>

View File

@ -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 && (
<DropdownMenuItemsContainer>
{selectableVisibleFieldMetadataItems.map(
(visibleFieldMetadataItem) => (
<ViewBarFilterDropdownFieldSelectMenuItem
key={visibleFieldMetadataItem.id}
fieldMetadataItemToSelect={visibleFieldMetadataItem}
/>
),
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{selectableHiddenFieldMetadataItems.map(
(hiddenFieldMetadataItem) => (
<ViewBarFilterDropdownFieldSelectMenuItem
key={hiddenFieldMetadataItem.id}
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
/>
),
)}
</DropdownMenuItemsContainer>
{shouldShowVisibleFields && (
<>
<DropdownMenuSectionLabel label={t`Visible fields`} />
<DropdownMenuItemsContainer scrollWrapperHeightAuto>
{selectableVisibleFieldMetadataItems.map(
(visibleFieldMetadataItem) => (
<ViewBarFilterDropdownFieldSelectMenuItem
key={visibleFieldMetadataItem.id}
fieldMetadataItemToSelect={visibleFieldMetadataItem}
/>
),
)}
</DropdownMenuItemsContainer>
</>
)}
{shouldShowSeparator && <DropdownMenuSeparator />}
{shouldShowHiddenFields && (
<>
<DropdownMenuSectionLabel label={t`Hidden fields`} />
<DropdownMenuItemsContainer scrollWrapperHeightAuto>
{selectableHiddenFieldMetadataItems.map(
(hiddenFieldMetadataItem) => (
<ViewBarFilterDropdownFieldSelectMenuItem
key={hiddenFieldMetadataItem.id}
fieldMetadataItemToSelect={hiddenFieldMetadataItem}
/>
),
)}
</DropdownMenuItemsContainer>
</>
)}
{hasSelectableItems && <DropdownMenuSeparator />}
<ViewBarFilterDropdownBottomMenu />