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 { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { MultipleFiltersButton } from './MultipleFiltersButton';
|
||||
import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent';
|
||||
@ -13,12 +14,18 @@ type MultipleFiltersDropdownButtonProps = {
|
||||
export const MultipleFiltersDropdownButton = ({
|
||||
hotkeyScope,
|
||||
}: MultipleFiltersDropdownButtonProps) => {
|
||||
const { resetFilter } = useFilterDropdown();
|
||||
const { resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded } =
|
||||
useFilterDropdown();
|
||||
|
||||
const handleDropdownClose = useCallback(() => {
|
||||
resetFilter();
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded(false);
|
||||
}, [resetFilter, setIsObjectFilterDropdownOperandSelectUnfolded]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={OBJECT_FILTER_DROPDOWN_ID}
|
||||
onClose={resetFilter}
|
||||
onClose={handleDropdownClose}
|
||||
clickableComponent={<MultipleFiltersButton />}
|
||||
dropdownComponents={<MultipleFiltersDropdownContent />}
|
||||
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 { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
|
||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||
@ -16,6 +14,21 @@ import { ObjectFilterDropdownOptionSelect } from './ObjectFilterDropdownOptionSe
|
||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||
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 = {
|
||||
filterDropdownId?: string;
|
||||
};
|
||||
@ -24,20 +37,23 @@ export const MultipleFiltersDropdownContent = ({
|
||||
filterDropdownId,
|
||||
}: MultipleFiltersDropdownContentProps) => {
|
||||
const {
|
||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||
filterDefinitionUsedInDropdownState,
|
||||
selectedOperandInDropdownState,
|
||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||
} = useFilterDropdown({ filterDropdownId });
|
||||
|
||||
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||
);
|
||||
|
||||
const filterDefinitionUsedInDropdown = useRecoilValue(
|
||||
filterDefinitionUsedInDropdownState,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilValue(
|
||||
selectedOperandInDropdownState,
|
||||
);
|
||||
|
||||
const isEmptyOperand =
|
||||
selectedOperandInDropdown &&
|
||||
[ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty].includes(
|
||||
@ -45,64 +61,64 @@ export const MultipleFiltersDropdownContent = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContainer>
|
||||
{!filterDefinitionUsedInDropdown ? (
|
||||
<ObjectFilterDropdownFilterSelect />
|
||||
) : isObjectFilterDropdownOperandSelectUnfolded ? (
|
||||
<ObjectFilterDropdownOperandSelect />
|
||||
) : isEmptyOperand ? (
|
||||
<ObjectFilterDropdownOperandButton />
|
||||
) : (
|
||||
selectedOperandInDropdown && (
|
||||
<>
|
||||
<ObjectFilterDropdownOperandButton />
|
||||
<DropdownMenuSeparator />
|
||||
{[
|
||||
'TEXT',
|
||||
'EMAIL',
|
||||
'EMAILS',
|
||||
'PHONE',
|
||||
'FULL_NAME',
|
||||
'LINK',
|
||||
'LINKS',
|
||||
'ADDRESS',
|
||||
'ACTOR',
|
||||
'ARRAY',
|
||||
'PHONES',
|
||||
].includes(filterDefinitionUsedInDropdown.type) && (
|
||||
<ObjectFilterDropdownTextSearchInput />
|
||||
)}
|
||||
{['NUMBER', 'CURRENCY'].includes(
|
||||
filterDefinitionUsedInDropdown.type,
|
||||
) && <ObjectFilterDropdownNumberInput />}
|
||||
{filterDefinitionUsedInDropdown.type === 'RATING' && (
|
||||
<ObjectFilterDropdownRatingInput />
|
||||
)}
|
||||
{['DATE_TIME', 'DATE'].includes(
|
||||
filterDefinitionUsedInDropdown.type,
|
||||
) && <ObjectFilterDropdownDateInput />}
|
||||
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect />
|
||||
</>
|
||||
)}
|
||||
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownOptionSelect />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
<>
|
||||
<ObjectFilterDropdownOperandButton />
|
||||
{isObjectFilterDropdownOperandSelectUnfolded && (
|
||||
<StyledOperandSelectContainer>
|
||||
<ObjectFilterDropdownOperandSelect />
|
||||
</StyledOperandSelectContainer>
|
||||
)}
|
||||
{!isEmptyOperand && selectedOperandInDropdown && (
|
||||
<>
|
||||
{[
|
||||
'TEXT',
|
||||
'EMAIL',
|
||||
'EMAILS',
|
||||
'PHONE',
|
||||
'FULL_NAME',
|
||||
'LINK',
|
||||
'LINKS',
|
||||
'ADDRESS',
|
||||
'ACTOR',
|
||||
'ARRAY',
|
||||
'PHONES',
|
||||
].includes(filterDefinitionUsedInDropdown.type) && (
|
||||
<ObjectFilterDropdownTextSearchInput />
|
||||
)}
|
||||
{['NUMBER', 'CURRENCY'].includes(
|
||||
filterDefinitionUsedInDropdown.type,
|
||||
) && <ObjectFilterDropdownNumberInput />}
|
||||
{filterDefinitionUsedInDropdown.type === 'RATING' && (
|
||||
<ObjectFilterDropdownRatingInput />
|
||||
)}
|
||||
{['DATE_TIME', 'DATE'].includes(
|
||||
filterDefinitionUsedInDropdown.type,
|
||||
) && <ObjectFilterDropdownDateInput />}
|
||||
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<ObjectFilterDropdownRecordSelect />
|
||||
</>
|
||||
)}
|
||||
{filterDefinitionUsedInDropdown.type === 'SELECT' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
<ObjectFilterDropdownOptionSelect />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<MultipleFiltersDropdownFilterOnFilterChangedEffect
|
||||
filterDefinitionUsedInDropdownType={
|
||||
filterDefinitionUsedInDropdown?.type
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -10,19 +10,11 @@ export const ObjectFilterDropdownOperandButton = () => {
|
||||
const {
|
||||
selectedOperandInDropdownState,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const selectedOperandInDropdown = useRecoilValue(
|
||||
selectedOperandInDropdownState,
|
||||
);
|
||||
const isObjectFilterDropdownOperandSelectUnfolded = useRecoilValue(
|
||||
isObjectFilterDropdownOperandSelectUnfoldedState,
|
||||
);
|
||||
|
||||
if (isObjectFilterDropdownOperandSelectUnfolded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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 = {
|
||||
sortDropdownId: string;
|
||||
hotkeyScope: HotkeyScope;
|
||||
@ -95,60 +110,61 @@ export const ObjectSortDropdownButton = ({
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
{isSortDirectionMenuUnfolded ? (
|
||||
<DropdownMenuItemsContainer>
|
||||
{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)
|
||||
}
|
||||
/>
|
||||
{isSortDirectionMenuUnfolded && (
|
||||
<StyledSelectedSortDirectionContainer>
|
||||
<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}
|
||||
/>
|
||||
))}
|
||||
{SORT_DIRECTIONS.map((sortOrder, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setSelectedSortDirection(sortOrder);
|
||||
setIsSortDirectionMenuUnfolded(false);
|
||||
}}
|
||||
text={sortOrder === 'asc' ? 'Ascending' : 'Descending'}
|
||||
/>
|
||||
))}
|
||||
</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}
|
||||
|
||||
@ -29,6 +29,7 @@ export const EditableFilterDropdownButton = ({
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setSelectedOperandInDropdown,
|
||||
setSelectedFilter,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
} = useFilterDropdown({
|
||||
filterDropdownId: viewFilterDropdownId,
|
||||
});
|
||||
@ -79,6 +80,10 @@ export const EditableFilterDropdownButton = ({
|
||||
}
|
||||
}, [viewFilter, deleteCombinedViewFilter]);
|
||||
|
||||
const handleDropdownClose = useCallback(() => {
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded(false);
|
||||
}, [setIsObjectFilterDropdownOperandSelectUnfolded]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={viewFilterDropdownId}
|
||||
@ -94,6 +99,7 @@ export const EditableFilterDropdownButton = ({
|
||||
dropdownOffset={{ y: 8, x: 0 }}
|
||||
dropdownPlacement="bottom-start"
|
||||
onClickOutside={handleDropdownClickOutside}
|
||||
onClose={handleDropdownClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user