Fix broken dropdown auto resize behavior (#11423)
This PR was originally about fixing advanced filter dropdown auto resize to avoid breaking the app main container, but the regression is not limited to advanced filter dropdown, so this PR fixes the regression for every dropdown in the app. This PR adds a max dropdown max width to allow resizing dropdowns horizontally also, which can happen easily for the advanced filter dropdown. In this PR we also start removing `fieldMetadataItemUsedInDropdown` in component `AdvancedFilterDropdownTextInput` because it has no impact outside of this component which is used only once. The autoresize behavior determines the right padding-bottom between mobile and PC. Mobile : <img width="604" alt="Capture d’écran 2025-04-07 à 16 03 12" src="https://github.com/user-attachments/assets/fbdd8020-1bfc-4e01-8a05-3a9f114cdd40" /> PC : <img width="757" alt="Capture d’écran 2025-04-07 à 16 03 30" src="https://github.com/user-attachments/assets/f80a5967-8f60-40bb-ae3c-fa9eb4c65707" /> Fixes https://github.com/twentyhq/core-team-issues/issues/725 Fixes https://github.com/twentyhq/twenty/issues/11409 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -5,43 +5,30 @@ import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-d
|
||||
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { AdvancedFilterDropdownDateInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownDateInput';
|
||||
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
||||
import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
|
||||
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type AdvancedFilterDropdownFilterInputProps = {
|
||||
filterDropdownId?: string;
|
||||
recordFilterId?: string;
|
||||
recordFilter: RecordFilter;
|
||||
};
|
||||
|
||||
export const AdvancedFilterDropdownFilterInput = ({
|
||||
filterDropdownId,
|
||||
recordFilterId,
|
||||
recordFilter,
|
||||
}: AdvancedFilterDropdownFilterInputProps) => {
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const subFieldNameUsedInDropdown = useRecoilComponentValueV2(
|
||||
subFieldNameUsedInDropdownComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
const filterType = recordFilter.type;
|
||||
|
||||
const isActorSourceCompositeFilter = isFilterOnActorSourceSubField(
|
||||
subFieldNameUsedInDropdown,
|
||||
@ -57,7 +44,7 @@ export const AdvancedFilterDropdownFilterInput = ({
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilterId} />
|
||||
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilter.id} />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'ACTOR' &&
|
||||
|
||||
@ -1,57 +1,36 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const AdvancedFilterDropdownTextInput = () => {
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedFilter = useRecoilComponentValueV2(
|
||||
selectedFilterComponentState,
|
||||
);
|
||||
type AdvancedFilterDropdownTextInputProps = {
|
||||
recordFilter: RecordFilter;
|
||||
};
|
||||
|
||||
export const AdvancedFilterDropdownTextInput = ({
|
||||
recordFilter,
|
||||
}: AdvancedFilterDropdownTextInputProps) => {
|
||||
const { applyRecordFilter } = useApplyRecordFilter();
|
||||
|
||||
const [inputValue, setInputValue] = useState(
|
||||
() => selectedFilter?.value || '',
|
||||
);
|
||||
const [inputValue, setInputValue] = useState(() => recordFilter?.value || '');
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
if (!fieldMetadataItemUsedInDropdown || !selectedOperandInDropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue(newValue);
|
||||
|
||||
applyRecordFilter({
|
||||
id: selectedFilter?.id ? selectedFilter.id : v4(),
|
||||
fieldMetadataId: fieldMetadataItemUsedInDropdown?.id ?? '',
|
||||
id: recordFilter.id,
|
||||
fieldMetadataId: recordFilter?.fieldMetadataId ?? '',
|
||||
value: newValue,
|
||||
operand: selectedOperandInDropdown,
|
||||
operand: recordFilter.operand,
|
||||
displayValue: newValue,
|
||||
type: getFilterTypeFromFieldType(fieldMetadataItemUsedInDropdown.type),
|
||||
label: fieldMetadataItemUsedInDropdown.label,
|
||||
recordFilterGroupId: selectedFilter?.recordFilterGroupId,
|
||||
positionInRecordFilterGroup: selectedFilter?.positionInRecordFilterGroup,
|
||||
type: recordFilter.type,
|
||||
label: recordFilter.label,
|
||||
recordFilterGroupId: recordFilter?.recordFilterGroupId,
|
||||
positionInRecordFilterGroup: recordFilter?.positionInRecordFilterGroup,
|
||||
});
|
||||
};
|
||||
|
||||
if (!selectedOperandInDropdown || !fieldMetadataItemUsedInDropdown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TextInputV2
|
||||
value={inputValue}
|
||||
|
||||
@ -5,8 +5,10 @@ import { AdvancedFilterRecordFilterRow } from '@/object-record/advanced-filter/c
|
||||
import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups';
|
||||
import { rootLevelRecordFilterGroupComponentSelector } from '@/object-record/advanced-filter/states/rootLevelRecordFilterGroupComponentSelector';
|
||||
import { isRecordFilterGroupChildARecordFilterGroup } from '@/object-record/advanced-filter/utils/isRecordFilterGroupChildARecordFilterGroup';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { id } from 'date-fns/locale';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
|
||||
@ -16,7 +18,7 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>`
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
overflow: hidden;
|
||||
min-width: 650px;
|
||||
`;
|
||||
|
||||
export const AdvancedFilterRootRecordFilterGroup = () => {
|
||||
@ -34,28 +36,32 @@ export const AdvancedFilterRootRecordFilterGroup = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{childRecordFiltersAndRecordFilterGroups.map(
|
||||
(recordFilterGroupChild, recordFilterGroupChildIndex) =>
|
||||
isRecordFilterGroupChildARecordFilterGroup(recordFilterGroupChild) ? (
|
||||
<AdvancedFilterRecordFilterGroupRow
|
||||
key={recordFilterGroupChild.id}
|
||||
parentRecordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilterGroup={recordFilterGroupChild}
|
||||
recordFilterGroupIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
) : (
|
||||
<AdvancedFilterRecordFilterRow
|
||||
key={recordFilterGroupChild.id}
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilter={recordFilterGroupChild}
|
||||
recordFilterIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<AdvancedFilterAddFilterRuleSelect
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
/>
|
||||
</StyledContainer>
|
||||
<ScrollWrapper componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}>
|
||||
<StyledContainer>
|
||||
{childRecordFiltersAndRecordFilterGroups.map(
|
||||
(recordFilterGroupChild, recordFilterGroupChildIndex) =>
|
||||
isRecordFilterGroupChildARecordFilterGroup(
|
||||
recordFilterGroupChild,
|
||||
) ? (
|
||||
<AdvancedFilterRecordFilterGroupRow
|
||||
key={recordFilterGroupChild.id}
|
||||
parentRecordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilterGroup={recordFilterGroupChild}
|
||||
recordFilterGroupIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
) : (
|
||||
<AdvancedFilterRecordFilterRow
|
||||
key={recordFilterGroupChild.id}
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
recordFilter={recordFilterGroupChild}
|
||||
recordFilterIndex={recordFilterGroupChildIndex}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<AdvancedFilterAddFilterRuleSelect
|
||||
recordFilterGroup={rootRecordFilterGroup}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</ScrollWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -45,11 +45,15 @@ export const AdvancedFilterValueInput = ({
|
||||
const operandHasNoInput =
|
||||
recordFilter && !configurableViewFilterOperands.has(recordFilter.operand);
|
||||
|
||||
if (!isDefined(recordFilter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleFilterValueDropdownClose = () => {
|
||||
setObjectFilterDropdownSearchInput('');
|
||||
};
|
||||
|
||||
const filterType = recordFilter?.type;
|
||||
const filterType = recordFilter.type;
|
||||
|
||||
const dropdownContentOffset =
|
||||
filterType === 'DATE' || filterType === 'DATE_TIME'
|
||||
@ -67,7 +71,7 @@ export const AdvancedFilterValueInput = ({
|
||||
) : isDefined(filterType) &&
|
||||
(TEXT_FILTER_TYPES.includes(filterType) ||
|
||||
NUMBER_FILTER_TYPES.includes(filterType)) ? (
|
||||
<AdvancedFilterDropdownTextInput />
|
||||
<AdvancedFilterDropdownTextInput recordFilter={recordFilter} />
|
||||
) : (
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
@ -77,14 +81,12 @@ export const AdvancedFilterValueInput = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<AdvancedFilterDropdownFilterInput
|
||||
recordFilterId={recordFilter.id}
|
||||
/>
|
||||
<AdvancedFilterDropdownFilterInput recordFilter={recordFilter} />
|
||||
}
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={dropdownContentOffset}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownMenuWidth={280}
|
||||
dropdownWidth={280}
|
||||
onClose={handleFilterValueDropdownClose}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -3,7 +3,6 @@ import { ObjectFilterOperandSelectAndInput } from '@/object-record/object-filter
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||
|
||||
type MultipleFiltersDropdownContentProps = {
|
||||
@ -40,7 +39,6 @@ export const MultipleFiltersDropdownContent = ({
|
||||
) : (
|
||||
<ObjectFilterDropdownFilterSelect isAdvancedFilterButtonVisible />
|
||||
)}
|
||||
<MultipleFiltersDropdownFilterOnFilterChangedEffect />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const MultipleFiltersDropdownFilterOnFilterChangedEffect = () => {
|
||||
const { setDropdownWidth } = useDropdown();
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
switch (filterType) {
|
||||
case 'DATE':
|
||||
case 'DATE_TIME':
|
||||
setDropdownWidth(280);
|
||||
break;
|
||||
default:
|
||||
setDropdownWidth(200);
|
||||
}
|
||||
}, [fieldMetadataItemUsedInDropdown, setDropdownWidth]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -33,7 +33,7 @@ export const ObjectOptionsDropdown = ({
|
||||
<Dropdown
|
||||
dropdownId={OBJECT_OPTIONS_DROPDOWN_ID}
|
||||
dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
|
||||
dropdownMenuWidth={DROPDOWN_WIDTH}
|
||||
dropdownWidth={DROPDOWN_WIDTH}
|
||||
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
|
||||
clickableComponent={
|
||||
<StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>
|
||||
|
||||
@ -26,8 +26,6 @@ describe('useOptionsDropdown', () => {
|
||||
closeDropdown: mockCloseDropdown,
|
||||
toggleDropdown: jest.fn(),
|
||||
openDropdown: jest.fn(),
|
||||
dropdownWidth: undefined,
|
||||
setDropdownWidth: jest.fn(),
|
||||
dropdownPlacement: null,
|
||||
setDropdownPlacement: jest.fn(),
|
||||
});
|
||||
|
||||
@ -46,7 +46,7 @@ export const RecordBoardColumnHeaderAggregateDropdown = ({
|
||||
dropdownHotkeyScope={{
|
||||
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
|
||||
}}
|
||||
dropdownMenuWidth={DROPDOWN_WIDTH}
|
||||
dropdownWidth={DROPDOWN_WIDTH}
|
||||
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
|
||||
clickableComponent={
|
||||
<RecordBoardColumnHeaderAggregateDropdownButton
|
||||
|
||||
@ -78,7 +78,7 @@ export const RecordIndexAddRecordInGroupDropdown = ({
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownMenuWidth="200px"
|
||||
dropdownWidth="200px"
|
||||
dropdownPlacement="bottom-start"
|
||||
clickableComponent={clickableComponent}
|
||||
dropdownId={dropdownId}
|
||||
|
||||
Reference in New Issue
Block a user