5922 - UI Overlap and State Persistence in Filter Menus (#7270)
fixes #5922 https://github.com/user-attachments/assets/07d088da-cefb-4d87-9016-e14cef18567d
This commit is contained in:
@ -2,6 +2,7 @@ import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdow
|
|||||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { MultipleFiltersButton } from './MultipleFiltersButton';
|
import { MultipleFiltersButton } from './MultipleFiltersButton';
|
||||||
import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent';
|
import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent';
|
||||||
@ -13,12 +14,18 @@ type MultipleFiltersDropdownButtonProps = {
|
|||||||
export const MultipleFiltersDropdownButton = ({
|
export const MultipleFiltersDropdownButton = ({
|
||||||
hotkeyScope,
|
hotkeyScope,
|
||||||
}: MultipleFiltersDropdownButtonProps) => {
|
}: MultipleFiltersDropdownButtonProps) => {
|
||||||
const { resetFilter } = useFilterDropdown();
|
const { resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded } =
|
||||||
|
useFilterDropdown();
|
||||||
|
|
||||||
|
const handleDropdownClose = useCallback(() => {
|
||||||
|
resetFilter();
|
||||||
|
setIsObjectFilterDropdownOperandSelectUnfolded(false);
|
||||||
|
}, [resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={OBJECT_FILTER_DROPDOWN_ID}
|
dropdownId={OBJECT_FILTER_DROPDOWN_ID}
|
||||||
onClose={resetFilter}
|
onClose={handleDropdownClose}
|
||||||
clickableComponent={<MultipleFiltersButton />}
|
clickableComponent={<MultipleFiltersButton />}
|
||||||
dropdownComponents={<MultipleFiltersDropdownContent />}
|
dropdownComponents={<MultipleFiltersDropdownContent />}
|
||||||
dropdownHotkeyScope={hotkeyScope}
|
dropdownHotkeyScope={hotkeyScope}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||||
|
|
||||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
||||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||||
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
|
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
|
||||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||||
@ -16,6 +14,21 @@ import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSe
|
|||||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||||
import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput';
|
import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledOperandSelectContainer = styled.div`
|
||||||
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
|
left: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
`;
|
||||||
|
|
||||||
type MultipleFiltersDropdownContentProps = {
|
type MultipleFiltersDropdownContentProps = {
|
||||||
filterDropdownId?: string;
|
filterDropdownId?: string;
|
||||||
};
|
};
|
||||||
@ -24,20 +37,23 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
filterDropdownId,
|
filterDropdownId,
|
||||||
}: MultipleFiltersDropdownContentProps) => {
|
}: MultipleFiltersDropdownContentProps) => {
|
||||||
const {
|
const {
|
||||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
} = useFilterDropdown({ filterDropdownId });
|
} = useFilterDropdown({ filterDropdownId });
|
||||||
|
|
||||||
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterDefinitionUsedInDropdown = useRecoilValue(
|
const filterDefinitionUsedInDropdown = useRecoilValue(
|
||||||
filterDefinitionUsedInDropdownState,
|
filterDefinitionUsedInDropdownState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedOperandInDropdown = useRecoilValue(
|
const selectedOperandInDropdown = useRecoilValue(
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEmptyOperand =
|
const isEmptyOperand =
|
||||||
selectedOperandInDropdown &&
|
selectedOperandInDropdown &&
|
||||||
[ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty].includes(
|
[ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty].includes(
|
||||||
@ -45,64 +61,64 @@ export const MultipleFiltersDropdownContent = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StyledContainer>
|
||||||
{!filterDefinitionUsedInDropdown ? (
|
{!filterDefinitionUsedInDropdown ? (
|
||||||
<ObjectFilterDropdownFilterSelect />
|
<ObjectFilterDropdownFilterSelect />
|
||||||
) : isObjectFilterDropdownOperandSelectUnfolded ? (
|
|
||||||
<ObjectFilterDropdownOperandSelect />
|
|
||||||
) : isEmptyOperand ? (
|
|
||||||
<ObjectFilterDropdownOperandButton />
|
|
||||||
) : (
|
) : (
|
||||||
selectedOperandInDropdown && (
|
<>
|
||||||
<>
|
<ObjectFilterDropdownOperandButton />
|
||||||
<ObjectFilterDropdownOperandButton />
|
{isObjectFilterDropdownOperandSelectUnfolded && (
|
||||||
<DropdownMenuSeparator />
|
<StyledOperandSelectContainer>
|
||||||
{[
|
<ObjectFilterDropdownOperandSelect />
|
||||||
'TEXT',
|
</StyledOperandSelectContainer>
|
||||||
'EMAIL',
|
)}
|
||||||
'EMAILS',
|
{!isEmptyOperand && selectedOperandInDropdown && (
|
||||||
'PHONE',
|
<>
|
||||||
'FULL_NAME',
|
{[
|
||||||
'LINK',
|
'TEXT',
|
||||||
'LINKS',
|
'EMAIL',
|
||||||
'ADDRESS',
|
'EMAILS',
|
||||||
'ACTOR',
|
'PHONE',
|
||||||
'ARRAY',
|
'FULL_NAME',
|
||||||
'PHONES',
|
'LINK',
|
||||||
].includes(filterDefinitionUsedInDropdown.type) && (
|
'LINKS',
|
||||||
<ObjectFilterDropdownTextSearchInput />
|
'ADDRESS',
|
||||||
)}
|
'ACTOR',
|
||||||
{['NUMBER', 'CURRENCY'].includes(
|
'ARRAY',
|
||||||
filterDefinitionUsedInDropdown.type,
|
'PHONES',
|
||||||
) && <ObjectFilterDropdownNumberInput />}
|
].includes(filterDefinitionUsedInDropdown.type) && (
|
||||||
{filterDefinitionUsedInDropdown.type === 'RATING' && (
|
<ObjectFilterDropdownTextSearchInput />
|
||||||
<ObjectFilterDropdownRatingInput />
|
)}
|
||||||
)}
|
{['NUMBER', 'CURRENCY'].includes(
|
||||||
{['DATE_TIME', 'DATE'].includes(
|
filterDefinitionUsedInDropdown.type,
|
||||||
filterDefinitionUsedInDropdown.type,
|
) && <ObjectFilterDropdownNumberInput />}
|
||||||
) && <ObjectFilterDropdownDateInput />}
|
{filterDefinitionUsedInDropdown.type === 'RATING' && (
|
||||||
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
<ObjectFilterDropdownRatingInput />
|
||||||
<>
|
)}
|
||||||
<ObjectFilterDropdownSearchInput />
|
{['DATE_TIME', 'DATE'].includes(
|
||||||
<DropdownMenuSeparator />
|
filterDefinitionUsedInDropdown.type,
|
||||||
<ObjectFilterDropdownRecordSelect />
|
) && <ObjectFilterDropdownDateInput />}
|
||||||
</>
|
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
||||||
)}
|
<>
|
||||||
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
|
<ObjectFilterDropdownSearchInput />
|
||||||
<>
|
<ObjectFilterDropdownRecordSelect />
|
||||||
<ObjectFilterDropdownSearchInput />
|
</>
|
||||||
<DropdownMenuSeparator />
|
)}
|
||||||
<ObjectFilterDropdownOptionSelect />
|
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
|
||||||
</>
|
<>
|
||||||
)}
|
<ObjectFilterDropdownSearchInput />
|
||||||
</>
|
<ObjectFilterDropdownOptionSelect />
|
||||||
)
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<MultipleFiltersDropdownFilterOnFilterChangedEffect
|
<MultipleFiltersDropdownFilterOnFilterChangedEffect
|
||||||
filterDefinitionUsedInDropdownType={
|
filterDefinitionUsedInDropdownType={
|
||||||
filterDefinitionUsedInDropdown?.type
|
filterDefinitionUsedInDropdown?.type
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,19 +10,11 @@ export const ObjectFilterDropdownOperandButton = () => {
|
|||||||
const {
|
const {
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
|
||||||
} = useFilterDropdown();
|
} = useFilterDropdown();
|
||||||
|
|
||||||
const selectedOperandInDropdown = useRecoilValue(
|
const selectedOperandInDropdown = useRecoilValue(
|
||||||
selectedOperandInDropdownState,
|
selectedOperandInDropdownState,
|
||||||
);
|
);
|
||||||
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
|
||||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isObjectFilterDropdownOperandSelectUnfolded) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
|
|||||||
@ -39,6 +39,21 @@ export const StyledInput = styled.input`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledSelectedSortDirectionContainer = styled.div`
|
||||||
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
|
left: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
`;
|
||||||
|
|
||||||
export type ObjectSortDropdownButtonProps = {
|
export type ObjectSortDropdownButtonProps = {
|
||||||
sortDropdownId: string;
|
sortDropdownId: string;
|
||||||
hotkeyScope: HotkeyScope;
|
hotkeyScope: HotkeyScope;
|
||||||
@ -95,60 +110,61 @@ export const ObjectSortDropdownButton = ({
|
|||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<>
|
<>
|
||||||
{isSortDirectionMenuUnfolded ? (
|
{isSortDirectionMenuUnfolded && (
|
||||||
<DropdownMenuItemsContainer>
|
<StyledSelectedSortDirectionContainer>
|
||||||
{SORT_DIRECTIONS.map((sortOrder, index) => (
|
|
||||||
<MenuItem
|
|
||||||
key={index}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedSortDirection(sortOrder);
|
|
||||||
setIsSortDirectionMenuUnfolded(false);
|
|
||||||
}}
|
|
||||||
text={sortOrder === 'asc' ? 'Ascending' : 'Descending'}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<DropdownMenuHeader
|
|
||||||
EndIcon={IconChevronDown}
|
|
||||||
onClick={() => setIsSortDirectionMenuUnfolded(true)}
|
|
||||||
>
|
|
||||||
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
|
||||||
</DropdownMenuHeader>
|
|
||||||
<StyledInput
|
|
||||||
autoFocus
|
|
||||||
value={objectSortDropdownSearchInput}
|
|
||||||
placeholder="Search fields"
|
|
||||||
onChange={(event) =>
|
|
||||||
setObjectSortDropdownSearchInput(event.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{[...availableSortDefinitions]
|
{SORT_DIRECTIONS.map((sortOrder, index) => (
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
<MenuItem
|
||||||
.filter((item) =>
|
key={index}
|
||||||
item.label
|
onClick={() => {
|
||||||
.toLocaleLowerCase()
|
setSelectedSortDirection(sortOrder);
|
||||||
.includes(
|
setIsSortDirectionMenuUnfolded(false);
|
||||||
objectSortDropdownSearchInput.toLocaleLowerCase(),
|
}}
|
||||||
),
|
text={sortOrder === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
)
|
/>
|
||||||
.map((availableSortDefinition, index) => (
|
))}
|
||||||
<MenuItem
|
|
||||||
testId={`select-sort-${index}`}
|
|
||||||
key={index}
|
|
||||||
onClick={() => {
|
|
||||||
setObjectSortDropdownSearchInput('');
|
|
||||||
handleAddSort(availableSortDefinition);
|
|
||||||
}}
|
|
||||||
LeftIcon={getIcon(availableSortDefinition.iconName)}
|
|
||||||
text={availableSortDefinition.label}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</>
|
</StyledSelectedSortDirectionContainer>
|
||||||
)}
|
)}
|
||||||
|
<StyledContainer>
|
||||||
|
<DropdownMenuHeader
|
||||||
|
EndIcon={IconChevronDown}
|
||||||
|
onClick={() => setIsSortDirectionMenuUnfolded(true)}
|
||||||
|
>
|
||||||
|
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
|
</DropdownMenuHeader>
|
||||||
|
<StyledInput
|
||||||
|
autoFocus
|
||||||
|
value={objectSortDropdownSearchInput}
|
||||||
|
placeholder="Search fields"
|
||||||
|
onChange={(event) =>
|
||||||
|
setObjectSortDropdownSearchInput(event.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{[...availableSortDefinitions]
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
|
.filter((item) =>
|
||||||
|
item.label
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(
|
||||||
|
objectSortDropdownSearchInput.toLocaleLowerCase(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map((availableSortDefinition, index) => (
|
||||||
|
<MenuItem
|
||||||
|
testId={`select-sort-${index}`}
|
||||||
|
key={index}
|
||||||
|
onClick={() => {
|
||||||
|
setObjectSortDropdownSearchInput('');
|
||||||
|
handleAddSort(availableSortDefinition);
|
||||||
|
}}
|
||||||
|
LeftIcon={getIcon(availableSortDefinition.iconName)}
|
||||||
|
text={availableSortDefinition.label}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</StyledContainer>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
onClose={handleDropdownButtonClose}
|
onClose={handleDropdownButtonClose}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export const EditableFilterDropdownButton = ({
|
|||||||
setFilterDefinitionUsedInDropdown,
|
setFilterDefinitionUsedInDropdown,
|
||||||
setSelectedOperandInDropdown,
|
setSelectedOperandInDropdown,
|
||||||
setSelectedFilter,
|
setSelectedFilter,
|
||||||
|
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||||
} = useFilterDropdown({
|
} = useFilterDropdown({
|
||||||
filterDropdownId: viewFilterDropdownId,
|
filterDropdownId: viewFilterDropdownId,
|
||||||
});
|
});
|
||||||
@ -79,6 +80,10 @@ export const EditableFilterDropdownButton = ({
|
|||||||
}
|
}
|
||||||
}, [viewFilter, deleteCombinedViewFilter]);
|
}, [viewFilter, deleteCombinedViewFilter]);
|
||||||
|
|
||||||
|
const handleDropdownClose = useCallback(() => {
|
||||||
|
setIsObjectFilterDropdownOperandSelectUnfolded(false);
|
||||||
|
}, [setIsObjectFilterDropdownOperandSelectUnfolded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={viewFilterDropdownId}
|
dropdownId={viewFilterDropdownId}
|
||||||
@ -94,6 +99,7 @@ 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