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:
@ -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>
|
||||||
|
|||||||
@ -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={
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 =
|
||||||
|
|||||||
@ -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 />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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} />
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user