Fix filter multi select (#8682)

- Created a dropdown inside a dropdown for the
`ObjectFilterDropdownOperandDropdown` so the operand can be opened over
the selection with an offset
- Refactored dropdown component and introduced `DropdownUnmountEffect`
to close the dropdown when the component unmounts
- Removed old logic

Before:
<img width="216" alt="Capture d’écran 2024-11-22 à 14 03 58"
src="https://github.com/user-attachments/assets/3c1bba03-af03-4993-a070-f009b8dc876f">

After:
<img width="222" alt="Capture d’écran 2024-11-22 à 14 03 40"
src="https://github.com/user-attachments/assets/a8a784b4-8672-4b02-bb21-e4a749682f2e">
This commit is contained in:
Raphaël Bosi
2024-11-22 15:08:29 +01:00
committed by GitHub
parent ac9cf737fb
commit f44e2935df
19 changed files with 148 additions and 248 deletions

View File

@ -62,7 +62,7 @@ export const RightDrawerActionMenuDropdown = () => {
clickableComponent={<Button title="Actions" shortcut="⌘O" />} clickableComponent={<Button title="Actions" shortcut="⌘O" />}
dropdownPlacement="top-end" dropdownPlacement="top-end"
dropdownOffset={{ dropdownOffset={{
y: parseInt(theme.spacing(2)), y: parseInt(theme.spacing(2), 10),
}} }}
dropdownComponents={ dropdownComponents={
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>

View File

@ -22,7 +22,6 @@ export const AdvancedFilterViewFilterValueInput = ({
const { const {
setFilterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown, setSelectedOperandInDropdown,
setIsObjectFilterDropdownOperandSelectUnfolded,
setSelectedFilter, setSelectedFilter,
} = useFilterDropdown(); } = useFilterDropdown();
@ -53,7 +52,6 @@ export const AdvancedFilterViewFilterValueInput = ({
onOpen={() => { onOpen={() => {
setFilterDefinitionUsedInDropdown(filter.definition); setFilterDefinitionUsedInDropdown(filter.definition);
setSelectedOperandInDropdown(filter.operand); setSelectedOperandInDropdown(filter.operand);
setIsObjectFilterDropdownOperandSelectUnfolded(true);
setSelectedFilter(filter); setSelectedFilter(filter);
}} }}
dropdownComponents={ dropdownComponents={

View File

@ -14,13 +14,11 @@ type MultipleFiltersDropdownButtonProps = {
export const MultipleFiltersDropdownButton = ({ export const MultipleFiltersDropdownButton = ({
hotkeyScope, hotkeyScope,
}: MultipleFiltersDropdownButtonProps) => { }: MultipleFiltersDropdownButtonProps) => {
const { resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded } = const { resetFilter } = useFilterDropdown();
useFilterDropdown();
const handleDropdownClose = useCallback(() => { const handleDropdownClose = useCallback(() => {
resetFilter(); resetFilter();
setIsObjectFilterDropdownOperandSelectUnfolded(false); }, [resetFilter]);
}, [resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded]);
return ( return (
<Dropdown <Dropdown

View File

@ -21,7 +21,6 @@ export const ObjectFilterDropdownDateInput = () => {
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
selectedOperandInDropdownState, selectedOperandInDropdownState,
selectedFilterState, selectedFilterState,
setIsObjectFilterDropdownUnfolded,
selectFilter, selectFilter,
} = useFilterDropdown(); } = useFilterDropdown();
@ -65,8 +64,6 @@ export const ObjectFilterDropdownDateInput = () => {
definition: filterDefinitionUsedInDropdown, definition: filterDefinitionUsedInDropdown,
viewFilterGroupId: selectedFilter?.viewFilterGroupId, viewFilterGroupId: selectedFilter?.viewFilterGroupId,
}); });
setIsObjectFilterDropdownUnfolded(false);
}; };
const handleRelativeDateChange = ( const handleRelativeDateChange = (
@ -95,8 +92,6 @@ export const ObjectFilterDropdownDateInput = () => {
definition: filterDefinitionUsedInDropdown, definition: filterDefinitionUsedInDropdown,
viewFilterGroupId: selectedFilter?.viewFilterGroupId, viewFilterGroupId: selectedFilter?.viewFilterGroupId,
}); });
setIsObjectFilterDropdownUnfolded(false);
}; };
const isRelativeOperand = const isRelativeOperand =

View File

@ -28,13 +28,8 @@ export const ObjectFilterDropdownFilterInput = ({
const { const {
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
selectedOperandInDropdownState, selectedOperandInDropdownState,
isObjectFilterDropdownOperandSelectUnfoldedState,
} = useFilterDropdown({ filterDropdownId }); } = useFilterDropdown({ filterDropdownId });
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
isObjectFilterDropdownOperandSelectUnfoldedState,
);
const filterDefinitionUsedInDropdown = useRecoilValue( const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
); );
@ -58,9 +53,7 @@ export const ObjectFilterDropdownFilterInput = ({
ViewFilterOperand.IsRelative, ViewFilterOperand.IsRelative,
].includes(selectedOperandInDropdown); ].includes(selectedOperandInDropdown);
const shouldHide = isObjectFilterDropdownOperandSelectUnfolded; if (!isDefined(filterDefinitionUsedInDropdown)) {
if (shouldHide || !isDefined(filterDefinitionUsedInDropdown)) {
return null; return null;
} }
@ -84,6 +77,7 @@ export const ObjectFilterDropdownFilterInput = ({
{filterDefinitionUsedInDropdown.type === 'RELATION' && ( {filterDefinitionUsedInDropdown.type === 'RELATION' && (
<> <>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect /> <ObjectFilterDropdownRecordSelect />
</> </>
)} )}
@ -98,6 +92,7 @@ export const ObjectFilterDropdownFilterInput = ({
) && ( ) && (
<> <>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect /> <ObjectFilterDropdownOptionSelect />
</> </>
)} )}

View File

@ -1,38 +0,0 @@
import { ObjectFilterDropdownOperandButton } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandButton';
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
const StyledOperandSelectContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
box-shadow: ${({ theme }) => theme.boxShadow.light};
border-radius: ${({ theme }) => theme.border.radius.md};
width: 100%;
z-index: 1000;
`;
export const ObjectFilterDropdownFilterOperandSelect = ({
filterDropdownId,
}: {
filterDropdownId?: string;
}) => {
const { isObjectFilterDropdownOperandSelectUnfoldedState } =
useFilterDropdown({ filterDropdownId });
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
isObjectFilterDropdownOperandSelectUnfoldedState,
);
return (
<>
<ObjectFilterDropdownOperandButton />
{isObjectFilterDropdownOperandSelectUnfolded && (
<StyledOperandSelectContainer>
<ObjectFilterDropdownOperandSelect />
</StyledOperandSelectContainer>
)}
</>
);
};

View File

@ -1,39 +0,0 @@
import { useRecoilValue } from 'recoil';
import { IconChevronDown } from 'twenty-ui';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { getOperandLabel } from '../utils/getOperandLabel';
export const ObjectFilterDropdownOperandButton = () => {
const {
selectedOperandInDropdownState,
setIsObjectFilterDropdownOperandSelectUnfolded,
isObjectFilterDropdownOperandSelectUnfoldedState,
} = useFilterDropdown();
const selectedOperandInDropdown = useRecoilValue(
selectedOperandInDropdownState,
);
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
isObjectFilterDropdownOperandSelectUnfoldedState,
);
const handleButtonClick = () => {
setIsObjectFilterDropdownOperandSelectUnfolded(
!isObjectFilterDropdownOperandSelectUnfolded,
);
};
return (
<DropdownMenuHeader
key={'selected-filter-operand'}
EndIcon={IconChevronDown}
onClick={handleButtonClick}
>
{getOperandLabel(selectedOperandInDropdown)}
</DropdownMenuHeader>
);
};

View File

@ -0,0 +1,51 @@
import { useRecoilValue } from 'recoil';
import { IconChevronDown } from 'twenty-ui';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { getOperandLabel } from '../utils/getOperandLabel';
const StyledDropdownMenuHeader = styled(DropdownMenuHeader)`
cursor: pointer;
`;
export const ObjectFilterDropdownOperandDropdown = ({
filterDropdownId,
}: {
filterDropdownId?: string;
}) => {
const { selectedOperandInDropdownState } = useFilterDropdown();
const selectedOperandInDropdown = useRecoilValue(
selectedOperandInDropdownState,
);
const theme = useTheme();
return (
<Dropdown
dropdownId={`${filterDropdownId}-operand-dropdown`}
clickableComponent={
<StyledDropdownMenuHeader
key={'selected-filter-operand'}
EndIcon={IconChevronDown}
>
{getOperandLabel(selectedOperandInDropdown)}
</StyledDropdownMenuHeader>
}
dropdownComponents={<ObjectFilterDropdownOperandSelect />}
dropdownHotkeyScope={{
scope: FiltersHotkeyScope.ObjectFilterDropdownOperandDropdown,
}}
dropdownOffset={{
x: parseInt(theme.spacing(2), 10),
}}
/>
);
};

View File

@ -2,33 +2,35 @@ import { useRecoilValue } from 'recoil';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import styled from '@emotion/styled';
import { MenuItem } from 'twenty-ui'; import { MenuItem } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { getInitialFilterValue } from '@/object-record/object-filter-dropdown/utils/getInitialFilterValue';
import { getOperandLabel } from '../utils/getOperandLabel'; import { getOperandLabel } from '../utils/getOperandLabel';
import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType'; import { getOperandsForFilterDefinition } from '../utils/getOperandsForFilterType';
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
background-color: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.md};
`;
export const ObjectFilterDropdownOperandSelect = () => { export const ObjectFilterDropdownOperandSelect = () => {
const { const {
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
setSelectedOperandInDropdown, setSelectedOperandInDropdown,
isObjectFilterDropdownOperandSelectUnfoldedState,
setIsObjectFilterDropdownOperandSelectUnfolded,
selectedFilterState, selectedFilterState,
selectFilter, selectFilter,
} = useFilterDropdown(); } = useFilterDropdown();
const { closeDropdown } = useDropdown();
const filterDefinitionUsedInDropdown = useRecoilValue( const filterDefinitionUsedInDropdown = useRecoilValue(
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
); );
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
isObjectFilterDropdownOperandSelectUnfoldedState,
);
const selectedFilter = useRecoilValue(selectedFilterState); const selectedFilter = useRecoilValue(selectedFilterState);
const operandsForFilterType = isDefined(filterDefinitionUsedInDropdown) const operandsForFilterType = isDefined(filterDefinitionUsedInDropdown)
@ -45,7 +47,6 @@ export const ObjectFilterDropdownOperandSelect = () => {
].includes(newOperand); ].includes(newOperand);
setSelectedOperandInDropdown(newOperand); setSelectedOperandInDropdown(newOperand);
setIsObjectFilterDropdownOperandSelectUnfolded(false);
if (isValuelessOperand && isDefined(filterDefinitionUsedInDropdown)) { if (isValuelessOperand && isDefined(filterDefinitionUsedInDropdown)) {
selectFilter?.({ selectFilter?.({
@ -81,21 +82,18 @@ export const ObjectFilterDropdownOperandSelect = () => {
} }
}; };
if (!isObjectFilterDropdownOperandSelectUnfolded) {
return <></>;
}
return ( return (
<DropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{operandsForFilterType.map((filterOperand, index) => ( {operandsForFilterType.map((filterOperand, index) => (
<MenuItem <MenuItem
key={`select-filter-operand-${index}`} key={`select-filter-operand-${index}`}
onClick={() => { onClick={() => {
handleOperandChange(filterOperand); handleOperandChange(filterOperand);
closeDropdown();
}} }}
text={getOperandLabel(filterOperand)} text={getOperandLabel(filterOperand)}
/> />
))} ))}
</DropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
); );
}; };

View File

@ -1,5 +1,5 @@
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput'; import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
import { ObjectFilterDropdownFilterOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterOperandSelect'; import { ObjectFilterDropdownOperandDropdown } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown';
type ObjectFilterOperandSelectAndInputProps = { type ObjectFilterOperandSelectAndInputProps = {
filterDropdownId?: string; filterDropdownId?: string;
@ -10,7 +10,7 @@ export const ObjectFilterOperandSelectAndInput = ({
}: ObjectFilterOperandSelectAndInputProps) => { }: ObjectFilterOperandSelectAndInputProps) => {
return ( return (
<> <>
<ObjectFilterDropdownFilterOperandSelect <ObjectFilterDropdownOperandDropdown
filterDropdownId={filterDropdownId} filterDropdownId={filterDropdownId}
/> />
<ObjectFilterDropdownFilterInput filterDropdownId={filterDropdownId} /> <ObjectFilterDropdownFilterInput filterDropdownId={filterDropdownId} />

View File

@ -259,64 +259,6 @@ describe('useFilterDropdown', () => {
}); });
}); });
it('should set isObjectFilterDropdownOperandSelectUnfolded', async () => {
const { result } = renderHook(() => {
useFilterDropdown({ filterDropdownId });
const { isObjectFilterDropdownOperandSelectUnfoldedState } =
useFilterDropdownStates(filterDropdownId);
const [
isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded,
] = useRecoilState(isObjectFilterDropdownOperandSelectUnfoldedState);
return {
isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded,
};
}, renderHookConfig);
expect(result.current.isObjectFilterDropdownOperandSelectUnfolded).toBe(
false,
);
act(() => {
result.current.setIsObjectFilterDropdownOperandSelectUnfolded(true);
});
await waitFor(() => {
expect(result.current.isObjectFilterDropdownOperandSelectUnfolded).toBe(
true,
);
});
});
it('should set isObjectFilterDropdownUnfolded', async () => {
const { result } = renderHook(() => {
useFilterDropdown({ filterDropdownId });
const { isObjectFilterDropdownUnfoldedState } =
useFilterDropdownStates(filterDropdownId);
const [
isObjectFilterDropdownUnfolded,
setIsObjectFilterDropdownUnfolded,
] = useRecoilState(isObjectFilterDropdownUnfoldedState);
return {
isObjectFilterDropdownUnfolded,
setIsObjectFilterDropdownUnfolded,
};
}, renderHookConfig);
expect(result.current.isObjectFilterDropdownUnfolded).toBe(false);
act(() => {
result.current.setIsObjectFilterDropdownUnfolded(true);
});
await waitFor(() => {
expect(result.current.isObjectFilterDropdownUnfolded).toBe(true);
});
});
it('should reset filter', async () => { it('should reset filter', async () => {
const { result } = renderHook(() => { const { result } = renderHook(() => {
const { resetFilter, selectFilter } = useFilterDropdown({ const { resetFilter, selectFilter } = useFilterDropdown({

View File

@ -28,8 +28,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
objectFilterDropdownSearchInputState, objectFilterDropdownSearchInputState,
objectFilterDropdownSelectedRecordIdsState, objectFilterDropdownSelectedRecordIdsState,
objectFilterDropdownSelectedOptionValuesState, objectFilterDropdownSelectedOptionValuesState,
isObjectFilterDropdownOperandSelectUnfoldedState,
isObjectFilterDropdownUnfoldedState,
selectedFilterState, selectedFilterState,
selectedOperandInDropdownState, selectedOperandInDropdownState,
onFilterSelectState, onFilterSelectState,
@ -121,12 +119,7 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
const setObjectFilterDropdownSelectedOptionValues = useSetRecoilState( const setObjectFilterDropdownSelectedOptionValues = useSetRecoilState(
objectFilterDropdownSelectedOptionValuesState, objectFilterDropdownSelectedOptionValuesState,
); );
const setIsObjectFilterDropdownOperandSelectUnfolded = useSetRecoilState(
isObjectFilterDropdownOperandSelectUnfoldedState,
);
const setIsObjectFilterDropdownUnfolded = useSetRecoilState(
isObjectFilterDropdownUnfoldedState,
);
const setOnFilterSelect = useSetRecoilState(onFilterSelectState); const setOnFilterSelect = useSetRecoilState(onFilterSelectState);
const setAdvancedFilterViewFilterGroupId = useSetRecoilState( const setAdvancedFilterViewFilterGroupId = useSetRecoilState(
advancedFilterViewFilterGroupIdState, advancedFilterViewFilterGroupIdState,
@ -143,22 +136,16 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
setSelectedOperandInDropdown, setSelectedOperandInDropdown,
setFilterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown,
setObjectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput,
// setObjectFilterDropdownSelectedEntityId,
setObjectFilterDropdownSelectedRecordIds, setObjectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedOptionValues, setObjectFilterDropdownSelectedOptionValues,
setIsObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownUnfolded,
setOnFilterSelect, setOnFilterSelect,
setAdvancedFilterViewFilterGroupId, setAdvancedFilterViewFilterGroupId,
setAdvancedFilterViewFilterId, setAdvancedFilterViewFilterId,
emptyFilterButKeepDefinition, emptyFilterButKeepDefinition,
filterDefinitionUsedInDropdownState, filterDefinitionUsedInDropdownState,
objectFilterDropdownSearchInputState, objectFilterDropdownSearchInputState,
// objectFilterDropdownSelectedEntityIdState,
objectFilterDropdownSelectedRecordIdsState, objectFilterDropdownSelectedRecordIdsState,
objectFilterDropdownSelectedOptionValuesState, objectFilterDropdownSelectedOptionValuesState,
isObjectFilterDropdownOperandSelectUnfoldedState,
isObjectFilterDropdownUnfoldedState,
selectedFilterState, selectedFilterState,
selectedOperandInDropdownState, selectedOperandInDropdownState,
onFilterSelectState, onFilterSelectState,

View File

@ -1,8 +1,6 @@
import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState'; import { advancedFilterViewFilterGroupIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterGroupIdComponentState';
import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState'; import { advancedFilterViewFilterIdComponentState } from '@/object-record/object-filter-dropdown/states/advancedFilterViewFilterIdComponentState';
import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState'; import { filterDefinitionUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/filterDefinitionUsedInDropdownComponentState';
import { isObjectFilterDropdownOperandSelectUnfoldedComponentState } from '@/object-record/object-filter-dropdown/states/isObjectFilterDropdownOperandSelectUnfoldedComponentState';
import { isObjectFilterDropdownUnfoldedComponentState } from '@/object-record/object-filter-dropdown/states/isObjectFilterDropdownUnfoldedComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState'; import { objectFilterDropdownSelectedOptionValuesComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedOptionValuesComponentState';
import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState'; import { objectFilterDropdownSelectedRecordIdsComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsComponentState';
@ -32,17 +30,6 @@ export const useFilterDropdownStates = (scopeId: string) => {
scopeId, scopeId,
); );
const isObjectFilterDropdownOperandSelectUnfoldedState =
extractComponentState(
isObjectFilterDropdownOperandSelectUnfoldedComponentState,
scopeId,
);
const isObjectFilterDropdownUnfoldedState = extractComponentState(
isObjectFilterDropdownUnfoldedComponentState,
scopeId,
);
const selectedFilterState = extractComponentState( const selectedFilterState = extractComponentState(
selectedFilterComponentState, selectedFilterComponentState,
scopeId, scopeId,
@ -73,8 +60,6 @@ export const useFilterDropdownStates = (scopeId: string) => {
objectFilterDropdownSearchInputState, objectFilterDropdownSearchInputState,
objectFilterDropdownSelectedRecordIdsState, objectFilterDropdownSelectedRecordIdsState,
objectFilterDropdownSelectedOptionValuesState, objectFilterDropdownSelectedOptionValuesState,
isObjectFilterDropdownOperandSelectUnfoldedState,
isObjectFilterDropdownUnfoldedState,
selectedFilterState, selectedFilterState,
selectedOperandInDropdownState, selectedOperandInDropdownState,
onFilterSelectState, onFilterSelectState,

View File

@ -1,4 +1,5 @@
export enum FiltersHotkeyScope { export enum FiltersHotkeyScope {
ObjectFilterDropdownButton = 'filter-dropdown-button', ObjectFilterDropdownButton = 'filter-dropdown-button',
ObjectSortDropdownButton = 'sort-dropdown-button', ObjectSortDropdownButton = 'sort-dropdown-button',
ObjectFilterDropdownOperandDropdown = 'filter-dropdown-operand-dropdown',
} }

View File

@ -21,6 +21,7 @@ import { isDefined } from '~/utils/isDefined';
import { useDropdown } from '../hooks/useDropdown'; import { useDropdown } from '../hooks/useDropdown';
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement'; import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
import { DropdownUnmountEffect } from '@/ui/layout/dropdown/components/DropdownUnmountEffect';
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
import { DropdownMenu } from './DropdownMenu'; import { DropdownMenu } from './DropdownMenu';
import { DropdownOnToggleEffect } from './DropdownOnToggleEffect'; import { DropdownOnToggleEffect } from './DropdownOnToggleEffect';
@ -149,25 +150,38 @@ export const Dropdown = ({
); );
return ( return (
<DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}> <>
<div ref={containerRef} className={className}> <DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}>
{clickableComponent && ( <div ref={containerRef} className={className}>
<div {clickableComponent && (
ref={refs.setReference} <div
onClick={handleClickableComponentClick} ref={refs.setReference}
className={className} onClick={handleClickableComponentClick}
> className={className}
{clickableComponent} >
</div> {clickableComponent}
)} </div>
{hotkey && ( )}
<HotkeyEffect {hotkey && (
hotkey={hotkey} <HotkeyEffect
onHotkeyTriggered={handleHotkeyTriggered} hotkey={hotkey}
/> onHotkeyTriggered={handleHotkeyTriggered}
)} />
{isDropdownOpen && usePortal && ( )}
<FloatingPortal> {isDropdownOpen && usePortal && (
<FloatingPortal>
<DropdownMenu
disableBlur={disableBlur}
width={dropdownMenuWidth ?? dropdownWidth}
data-select-disable
ref={refs.setFloating}
style={floatingStyles}
>
{dropdownComponents}
</DropdownMenu>
</FloatingPortal>
)}
{isDropdownOpen && !usePortal && (
<DropdownMenu <DropdownMenu
disableBlur={disableBlur} disableBlur={disableBlur}
width={dropdownMenuWidth ?? dropdownWidth} width={dropdownMenuWidth ?? dropdownWidth}
@ -177,24 +191,14 @@ export const Dropdown = ({
> >
{dropdownComponents} {dropdownComponents}
</DropdownMenu> </DropdownMenu>
</FloatingPortal> )}
)} <DropdownOnToggleEffect
{isDropdownOpen && !usePortal && ( onDropdownClose={onClose}
<DropdownMenu onDropdownOpen={onOpen}
disableBlur={disableBlur} />
width={dropdownMenuWidth ?? dropdownWidth} </div>
data-select-disable </DropdownScope>
ref={refs.setFloating} <DropdownUnmountEffect dropdownId={dropdownId} />
style={floatingStyles} </>
>
{dropdownComponents}
</DropdownMenu>
)}
<DropdownOnToggleEffect
onDropdownClose={onClose}
onDropdownOpen={onOpen}
/>
</div>
</DropdownScope>
); );
}; };

View File

@ -49,6 +49,7 @@ type DropdownMenuHeaderProps = ComponentProps<'li'> & {
EndIcon?: IconComponent; EndIcon?: IconComponent;
onClick?: (event: MouseEvent<HTMLButtonElement>) => void; onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
testId?: string; testId?: string;
className?: string;
}; };
export const DropdownMenuHeader = ({ export const DropdownMenuHeader = ({
@ -57,12 +58,17 @@ export const DropdownMenuHeader = ({
EndIcon, EndIcon,
onClick, onClick,
testId, testId,
className,
}: DropdownMenuHeaderProps) => { }: DropdownMenuHeaderProps) => {
const theme = useTheme(); const theme = useTheme();
return ( return (
<> <>
{EndIcon && ( {EndIcon && (
<StyledHeader data-testid={testId} onClick={onClick}> <StyledHeader
data-testid={testId}
onClick={onClick}
className={className}
>
<StyledChildrenWrapper>{children}</StyledChildrenWrapper> <StyledChildrenWrapper>{children}</StyledChildrenWrapper>
<StyledEndIcon> <StyledEndIcon>
<EndIcon size={theme.icon.size.md} /> <EndIcon size={theme.icon.size.md} />
@ -70,7 +76,7 @@ export const DropdownMenuHeader = ({
</StyledHeader> </StyledHeader>
)} )}
{StartIcon && ( {StartIcon && (
<StyledHeader data-testid={testId}> <StyledHeader data-testid={testId} className={className}>
<LightIconButton <LightIconButton
testId="dropdown-menu-header-end-icon" testId="dropdown-menu-header-end-icon"
Icon={StartIcon} Icon={StartIcon}

View File

@ -37,12 +37,17 @@ const StyledDropdownMenuItemsInternalContainer = styled.div`
export const DropdownMenuItemsContainer = ({ export const DropdownMenuItemsContainer = ({
children, children,
hasMaxHeight, hasMaxHeight,
className,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
hasMaxHeight?: boolean; hasMaxHeight?: boolean;
className?: string;
}) => { }) => {
return ( return (
<StyledDropdownMenuItemsExternalContainer hasMaxHeight={hasMaxHeight}> <StyledDropdownMenuItemsExternalContainer
hasMaxHeight={hasMaxHeight}
className={className}
>
{hasMaxHeight ? ( {hasMaxHeight ? (
<StyledScrollWrapper contextProviderName="dropdownMenuItemsContainer"> <StyledScrollWrapper contextProviderName="dropdownMenuItemsContainer">
<StyledDropdownMenuItemsInternalContainer> <StyledDropdownMenuItemsInternalContainer>

View File

@ -0,0 +1,18 @@
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { useEffect } from 'react';
export const DropdownUnmountEffect = ({
dropdownId,
}: {
dropdownId: string;
}) => {
const { closeDropdown } = useDropdownV2();
useEffect(() => {
return () => {
closeDropdown(dropdownId);
};
}, [closeDropdown, dropdownId]);
return null;
};

View File

@ -30,7 +30,6 @@ export const EditableFilterDropdownButton = ({
setFilterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown, setSelectedOperandInDropdown,
setSelectedFilter, setSelectedFilter,
setIsObjectFilterDropdownOperandSelectUnfolded,
} = useFilterDropdown({ } = useFilterDropdown({
filterDropdownId: viewFilterDropdownId, filterDropdownId: viewFilterDropdownId,
}); });
@ -87,10 +86,6 @@ export const EditableFilterDropdownButton = ({
} }
}, [viewFilter, deleteCombinedViewFilter]); }, [viewFilter, deleteCombinedViewFilter]);
const handleDropdownClose = useCallback(() => {
setIsObjectFilterDropdownOperandSelectUnfolded(false);
}, [setIsObjectFilterDropdownOperandSelectUnfolded]);
return ( return (
<Dropdown <Dropdown
dropdownId={viewFilterDropdownId} dropdownId={viewFilterDropdownId}
@ -106,7 +101,6 @@ export const EditableFilterDropdownButton = ({
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
onClickOutside={handleDropdownClickOutside} onClickOutside={handleDropdownClickOutside}
onClose={handleDropdownClose}
/> />
); );
}; };