Created DropdownMenuInnerSelect and implemented it for filter dropdowns (#12626)

This PR introduces a new generic UI component DropdownMenuInnerSelect,
that improves the UI by allowing to have both a dropdown menu header and
a select in the header.

In this PR we implement it just for filter dropdown components.

Fixes https://github.com/twentyhq/core-team-issues/issues/1001
This commit is contained in:
Lucas Bordeau
2025-06-16 16:16:32 +02:00
committed by GitHub
parent ed1593c089
commit e922843afb
6 changed files with 190 additions and 6 deletions

View File

@ -10,7 +10,8 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
import { ObjectFilterDropdownOperandDropdown } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown';
import { ObjectFilterDropdownFilterInputHeader } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInputHeader';
import { ObjectFilterDropdownInnerSelectOperandDropdown } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownInnerSelectOperandDropdown';
import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth';
@ -41,7 +42,7 @@ export const ObjectFilterDropdownFilterInput = ({
filterDropdownId,
);
const isConfigurable =
const isOperandWithFilterValue =
selectedOperandInDropdown &&
[
ViewFilterOperand.Is,
@ -76,25 +77,30 @@ export const ObjectFilterDropdownFilterInput = ({
);
const isDateFilter = DATE_FILTER_TYPES.includes(filterType);
const isOnlyOperand = !isConfigurable;
const isOnlyOperand = !isOperandWithFilterValue;
if (isOnlyOperand) {
return (
<DropdownContent>
<ObjectFilterDropdownOperandDropdown />
<ObjectFilterDropdownFilterInputHeader />
<ObjectFilterDropdownInnerSelectOperandDropdown />
</DropdownContent>
);
} else if (isDateFilter) {
return (
<DropdownContent widthInPixels={DATE_PICKER_DROPDOWN_CONTENT_WIDTH}>
<ObjectFilterDropdownOperandDropdown />
<ObjectFilterDropdownFilterInputHeader />
<ObjectFilterDropdownInnerSelectOperandDropdown />
<DropdownMenuSeparator />
<ObjectFilterDropdownDateInput />
</DropdownContent>
);
} else {
return (
<DropdownContent>
<ObjectFilterDropdownOperandDropdown />
<ObjectFilterDropdownFilterInputHeader />
<ObjectFilterDropdownInnerSelectOperandDropdown />
<DropdownMenuSeparator />
{TEXT_FILTER_TYPES.includes(filterType) && (
<ObjectFilterDropdownTextInput />
)}

View File

@ -0,0 +1,16 @@
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const ObjectFilterDropdownFilterInputHeader = () => {
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
);
return (
<DropdownMenuHeader>
{fieldMetadataItemUsedInDropdown?.label}
</DropdownMenuHeader>
);
};

View File

@ -0,0 +1,69 @@
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownOperand';
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { DropdownMenuInnerSelect } from '@/ui/layout/dropdown/components/DropdownMenuInnerSelect';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils';
import { SelectOption } from 'twenty-ui/input';
const OBJECT_FILTER_DROPDOWN_INNER_SELECT_OPERAND_DROPDOWN_ID =
'object-filter-dropdown-inner-select-operand-dropdown';
export const ObjectFilterDropdownInnerSelectOperandDropdown = () => {
const selectedOperandInDropdown = useRecoilComponentValueV2(
selectedOperandInDropdownComponentState,
);
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
);
const subFieldNameUsedInDropdown = useRecoilComponentValueV2(
subFieldNameUsedInDropdownComponentState,
);
const operandsForFilterType = isDefined(fieldMetadataItemUsedInDropdown)
? getRecordFilterOperands({
filterType: getFilterTypeFromFieldType(
fieldMetadataItemUsedInDropdown.type,
),
subFieldName: subFieldNameUsedInDropdown,
})
: [];
const options = operandsForFilterType.map((operand) => ({
label: getOperandLabel(operand),
value: operand,
})) as SelectOption[];
const selectedOption =
options.find((option) => option.value === selectedOperandInDropdown) ??
options[0];
const { applyObjectFilterDropdownOperand } =
useApplyObjectFilterDropdownOperand();
const handleOperandChange = (newOperandOption: SelectOption) => {
applyObjectFilterDropdownOperand(
newOperandOption.value as RecordFilterOperand,
);
};
if (!isDefined(selectedOperandInDropdown)) {
return null;
}
return (
<DropdownMenuInnerSelect
dropdownId={OBJECT_FILTER_DROPDOWN_INNER_SELECT_OPERAND_DROPDOWN_ID}
selectedOption={selectedOption}
onChange={handleOperandChange}
options={options}
/>
);
};