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:
nitin
2024-09-27 17:50:21 +05:30
committed by GitHub
parent c4762c3921
commit ca906bbf6b
5 changed files with 154 additions and 117 deletions

View File

@ -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}

View File

@ -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>
); );
}; };

View File

@ -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

View File

@ -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}

View File

@ -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}
/> />
); );
}; };