Improved dropdown menu headers for filter and sorts (#13177)
This PR improves dropdown menu headers for filter and sort dropdown in view bar and editable filter chips. It adds what's necessary to navigate back or close the dropdown, so that we don't rely solely on click outside to exit the dropdown. This PR also refactors the components so that we clearly identify the two code paths that can use filter dropdowns : view bar and filter chip, everything that can be DRY stays in the object-filter-dropdown module but we try to have our wrapping components in each distinct module instead of blending everything with ternaries inside object-filter-dropdown module. The vector search input value wasn't correctly handled across the different dropdowns, due to a wrong component instance management, since the dropdown menu header improvement put this into light, I also refactored the state management of the vector search input. @Bonapara please check the QA video and tell me if it's ok, I didn't add dropdown menu header on the advanced filter field list dropdown because it's a select more than a standalone dropdown, what do you think ? QA : https://github.com/user-attachments/assets/17080f32-f302-436c-937b-3577715b7e84 QA Vector search fix : https://github.com/user-attachments/assets/6367bbf6-8a98-4b53-86cf-6ba92be130eb Fixes https://github.com/twentyhq/core-team-issues/issues/640 Fixes https://github.com/twentyhq/core-team-issues/issues/1206
This commit is contained in:
@ -0,0 +1,38 @@
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||
import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const ObjectFilterDropdownContentWrapper = ({
|
||||
children,
|
||||
}: React.PropsWithChildren) => {
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const isDateFilter = DATE_FILTER_TYPES.includes(filterType);
|
||||
|
||||
return (
|
||||
<DropdownContent
|
||||
widthInPixels={
|
||||
isDateFilter
|
||||
? DATE_PICKER_DROPDOWN_CONTENT_WIDTH
|
||||
: GenericDropdownContentWidth.ExtraLarge
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
@ -5,22 +5,19 @@ import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-d
|
||||
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { ViewBarFilterDropdownVectorSearchInput } from '@/views/components/ViewBarFilterDropdownVectorSearchInput';
|
||||
|
||||
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
||||
import { ObjectFilterDropdownFilterInputHeader } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInputHeader';
|
||||
import { ObjectFilterDropdownInnerSelectOperandDropdown } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownInnerSelectOperandDropdown';
|
||||
import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
|
||||
import { ObjectFilterDropdownVectorSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownVectorSearchInput';
|
||||
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||
import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth';
|
||||
import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes';
|
||||
import { TEXT_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/TextFilterTypes';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@ -62,11 +59,7 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
selectedOperandInDropdown === ViewFilterOperand.VectorSearch;
|
||||
|
||||
if (isVectorSearchFilter && isDefined(filterDropdownId)) {
|
||||
return (
|
||||
<ViewBarFilterDropdownVectorSearchInput
|
||||
filterDropdownId={filterDropdownId}
|
||||
/>
|
||||
);
|
||||
return <ObjectFilterDropdownVectorSearchInput />;
|
||||
}
|
||||
|
||||
if (!isDefined(fieldMetadataItemUsedInDropdown)) {
|
||||
@ -82,24 +75,21 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
|
||||
if (isOnlyOperand) {
|
||||
return (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<ObjectFilterDropdownFilterInputHeader />
|
||||
<>
|
||||
<ObjectFilterDropdownInnerSelectOperandDropdown />
|
||||
</DropdownContent>
|
||||
</>
|
||||
);
|
||||
} else if (isDateFilter) {
|
||||
return (
|
||||
<DropdownContent widthInPixels={DATE_PICKER_DROPDOWN_CONTENT_WIDTH}>
|
||||
<ObjectFilterDropdownFilterInputHeader />
|
||||
<>
|
||||
<ObjectFilterDropdownInnerSelectOperandDropdown />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownDateInput />
|
||||
</DropdownContent>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<ObjectFilterDropdownFilterInputHeader />
|
||||
<>
|
||||
<ObjectFilterDropdownInnerSelectOperandDropdown />
|
||||
<DropdownMenuSeparator />
|
||||
{TEXT_FILTER_TYPES.includes(filterType) && (
|
||||
@ -130,7 +120,7 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
</>
|
||||
)}
|
||||
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
|
||||
</DropdownContent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,16 +1,56 @@
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewBarFilterDropdownFilterInputMenuHeader } from '@/views/components/ViewBarFilterDropdownFilterInputMenuHeader';
|
||||
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { IconX } from 'twenty-ui/display';
|
||||
|
||||
export const ObjectFilterDropdownFilterInputHeader = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenuHeader>
|
||||
{fieldMetadataItemUsedInDropdown?.label}
|
||||
</DropdownMenuHeader>
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const dropdownInstanceId = useContext(
|
||||
DropdownComponentInstanceContext,
|
||||
)?.instanceId;
|
||||
|
||||
const isInViewBarFilterDropdown =
|
||||
dropdownInstanceId === VIEW_BAR_FILTER_DROPDOWN_ID;
|
||||
|
||||
const isVectorSearchFilter =
|
||||
selectedOperandInDropdown === ViewFilterOperand.VectorSearch;
|
||||
|
||||
if (isInViewBarFilterDropdown) {
|
||||
return <ViewBarFilterDropdownFilterInputMenuHeader />;
|
||||
} else {
|
||||
return (
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={() => closeDropdown(dropdownInstanceId)}
|
||||
Icon={IconX}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isVectorSearchFilter
|
||||
? t`Search`
|
||||
: fieldMetadataItemUsedInDropdown?.label}
|
||||
</DropdownMenuHeader>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||
import { DATE_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/object-record/object-filter-dropdown/constants/DatePickerDropdownContentWidth';
|
||||
import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownOperand';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
@ -7,6 +9,7 @@ import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/ge
|
||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { DropdownMenuInnerSelect } from '@/ui/layout/dropdown/components/DropdownMenuInnerSelect';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
@ -54,16 +57,30 @@ export const ObjectFilterDropdownInnerSelectOperandDropdown = () => {
|
||||
);
|
||||
};
|
||||
|
||||
if (!isDefined(selectedOperandInDropdown)) {
|
||||
if (
|
||||
!isDefined(selectedOperandInDropdown) ||
|
||||
!isDefined(fieldMetadataItemUsedInDropdown)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const isDateFilter = DATE_FILTER_TYPES.includes(filterType);
|
||||
|
||||
const widthInPixels = isDateFilter
|
||||
? DATE_PICKER_DROPDOWN_CONTENT_WIDTH
|
||||
: GenericDropdownContentWidth.ExtraLarge;
|
||||
|
||||
return (
|
||||
<DropdownMenuInnerSelect
|
||||
dropdownId={OBJECT_FILTER_DROPDOWN_INNER_SELECT_OPERAND_DROPDOWN_ID}
|
||||
selectedOption={selectedOption}
|
||||
onChange={handleOperandChange}
|
||||
options={options}
|
||||
widthInPixels={widthInPixels}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useVectorSearchFilterActions } from '@/views/hooks/useVectorSearchFilterActions';
|
||||
import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
export const ObjectFilterDropdownVectorSearchInput = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const [vectorSearchInputValue, setVectorSearchInputValue] =
|
||||
useRecoilComponentStateV2(vectorSearchInputComponentState);
|
||||
|
||||
const { applyVectorSearchFilter } = useVectorSearchFilterActions();
|
||||
|
||||
const debouncedApplyVectorSearchFilter = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
applyVectorSearchFilter(value);
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value;
|
||||
setVectorSearchInputValue(inputValue);
|
||||
debouncedApplyVectorSearchFilter(inputValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
value={vectorSearchInputValue}
|
||||
placeholder={t`Search`}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,5 +1,3 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId';
|
||||
@ -20,7 +18,10 @@ import { visibleTableColumnsComponentSelector } from '@/object-record/record-tab
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { DropdownMenuInnerSelect } from '@/ui/layout/dropdown/components/DropdownMenuInnerSelect';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
|
||||
@ -32,54 +33,12 @@ import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconChevronDown, useIcons } from 'twenty-ui/display';
|
||||
import { IconX, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-top: none;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-radius: 0;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
height: 19px;
|
||||
font-family: inherit;
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
|
||||
font-weight: inherit;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSelectedSortDirectionContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
`;
|
||||
|
||||
const StyledDropdownMenuHeaderEndComponent = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const ObjectSortDropdownButton = () => {
|
||||
const { resetRecordSortDropdownSearchInput } =
|
||||
useResetRecordSortDropdownSearchInput();
|
||||
@ -88,10 +47,6 @@ export const ObjectSortDropdownButton = () => {
|
||||
objectSortDropdownSearchInputComponentState,
|
||||
);
|
||||
|
||||
const isRecordSortDirectionMenuUnfolded = useRecoilComponentValueV2(
|
||||
isRecordSortDirectionDropdownMenuUnfoldedComponentState,
|
||||
);
|
||||
|
||||
const { resetSortDropdown } = useResetSortDropdown();
|
||||
|
||||
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
@ -195,8 +150,6 @@ export const ObjectSortDropdownButton = () => {
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const selectableItemIdArray = [
|
||||
...visibleFieldMetadataItems.map((item) => item.id),
|
||||
...hiddenFieldMetadataItems.map((item) => item.id),
|
||||
@ -227,51 +180,50 @@ export const ObjectSortDropdownButton = () => {
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={() => closeSortDropdown()}
|
||||
Icon={IconX}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t`Sort`}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuInnerSelect
|
||||
dropdownId="record-sort-direction-dropdown"
|
||||
options={RECORD_SORT_DIRECTIONS.map((sortDirection) => ({
|
||||
value: sortDirection,
|
||||
label: sortDirection === 'asc' ? t`Ascending` : t`Descending`,
|
||||
}))}
|
||||
selectedOption={{
|
||||
value: selectedRecordSortDirection,
|
||||
label:
|
||||
selectedRecordSortDirection === 'asc'
|
||||
? t`Ascending`
|
||||
: t`Descending`,
|
||||
}}
|
||||
onChange={(sortDirection) =>
|
||||
handleSortDirectionClick(
|
||||
sortDirection.value as RecordSortDirection,
|
||||
)
|
||||
}
|
||||
widthInPixels={GenericDropdownContentWidth.ExtraLarge}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
value={objectSortDropdownSearchInput}
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) =>
|
||||
setObjectSortDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
<SelectableList
|
||||
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
focusId={OBJECT_SORT_DROPDOWN_ID}
|
||||
>
|
||||
{isRecordSortDirectionMenuUnfolded && (
|
||||
<StyledSelectedSortDirectionContainer>
|
||||
<DropdownMenuItemsContainer>
|
||||
{RECORD_SORT_DIRECTIONS.map((sortDirection, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
focused={selectedItemId === sortDirection}
|
||||
onClick={() => handleSortDirectionClick(sortDirection)}
|
||||
text={
|
||||
sortDirection === 'asc' ? t`Ascending` : t`Descending`
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledSelectedSortDirectionContainer>
|
||||
)}
|
||||
<DropdownMenuHeader
|
||||
onClick={() =>
|
||||
setIsRecordSortDirectionMenuUnfolded(
|
||||
!isRecordSortDirectionMenuUnfolded,
|
||||
)
|
||||
}
|
||||
EndComponent={
|
||||
<StyledDropdownMenuHeaderEndComponent>
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</StyledDropdownMenuHeaderEndComponent>
|
||||
}
|
||||
>
|
||||
{selectedRecordSortDirection === 'asc'
|
||||
? t`Ascending`
|
||||
: t`Descending`}
|
||||
</DropdownMenuHeader>
|
||||
<StyledInput
|
||||
autoFocus
|
||||
value={objectSortDropdownSearchInput}
|
||||
placeholder={t`Search fields`}
|
||||
onChange={(event) =>
|
||||
setObjectSortDropdownSearchInput(event.target.value)
|
||||
}
|
||||
/>
|
||||
{shouldShowVisibleFields && (
|
||||
<>
|
||||
<DropdownMenuSectionLabel label={t`Visible fields`} />
|
||||
|
||||
@ -33,6 +33,7 @@ export type DropdownMenuInnerSelectProps = {
|
||||
onChange: (value: SelectOption) => void;
|
||||
options: SelectOption[];
|
||||
dropdownId: string;
|
||||
widthInPixels?: number;
|
||||
};
|
||||
|
||||
export const DropdownMenuInnerSelect = ({
|
||||
@ -40,6 +41,7 @@ export const DropdownMenuInnerSelect = ({
|
||||
onChange,
|
||||
options,
|
||||
dropdownId,
|
||||
widthInPixels,
|
||||
}: DropdownMenuInnerSelectProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@ -54,7 +56,7 @@ export const DropdownMenuInnerSelect = ({
|
||||
</StyledDropdownMenuInnerSelectDropdownButton>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownContent widthInPixels={widthInPixels}>
|
||||
<DropdownMenuItemsContainer>
|
||||
{options.map((selectOption) => (
|
||||
<MenuItemSelect
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import { ObjectFilterDropdownContentWrapper } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownContentWrapper';
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { EditableFilterChipDropdownMenuHeader } from '@/views/components/EditableFilterChipDropdownMenuHeader';
|
||||
|
||||
type EditableFilterChipDropdownContentProps = {
|
||||
recordFilterId: string;
|
||||
};
|
||||
|
||||
export const EditableFilterChipDropdownContent = ({
|
||||
recordFilterId,
|
||||
}: EditableFilterChipDropdownContentProps) => {
|
||||
return (
|
||||
<ObjectFilterDropdownContentWrapper>
|
||||
<EditableFilterChipDropdownMenuHeader />
|
||||
<ObjectFilterDropdownFilterInput
|
||||
filterDropdownId={recordFilterId}
|
||||
recordFilterId={recordFilterId}
|
||||
/>
|
||||
</ObjectFilterDropdownContentWrapper>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,45 @@
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { IconX } from 'twenty-ui/display';
|
||||
|
||||
export const EditableFilterChipDropdownMenuHeader = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const isVectorSearchFilter =
|
||||
selectedOperandInDropdown === ViewFilterOperand.VectorSearch;
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const handleBackButtonClick = () => {
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={handleBackButtonClick}
|
||||
Icon={IconX}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isVectorSearchFilter
|
||||
? t`Search`
|
||||
: fieldMetadataItemUsedInDropdown?.label}
|
||||
</DropdownMenuHeader>
|
||||
);
|
||||
};
|
||||
@ -4,10 +4,11 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { EditableFilterChip } from '@/views/components/EditableFilterChip';
|
||||
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
|
||||
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { EditableFilterChipDropdownContent } from '@/views/components/EditableFilterChipDropdownContent';
|
||||
import { useClearVectorSearchInput } from '@/views/hooks/useClearVectorSearchInput';
|
||||
import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates';
|
||||
|
||||
type EditableFilterDropdownButtonProps = {
|
||||
@ -29,13 +30,17 @@ export const EditableFilterDropdownButton = ({
|
||||
removeRecordFilter({ recordFilterId: recordFilter.id });
|
||||
};
|
||||
|
||||
const { clearVectorSearchInput } = useClearVectorSearchInput();
|
||||
|
||||
const onFilterDropdownClose = useCallback(() => {
|
||||
const recordFilterIsEmpty = isRecordFilterConsideredEmpty(recordFilter);
|
||||
|
||||
if (recordFilterIsEmpty) {
|
||||
removeRecordFilter({ recordFilterId: recordFilter.id });
|
||||
}
|
||||
}, [recordFilter, removeRecordFilter]);
|
||||
|
||||
clearVectorSearchInput();
|
||||
}, [recordFilter, removeRecordFilter, clearVectorSearchInput]);
|
||||
|
||||
const { setEditableFilterChipDropdownStates } =
|
||||
useSetEditableFilterChipDropdownStates();
|
||||
@ -56,7 +61,7 @@ export const EditableFilterDropdownButton = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<ObjectFilterDropdownFilterInput filterDropdownId={recordFilter.id} />
|
||||
<EditableFilterChipDropdownContent recordFilterId={recordFilter.id} />
|
||||
}
|
||||
dropdownOffset={{ y: 8, x: 0 }}
|
||||
dropdownPlacement="bottom-start"
|
||||
|
||||
@ -8,6 +8,7 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe
|
||||
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewBarFilterDropdownContent } from '@/views/components/ViewBarFilterDropdownContent';
|
||||
import { useClearVectorSearchInput } from '@/views/hooks/useClearVectorSearchInput';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ViewBarFilterButton } from './ViewBarFilterButton';
|
||||
|
||||
@ -20,6 +21,8 @@ export const ViewBarFilterDropdown = () => {
|
||||
objectFilterDropdownCurrentRecordFilterComponentState,
|
||||
);
|
||||
|
||||
const { clearVectorSearchInput } = useClearVectorSearchInput();
|
||||
|
||||
const handleDropdownClickOutside = () => {
|
||||
const recordFilterIsEmpty =
|
||||
isDefined(objectFilterDropdownCurrentRecordFilter) &&
|
||||
@ -37,6 +40,7 @@ export const ViewBarFilterDropdown = () => {
|
||||
const handleDropdownClose = () => {
|
||||
resetFilterDropdown();
|
||||
removeEmptyVectorSearchFilter();
|
||||
clearVectorSearchInput();
|
||||
};
|
||||
|
||||
const handleDropdownOpen = () => {
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewBarFilterDropdownFieldSelectMenu } from '@/views/components/ViewBarFilterDropdownFieldSelectMenu';
|
||||
import { ViewBarFilterDropdownFilterInput } from '@/views/components/ViewBarFilterDropdownFilterInput';
|
||||
import { ViewBarFilterDropdownVectorSearchInput } from '@/views/components/ViewBarFilterDropdownVectorSearchInput';
|
||||
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
export const ViewBarFilterDropdownContent = () => {
|
||||
const [objectFilterDropdownFilterIsSelected] = useRecoilComponentStateV2(
|
||||
@ -10,14 +14,23 @@ export const ViewBarFilterDropdownContent = () => {
|
||||
VIEW_BAR_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const isVectorSearchFilter =
|
||||
selectedOperandInDropdown === ViewFilterOperand.VectorSearch;
|
||||
|
||||
if (isVectorSearchFilter) {
|
||||
return <ViewBarFilterDropdownVectorSearchInput />;
|
||||
}
|
||||
|
||||
const shouldShowFilterInput = objectFilterDropdownFilterIsSelected;
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldShowFilterInput ? (
|
||||
<ObjectFilterDropdownFilterInput
|
||||
filterDropdownId={VIEW_BAR_FILTER_DROPDOWN_ID}
|
||||
/>
|
||||
<ViewBarFilterDropdownFilterInput />
|
||||
) : (
|
||||
<ViewBarFilterDropdownFieldSelectMenu />
|
||||
)}
|
||||
|
||||
@ -16,10 +16,14 @@ import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/
|
||||
import { ViewBarFilterDropdownBottomMenu } from '@/views/components/ViewBarFilterDropdownBottomMenu';
|
||||
import { ViewBarFilterDropdownFieldSelectMenuItem } from '@/views/components/ViewBarFilterDropdownFieldSelectMenuItem';
|
||||
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds';
|
||||
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { IconX } from 'twenty-ui/display';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
background: transparent;
|
||||
@ -56,6 +60,8 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => {
|
||||
selectableVisibleFieldMetadataItems,
|
||||
} = useFilterDropdownSelectableFieldMetadataItems();
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const selectableFieldMetadataItemIds = [
|
||||
...selectableVisibleFieldMetadataItems.map(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id,
|
||||
@ -83,6 +89,16 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => {
|
||||
|
||||
return (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={() => closeDropdown()}
|
||||
Icon={IconX}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t`Filter`}
|
||||
</DropdownMenuHeader>
|
||||
<StyledInput
|
||||
value={objectFilterDropdownSearchInput}
|
||||
autoFocus
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import { ObjectFilterDropdownContentWrapper } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownContentWrapper';
|
||||
import { ObjectFilterDropdownFilterInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput';
|
||||
import { ViewBarFilterDropdownFilterInputMenuHeader } from '@/views/components/ViewBarFilterDropdownFilterInputMenuHeader';
|
||||
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
|
||||
|
||||
type ViewBarFilterDropdownFilterInputProps = {
|
||||
recordFilterId?: string;
|
||||
};
|
||||
|
||||
export const ViewBarFilterDropdownFilterInput = ({
|
||||
recordFilterId,
|
||||
}: ViewBarFilterDropdownFilterInputProps) => {
|
||||
return (
|
||||
<ObjectFilterDropdownContentWrapper>
|
||||
<ViewBarFilterDropdownFilterInputMenuHeader />
|
||||
<ObjectFilterDropdownFilterInput
|
||||
filterDropdownId={VIEW_BAR_FILTER_DROPDOWN_ID}
|
||||
recordFilterId={recordFilterId}
|
||||
/>
|
||||
</ObjectFilterDropdownContentWrapper>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,49 @@
|
||||
import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useResetFilterDropdown';
|
||||
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useClearVectorSearchInput } from '@/views/hooks/useClearVectorSearchInput';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { IconChevronLeft } from 'twenty-ui/display';
|
||||
|
||||
export const ViewBarFilterDropdownFilterInputMenuHeader = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
|
||||
fieldMetadataItemUsedInDropdownComponentSelector,
|
||||
);
|
||||
|
||||
const selectedOperandInDropdown = useRecoilComponentValueV2(
|
||||
selectedOperandInDropdownComponentState,
|
||||
);
|
||||
|
||||
const isVectorSearchFilter =
|
||||
selectedOperandInDropdown === ViewFilterOperand.VectorSearch;
|
||||
|
||||
const { clearVectorSearchInput } = useClearVectorSearchInput();
|
||||
|
||||
const { resetFilterDropdown } = useResetFilterDropdown();
|
||||
|
||||
const handleBackButtonClick = () => {
|
||||
resetFilterDropdown();
|
||||
clearVectorSearchInput();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={handleBackButtonClick}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{isVectorSearchFilter
|
||||
? t`Search`
|
||||
: fieldMetadataItemUsedInDropdown?.label}
|
||||
</DropdownMenuHeader>
|
||||
);
|
||||
};
|
||||
@ -9,7 +9,6 @@ import { IconSearch } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds';
|
||||
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
|
||||
|
||||
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
|
||||
import { useOpenVectorSearchFilter } from '@/views/hooks/useOpenVectorSearchFilter';
|
||||
@ -26,20 +25,16 @@ export const ViewBarFilterDropdownVectorSearchButton = () => {
|
||||
const { t } = useLingui();
|
||||
const [, setVectorSearchInputValue] = useRecoilComponentStateV2(
|
||||
vectorSearchInputComponentState,
|
||||
VIEW_BAR_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
const { setVectorSearchInputValueFromExistingFilter } =
|
||||
useSetVectorSearchInputValueFromExistingFilter(VIEW_BAR_FILTER_DROPDOWN_ID);
|
||||
useSetVectorSearchInputValueFromExistingFilter();
|
||||
|
||||
const fieldSearchInputValue = useRecoilComponentValueV2(
|
||||
const objectFilterDropdownSearchInput = useRecoilComponentValueV2(
|
||||
objectFilterDropdownSearchInputComponentState,
|
||||
VIEW_BAR_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const { applyVectorSearchFilter } = useVectorSearchFilterActions();
|
||||
const { openVectorSearchFilter } = useOpenVectorSearchFilter(
|
||||
VIEW_BAR_FILTER_DROPDOWN_ID,
|
||||
);
|
||||
const { openVectorSearchFilter } = useOpenVectorSearchFilter();
|
||||
|
||||
const isSelected = useRecoilComponentFamilyValueV2(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
@ -49,9 +44,9 @@ export const ViewBarFilterDropdownVectorSearchButton = () => {
|
||||
const handleSearchClick = () => {
|
||||
openVectorSearchFilter();
|
||||
|
||||
if (fieldSearchInputValue.length > 0) {
|
||||
setVectorSearchInputValue(fieldSearchInputValue);
|
||||
applyVectorSearchFilter(fieldSearchInputValue);
|
||||
if (objectFilterDropdownSearchInput.length > 0) {
|
||||
setVectorSearchInputValue(objectFilterDropdownSearchInput);
|
||||
applyVectorSearchFilter(objectFilterDropdownSearchInput);
|
||||
} else {
|
||||
setVectorSearchInputValueFromExistingFilter();
|
||||
}
|
||||
@ -69,8 +64,8 @@ export const ViewBarFilterDropdownVectorSearchButton = () => {
|
||||
text={
|
||||
<>
|
||||
{t`Search`}
|
||||
{fieldSearchInputValue && (
|
||||
<StyledSearchText>{t`· ${fieldSearchInputValue}`}</StyledSearchText>
|
||||
{objectFilterDropdownSearchInput && (
|
||||
<StyledSearchText>{t`· ${objectFilterDropdownSearchInput}`}</StyledSearchText>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@ -1,47 +1,13 @@
|
||||
import { ObjectFilterDropdownVectorSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownVectorSearchInput';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useVectorSearchFilterActions } from '@/views/hooks/useVectorSearchFilterActions';
|
||||
import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
export const ViewBarFilterDropdownVectorSearchInput = ({
|
||||
filterDropdownId,
|
||||
}: {
|
||||
filterDropdownId: string;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
const [vectorSearchInputValue, setVectorSearchInputValue] =
|
||||
useRecoilComponentStateV2(
|
||||
vectorSearchInputComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
const { applyVectorSearchFilter } = useVectorSearchFilterActions();
|
||||
|
||||
const debouncedApplyVectorSearchFilter = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
applyVectorSearchFilter(value);
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value;
|
||||
setVectorSearchInputValue(inputValue);
|
||||
debouncedApplyVectorSearchFilter(inputValue);
|
||||
};
|
||||
import { ViewBarFilterDropdownFilterInputMenuHeader } from '@/views/components/ViewBarFilterDropdownFilterInputMenuHeader';
|
||||
|
||||
export const ViewBarFilterDropdownVectorSearchInput = () => {
|
||||
return (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<DropdownMenuSearchInput
|
||||
autoFocus
|
||||
type="text"
|
||||
value={vectorSearchInputValue}
|
||||
placeholder={t`Search`}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<ViewBarFilterDropdownFilterInputMenuHeader />
|
||||
<ObjectFilterDropdownVectorSearchInput />
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
|
||||
|
||||
export const useClearVectorSearchInput = () => {
|
||||
const setVectorSearchInputValue = useSetRecoilComponentStateV2(
|
||||
vectorSearchInputComponentState,
|
||||
);
|
||||
|
||||
const clearVectorSearchInput = () => {
|
||||
setVectorSearchInputValue('');
|
||||
};
|
||||
|
||||
return {
|
||||
clearVectorSearchInput,
|
||||
};
|
||||
};
|
||||
@ -24,15 +24,6 @@ export const useSetEditableFilterChipDropdownStates = () => {
|
||||
? filterableFieldMetadataItems.concat(vectorSearchField)
|
||||
: filterableFieldMetadataItems;
|
||||
|
||||
const fieldMetadataItem = filterableFieldsWithVector.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === recordFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(fieldMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVectorSearchFilter(recordFilter)) {
|
||||
set(
|
||||
vectorSearchInputComponentState.atomFamily({
|
||||
@ -42,13 +33,20 @@ export const useSetEditableFilterChipDropdownStates = () => {
|
||||
);
|
||||
}
|
||||
|
||||
set(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
}),
|
||||
fieldMetadataItem.id,
|
||||
const fieldMetadataItem = filterableFieldsWithVector.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === recordFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (isDefined(fieldMetadataItem)) {
|
||||
set(
|
||||
fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
}),
|
||||
fieldMetadataItem.id,
|
||||
);
|
||||
}
|
||||
|
||||
set(
|
||||
selectedOperandInDropdownComponentState.atomFamily({
|
||||
instanceId: recordFilter.id,
|
||||
|
||||
@ -3,17 +3,15 @@ import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInpu
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useVectorSearchFilterState } from './useVectorSearchFilterState';
|
||||
|
||||
export const useSetVectorSearchInputValueFromExistingFilter = (
|
||||
filterDropdownId: string,
|
||||
) => {
|
||||
export const useSetVectorSearchInputValueFromExistingFilter = () => {
|
||||
const [, setVectorSearchInputValue] = useRecoilComponentStateV2(
|
||||
vectorSearchInputComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
const { getExistingVectorSearchFilter } = useVectorSearchFilterState();
|
||||
|
||||
const setVectorSearchInputValueFromExistingFilter = () => {
|
||||
const existingVectorSearchFilter = getExistingVectorSearchFilter();
|
||||
|
||||
if (isDefined(existingVectorSearchFilter)) {
|
||||
setVectorSearchInputValue(existingVectorSearchFilter.value);
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
|
||||
export const vectorSearchInputComponentState = createComponentStateV2<string>({
|
||||
key: 'vectorSearchInputComponentState',
|
||||
defaultValue: '',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
componentInstanceContext: ObjectFilterDropdownComponentInstanceContext,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user