diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx index aa402d260..caf6db621 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx @@ -1,15 +1,18 @@ import styled from '@emotion/styled'; import { useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { useIcons } from 'twenty-ui'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; -import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { getOperandsForFilterType } from '../utils/getOperandsForFilterType'; +import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem'; +import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; +import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; +import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; +import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import { isDefined } from 'twenty-ui'; export const StyledInput = styled.input` background: transparent; @@ -39,20 +42,40 @@ export const StyledInput = styled.input` export const ObjectFilterDropdownFilterSelect = () => { const [searchText, setSearchText] = useState(''); - const { - setFilterDefinitionUsedInDropdown, - setSelectedOperandInDropdown, - setObjectFilterDropdownSearchInput, - availableFilterDefinitionsState, - } = useFilterDropdown(); + + const { availableFilterDefinitionsState } = useFilterDropdown(); const availableFilterDefinitions = useRecoilValue( availableFilterDefinitionsState, ); - const { getIcon } = useIcons(); + const sortedAvailableFilterDefinitions = [...availableFilterDefinitions] + .sort((a, b) => a.label.localeCompare(b.label)) + .filter((item) => + item.label.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()), + ); - const setHotkeyScope = useSetHotkeyScope(); + const selectableListItemIds = sortedAvailableFilterDefinitions.map( + (item) => item.fieldMetadataId, + ); + + const { selectFilter } = useSelectFilter(); + + const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID); + + const handleEnter = (itemId: string) => { + const selectedFilterDefinition = sortedAvailableFilterDefinitions.find( + (item) => item.fieldMetadataId === itemId, + ); + + if (!isDefined(selectedFilterDefinition)) { + return; + } + + resetSelectedItem(); + + selectFilter({ filterDefinition: selectedFilterDefinition }); + }; return ( <> @@ -64,39 +87,27 @@ export const ObjectFilterDropdownFilterSelect = () => { setSearchText(event.target.value) } /> - - {[...availableFilterDefinitions] - .sort((a, b) => a.label.localeCompare(b.label)) - .filter((item) => - item.label - .toLocaleLowerCase() - .includes(searchText.toLocaleLowerCase()), - ) - .map((availableFilterDefinition, index) => ( - { - setFilterDefinitionUsedInDropdown(availableFilterDefinition); - - if ( - availableFilterDefinition.type === 'RELATION' || - availableFilterDefinition.type === 'SELECT' - ) { - setHotkeyScope(RelationPickerHotkeyScope.RelationPicker); - } - - setSelectedOperandInDropdown( - getOperandsForFilterType(availableFilterDefinition.type)?.[0], - ); - - setObjectFilterDropdownSearchInput(''); - }} - LeftIcon={getIcon(availableFilterDefinition.iconName)} - text={availableFilterDefinition.label} - /> - ))} - + + + {sortedAvailableFilterDefinitions.map( + (availableFilterDefinition, index) => ( + + + + ), + )} + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx new file mode 100644 index 000000000..eb04750c9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem.tsx @@ -0,0 +1,43 @@ +import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdown/constants/ObjectFilterDropdownId'; +import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect'; +import { useRecoilValue } from 'recoil'; +import { useIcons } from 'twenty-ui'; + +export type ObjectFilterDropdownFilterSelectMenuItemProps = { + filterDefinition: FilterDefinition; +}; + +export const ObjectFilterDropdownFilterSelectMenuItem = ({ + filterDefinition, +}: ObjectFilterDropdownFilterSelectMenuItemProps) => { + const { selectFilter } = useSelectFilter(); + + const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList( + OBJECT_FILTER_DROPDOWN_ID, + ); + + const isSelectedItem = useRecoilValue( + isSelectedItemIdSelector(filterDefinition.fieldMetadataId), + ); + + const { getIcon } = useIcons(); + + const handleClick = () => { + resetSelectedItem(); + + selectFilter({ filterDefinition }); + }; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx index 9ced7ee64..213f828cf 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOptionSelect.tsx @@ -41,7 +41,7 @@ export const ObjectFilterDropdownOptionSelect = () => { selectableListScopeId: MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID, }); - const { handleResetSelectedPosition } = useSelectableList( + const { resetSelectedItem } = useSelectableList( MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID, ); @@ -90,10 +90,10 @@ export const ObjectFilterDropdownOptionSelect = () => { [Key.Escape], () => { closeDropdown(); - handleResetSelectedPosition(); + resetSelectedItem(); }, RelationPickerHotkeyScope.RelationPicker, - [closeDropdown, handleResetSelectedPosition], + [closeDropdown, resetSelectedItem], ); const handleMultipleOptionSelectChange = ( @@ -137,7 +137,7 @@ export const ObjectFilterDropdownOptionSelect = () => { value: newFilterValue, }); } - handleResetSelectedPosition(); + resetSelectedItem(); }; const optionsInDropdown = selectableOptions?.filter((option) => diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useSelectFilter.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useSelectFilter.ts new file mode 100644 index 000000000..3954d8d6d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useSelectFilter.ts @@ -0,0 +1,40 @@ +import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; +import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition'; +import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; +import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; + +type SelectFilterParams = { + filterDefinition: FilterDefinition; +}; + +export const useSelectFilter = () => { + const { + setFilterDefinitionUsedInDropdown, + setSelectedOperandInDropdown, + setObjectFilterDropdownSearchInput, + } = useFilterDropdown(); + + const setHotkeyScope = useSetHotkeyScope(); + + const selectFilter = ({ filterDefinition }: SelectFilterParams) => { + setFilterDefinitionUsedInDropdown(filterDefinition); + + if ( + filterDefinition.type === 'RELATION' || + filterDefinition.type === 'SELECT' + ) { + setHotkeyScope(RelationPickerHotkeyScope.RelationPicker); + } + + setSelectedOperandInDropdown( + getOperandsForFilterType(filterDefinition.type)?.[0], + ); + + setObjectFilterDropdownSearchInput(''); + }; + + return { + selectFilter, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx index c6134f911..f4c5745a4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiSelectFieldInput.tsx @@ -33,7 +33,7 @@ export const MultiSelectFieldInput = ({ const { selectedItemIdState } = useSelectableListStates({ selectableListScopeId: MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID, }); - const { handleResetSelectedPosition } = useSelectableList( + const { resetSelectedItem } = useSelectableList( MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID, ); const { persistField, fieldDefinition, fieldValues, hotkeyScope } = @@ -65,10 +65,10 @@ export const MultiSelectFieldInput = ({ Key.Escape, () => { onCancel?.(); - handleResetSelectedPosition(); + resetSelectedItem(); }, hotkeyScope, - [onCancel, handleResetSelectedPosition], + [onCancel, resetSelectedItem], ); useListenClickOutside({ @@ -83,7 +83,7 @@ export const MultiSelectFieldInput = ({ if (weAreNotInAnHTMLInput && isDefined(onCancel)) { onCancel(); } - handleResetSelectedPosition(); + resetSelectedItem(); }, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx index d9082b850..86bea16ad 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/SelectFieldInput.tsx @@ -27,7 +27,7 @@ export const SelectFieldInput = ({ const [filteredOptions, setFilteredOptions] = useState([]); - const { handleResetSelectedPosition } = useSelectableList( + const { resetSelectedItem } = useSelectableList( SINGLE_ENTITY_SELECT_BASE_LIST, ); const clearField = useClearField(); @@ -44,17 +44,17 @@ export const SelectFieldInput = ({ const handleSubmit = (option: SelectOption) => { onSubmit?.(() => persistField(option?.value)); - handleResetSelectedPosition(); + resetSelectedItem(); }; useScopedHotkeys( Key.Escape, () => { onCancel?.(); - handleResetSelectedPosition(); + resetSelectedItem(); }, hotkeyScope, - [onCancel, handleResetSelectedPosition], + [onCancel, resetSelectedItem], ); const optionIds = [ @@ -74,7 +74,7 @@ export const SelectFieldInput = ({ ); if (isDefined(option)) { onSubmit?.(() => persistField(option.value)); - handleResetSelectedPosition(); + resetSelectedItem(); } }} > diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx index 87c053220..f325edecc 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultiRecordSelect.tsx @@ -48,7 +48,7 @@ export const MultiRecordSelect = ({ const { objectRecordsIdsMultiSelectState, recordMultiSelectIsLoadingState } = useObjectRecordMultiSelectScopedStates(relationPickerScopedId); - const { handleResetSelectedPosition } = useSelectableList( + const { resetSelectedItem } = useSelectableList( MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID, ); const recordMultiSelectIsLoading = useRecoilValue( @@ -79,10 +79,10 @@ export const MultiRecordSelect = ({ () => { onSubmit?.(); goBackToPreviousHotkeyScope(); - handleResetSelectedPosition(); + resetSelectedItem(); }, relationPickerScopedId, - [onSubmit, goBackToPreviousHotkeyScope, handleResetSelectedPosition], + [onSubmit, goBackToPreviousHotkeyScope, resetSelectedItem], ); const debouncedOnCreate = useDebouncedCallback( @@ -123,7 +123,7 @@ export const MultiRecordSelect = ({ hotkeyScope={relationPickerScopedId} onEnter={(selectedId) => { onChange?.(selectedId); - handleResetSelectedPosition(); + resetSelectedItem(); }} > {objectRecordsIdsMultiSelect?.map((recordId) => { @@ -133,7 +133,7 @@ export const MultiRecordSelect = ({ objectRecordId={recordId} onChange={(recordId) => { onChange?.(recordId); - handleResetSelectedPosition(); + resetSelectedItem(); }} /> ); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SelectableMenuItemSelect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SelectableMenuItemSelect.tsx index 204467062..eef1523d1 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/SelectableMenuItemSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SelectableMenuItemSelect.tsx @@ -27,7 +27,6 @@ export const SelectableMenuItemSelect = ({ ); const isSelectedItemId = useRecoilValue(isSelectedItemIdSelector(entity.id)); - return ( { - handleResetSelectedPosition(); + resetSelectedItem(); onCancel?.(); }, hotkeyScope, - [onCancel, handleResetSelectedPosition], + [onCancel, resetSelectedItem], ); const selectableItemIds = entitiesInDropdown.map((entity) => entity.id); @@ -134,7 +135,7 @@ export const SingleEntitySelectMenuItems = ({ ); onEntitySelected(entitiesInDropdown[entityIndex]); } - handleResetSelectedPosition(); + resetSelectedItem(); }} > diff --git a/packages/twenty-front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx b/packages/twenty-front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx index e8a286ad4..3914a4163 100644 --- a/packages/twenty-front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx @@ -40,7 +40,7 @@ export const MultipleRecordSelectDropdown = ({ selectableListScopeId: selectableListId, }); - const { handleResetSelectedPosition } = useSelectableList(selectableListId); + const { resetSelectedItem } = useSelectableList(selectableListId); const selectedItemId = useRecoilValue(selectedItemIdState); @@ -75,10 +75,10 @@ export const MultipleRecordSelectDropdown = ({ [Key.Escape], () => { closeDropdown(); - handleResetSelectedPosition(); + resetSelectedItem(); }, hotkeyScope, - [closeDropdown, handleResetSelectedPosition], + [closeDropdown, resetSelectedItem], ); const showNoResult = @@ -105,7 +105,7 @@ export const MultipleRecordSelectDropdown = ({ recordsInDropdown[record], !recordIsSelectedInDropwdown, ); - handleResetSelectedPosition(); + resetSelectedItem(); }} > @@ -116,7 +116,7 @@ export const MultipleRecordSelectDropdown = ({ selected={record.isSelected} isKeySelected={record.id === selectedItemId} onSelectChange={(newCheckedValue) => { - handleResetSelectedPosition(); + resetSelectedItem(); handleRecordSelectChange(record, newCheckedValue); }} avatar={ diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx index 7be321009..98da24f05 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx @@ -10,9 +10,7 @@ import { MouseEvent, useRef } from 'react'; import { Keys } from 'react-hotkeys-hook'; import { Key } from 'ts-key-enum'; -import { SINGLE_ENTITY_SELECT_BASE_LIST } from '@/object-record/relation-picker/constants/SingleEntitySelectBaseList'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; -import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; @@ -69,9 +67,6 @@ export const Dropdown = ({ const { isDropdownOpen, toggleDropdown, closeDropdown, dropdownWidth } = useDropdown(dropdownId); - const { handleResetSelectedPosition } = useSelectableList( - SINGLE_ENTITY_SELECT_BASE_LIST, - ); const offsetMiddlewares = []; if (isDefined(dropdownOffset.x)) { @@ -108,7 +103,6 @@ export const Dropdown = ({ if (isDropdownOpen) { closeDropdown(); - handleResetSelectedPosition(); } }, }); @@ -122,10 +116,9 @@ export const Dropdown = ({ [Key.Escape], () => { closeDropdown(); - handleResetSelectedPosition(); }, dropdownHotkeyScope.scope, - [closeDropdown, handleResetSelectedPosition], + [closeDropdown], ); return ( diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableItem.tsx b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableItem.tsx index c4678f4d2..de7114062 100644 --- a/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableItem.tsx +++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/components/SelectableItem.tsx @@ -2,6 +2,12 @@ import { ReactNode, useEffect, useRef } from 'react'; import { useRecoilValue } from 'recoil'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; +import styled from '@emotion/styled'; + +const StyledContainer = styled.div` + height: 100%; + width: 100%; +`; export type SelectableItemProps = { itemId: string; @@ -27,8 +33,8 @@ export const SelectableItem = ({ }, [isSelectedItemId]); return ( -
+ {children} -
+ ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts index 89518de5d..3dadd58d1 100644 --- a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts +++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/useSelectableList.ts @@ -1,8 +1,4 @@ -import { - useRecoilCallback, - useResetRecoilState, - useSetRecoilState, -} from 'recoil'; +import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; @@ -24,16 +20,11 @@ export const useSelectableList = (selectableListId?: string) => { selectableListOnEnterState, ); - const resetSelectedItemIdState = useResetRecoilState(selectedItemIdState); - - const resetSelectedItem = () => { - resetSelectedItemIdState(); - }; - - const handleResetSelectedPosition = useRecoilCallback( + const resetSelectedItem = useRecoilCallback( ({ snapshot, set }) => () => { const selectedItemId = getSnapshotValue(snapshot, selectedItemIdState); + if (isDefined(selectedItemId)) { set(selectedItemIdState, null); set(isSelectedItemIdSelector(selectedItemId), false); @@ -44,11 +35,9 @@ export const useSelectableList = (selectableListId?: string) => { return { selectableListId: scopeId, - setSelectableItemIds, isSelectedItemIdSelector, setSelectableListOnEnter, resetSelectedItem, - handleResetSelectedPosition, }; }; diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx index 890322882..85d639138 100644 --- a/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx @@ -8,12 +8,12 @@ export type MenuItemBaseProps = { accent?: MenuItemAccent; isKeySelected?: boolean; isHoverBackgroundDisabled?: boolean; + hovered?: boolean; }; export const StyledMenuItemBase = styled.div` --horizontal-padding: ${({ theme }) => theme.spacing(1)}; --vertical-padding: ${({ theme }) => theme.spacing(2)}; - align-items: center; border-radius: ${({ theme }) => theme.border.radius.sm};