CreateComponentFamilyState -> createComponentFamilyStateV2 (#11546)

Refacto of createComponentFamilyState

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Guillim
2025-04-14 10:31:30 +02:00
committed by GitHub
parent 27f542e132
commit 0de8140b3a
36 changed files with 311 additions and 299 deletions

View File

@ -1,5 +1,7 @@
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState'; import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -9,10 +11,12 @@ export const CommandMenuDefaultSelectionEffect = ({
}: { }: {
selectableItemIds: string[]; selectableItemIds: string[];
}) => { }) => {
const { setSelectedItemId, selectedItemIdState } = const { setSelectedItemId } = useSelectableList('command-menu-list');
useSelectableList('command-menu-list');
const selectedItemId = useRecoilValue(selectedItemIdState); const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
'command-menu-list',
);
const hasUserSelectedCommand = useRecoilValue(hasUserSelectedCommandState); const hasUserSelectedCommand = useRecoilValue(hasUserSelectedCommandState);

View File

@ -1,8 +1,8 @@
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick'; import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { IconArrowUpRight, IconComponent } from 'twenty-ui/display'; import { IconArrowUpRight, IconComponent } from 'twenty-ui/display';
import { MenuItemCommand } from 'twenty-ui/navigation'; import { MenuItemCommand } from 'twenty-ui/navigation';
@ -35,8 +35,10 @@ export const CommandMenuItem = ({
Icon = IconArrowUpRight; Icon = IconArrowUpRight;
} }
const { isSelectedItemIdSelector } = useSelectableList(); const isSelectedItemId = useRecoilComponentFamilyValueV2(
const isSelectedItemId = useRecoilValue(isSelectedItemIdSelector(id)); isSelectedItemIdComponentFamilySelector,
id,
);
return ( return (
<MenuItemCommand <MenuItemCommand

View File

@ -79,7 +79,7 @@ export const CommandMenuList = ({
<ScrollWrapper componentInstanceId={`scroll-wrapper-command-menu`}> <ScrollWrapper componentInstanceId={`scroll-wrapper-command-menu`}>
<StyledInnerList> <StyledInnerList>
<SelectableList <SelectableList
selectableListId="command-menu-list" selectableListInstanceId="command-menu-list"
hotkeyScope={AppHotkeyScope.CommandMenuOpen} hotkeyScope={AppHotkeyScope.CommandMenuOpen}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
onEnter={(itemId) => { onEnter={(itemId) => {

View File

@ -144,7 +144,7 @@ export const AdvancedFilterFieldSelectMenu = ({
<SelectableList <SelectableList
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton} hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
selectableItemIdArray={selectableFieldMetadataItemIds} selectableItemIdArray={selectableFieldMetadataItemIds}
selectableListId={OBJECT_FILTER_DROPDOWN_ID} selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
onEnter={handleEnter} onEnter={handleEnter}
> >
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>

View File

@ -90,7 +90,7 @@ export const ObjectFilterDropdownBooleanSelect = () => {
return ( return (
<SelectableList <SelectableList
selectableListId="boolean-select" selectableListInstanceId="boolean-select"
selectableItemIdArray={options.map((option) => option.toString())} selectableItemIdArray={options.map((option) => option.toString())}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
onEnter={(itemId) => { onEnter={(itemId) => {

View File

@ -150,7 +150,7 @@ export const ObjectFilterDropdownFilterSelect = ({
<SelectableList <SelectableList
hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton} hotkeyScope={FiltersHotkeyScope.ObjectFilterDropdownButton}
selectableItemIdArray={selectableFieldMetadataItemIds} selectableItemIdArray={selectableFieldMetadataItemIds}
selectableListId={OBJECT_FILTER_DROPDOWN_ID} selectableListInstanceId={OBJECT_FILTER_DROPDOWN_ID}
onEnter={handleEnter} onEnter={handleEnter}
> >
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>

View File

@ -15,14 +15,15 @@ import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-re
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { useIcons } from 'twenty-ui/display'; import { useIcons } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
export type ObjectFilterDropdownFilterSelectMenuItemProps = { export type ObjectFilterDropdownFilterSelectMenuItemProps = {
fieldMetadataItemToSelect: FieldMetadataItem; fieldMetadataItemToSelect: FieldMetadataItem;
@ -48,12 +49,11 @@ export const ObjectFilterDropdownFilterSelectMenuItem = ({
objectFilterDropdownFilterIsSelectedComponentState, objectFilterDropdownFilterIsSelectedComponentState,
); );
const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList( const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
OBJECT_FILTER_DROPDOWN_ID,
);
const isSelectedItem = useRecoilValue( const isSelectedItem = useRecoilComponentFamilyValueV2(
isSelectedItemIdSelector(fieldMetadataItemToSelect.id), isSelectedItemIdComponentFamilySelector,
fieldMetadataItemToSelect.id,
); );
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(

View File

@ -3,9 +3,10 @@ import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdow
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField'; import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useRecoilValue } from 'recoil'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { MenuItemSelect } from 'twenty-ui/navigation'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useIcons } from 'twenty-ui/display'; import { useIcons } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
export type ObjectFilterDropdownFilterSelectMenuItemV2Props = { export type ObjectFilterDropdownFilterSelectMenuItemV2Props = {
fieldMetadataItemToSelect: FieldMetadataItem; fieldMetadataItemToSelect: FieldMetadataItem;
@ -16,12 +17,11 @@ export const ObjectFilterDropdownFilterSelectMenuItemV2 = ({
fieldMetadataItemToSelect, fieldMetadataItemToSelect,
onClick, onClick,
}: ObjectFilterDropdownFilterSelectMenuItemV2Props) => { }: ObjectFilterDropdownFilterSelectMenuItemV2Props) => {
const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList( const { resetSelectedItem } = useSelectableList(OBJECT_FILTER_DROPDOWN_ID);
OBJECT_FILTER_DROPDOWN_ID,
);
const isSelectedItem = useRecoilValue( const isSelectedItem = useRecoilComponentFamilyValueV2(
isSelectedItemIdSelector(fieldMetadataItemToSelect.id), isSelectedItemIdComponentFamilySelector,
fieldMetadataItemToSelect.id,
); );
const { getIcon } = useIcons(); const { getIcon } = useIcons();

View File

@ -1,5 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -8,7 +7,7 @@ import { useOptionsForSelect } from '@/object-record/object-filter-dropdown/hook
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
@ -20,6 +19,7 @@ import { selectedFilterComponentState } from '@/object-record/object-filter-drop
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter'; import { useApplyRecordFilter } from '@/object-record/record-filter/hooks/useApplyRecordFilter';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -62,13 +62,12 @@ export const ObjectFilterDropdownOptionSelect = () => {
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown();
const { selectedItemIdState } = useSelectableListStates({
selectableListScopeId: componentInstanceId,
});
const { resetSelectedItem } = useSelectableList(componentInstanceId); const { resetSelectedItem } = useSelectableList(componentInstanceId);
const selectedItemId = useRecoilValue(selectedItemIdState); const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
componentInstanceId,
);
const fieldMetaDataId = fieldMetadataItemUsedInDropdown?.id ?? ''; const fieldMetaDataId = fieldMetadataItemUsedInDropdown?.id ?? '';
@ -164,7 +163,7 @@ export const ObjectFilterDropdownOptionSelect = () => {
return ( return (
<SelectableList <SelectableList
selectableListId={componentInstanceId} selectableListInstanceId={componentInstanceId}
selectableItemIdArray={objectRecordsIds} selectableItemIdArray={objectRecordsIds}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
onEnter={(itemId) => { onEnter={(itemId) => {

View File

@ -1,5 +1,4 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getAvatarType } from '@/object-metadata/utils/getAvatarType'; import { getAvatarType } from '@/object-metadata/utils/getAvatarType';
@ -8,12 +7,12 @@ import { multipleRecordPickerIsSelectedComponentFamilySelector } from '@/object-
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId'; import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { SearchRecord } from '~/generated-metadata/graphql';
import { Avatar } from 'twenty-ui/display'; import { Avatar } from 'twenty-ui/display';
import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation'; import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
import { SearchRecord } from '~/generated-metadata/graphql';
export const StyledSelectableItem = styled(SelectableItem)` export const StyledSelectableItem = styled(SelectableItem)`
height: 100%; height: 100%;
@ -38,14 +37,12 @@ export const MultipleRecordPickerMenuItemContent = ({
const selectableListComponentInstanceId = const selectableListComponentInstanceId =
getMultipleRecordPickerSelectableListId(componentInstanceId); getMultipleRecordPickerSelectableListId(componentInstanceId);
const { isSelectedItemIdSelector } = useSelectableList( const isSelectedByKeyboard = useRecoilComponentFamilyValueV2(
isSelectedItemIdComponentFamilySelector,
searchRecord.recordId,
selectableListComponentInstanceId, selectableListComponentInstanceId,
); );
const isSelectedByKeyboard = useRecoilValue(
isSelectedItemIdSelector(searchRecord.recordId),
);
const isRecordSelectedWithObjectItem = useRecoilComponentFamilyValueV2( const isRecordSelectedWithObjectItem = useRecoilComponentFamilyValueV2(
multipleRecordPickerIsSelectedComponentFamilySelector, multipleRecordPickerIsSelectedComponentFamilySelector,
searchRecord.recordId, searchRecord.recordId,

View File

@ -114,7 +114,7 @@ export const MultipleRecordPickerMenuItems = ({
return ( return (
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
<SelectableList <SelectableList
selectableListId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={pickableRecordIds} selectableItemIdArray={pickableRecordIds}
hotkeyScope={MultipleRecordPickerHotkeyScope.MultipleRecordPicker} hotkeyScope={MultipleRecordPickerHotkeyScope.MultipleRecordPicker}
onEnter={handleEnter} onEnter={handleEnter}

View File

@ -12,7 +12,6 @@ import { isDefined } from 'twenty-shared/utils';
export const SINGLE_RECORD_PICKER_LISTENER_ID = 'single-record-select'; export const SINGLE_RECORD_PICKER_LISTENER_ID = 'single-record-select';
export type SingleRecordPickerProps = { export type SingleRecordPickerProps = {
width?: number;
componentInstanceId: string; componentInstanceId: string;
} & SingleRecordPickerMenuItemsWithSearchProps; } & SingleRecordPickerMenuItemsWithSearchProps;
@ -24,7 +23,6 @@ export const SingleRecordPicker = ({
onCreate, onCreate,
onRecordSelected, onRecordSelected,
objectNameSingular, objectNameSingular,
width = 200,
componentInstanceId, componentInstanceId,
layoutDirection, layoutDirection,
}: SingleRecordPickerProps) => { }: SingleRecordPickerProps) => {
@ -51,7 +49,7 @@ export const SingleRecordPicker = ({
<SingleRecordPickerComponentInstanceContext.Provider <SingleRecordPickerComponentInstanceContext.Provider
value={{ instanceId: componentInstanceId }} value={{ instanceId: componentInstanceId }}
> >
<DropdownMenu ref={containerRef} width={width} data-select-disable> <DropdownMenu ref={containerRef} data-select-disable>
<SingleRecordPickerMenuItemsWithSearch <SingleRecordPickerMenuItemsWithSearch
{...{ {...{
EmptyIcon, EmptyIcon,

View File

@ -1,12 +1,12 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext'; import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId'; import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { Avatar } from 'twenty-ui/display'; import { Avatar } from 'twenty-ui/display';
import { MenuItemSelectAvatar } from 'twenty-ui/navigation'; import { MenuItemSelectAvatar } from 'twenty-ui/navigation';
@ -33,12 +33,12 @@ export const SingleRecordPickerMenuItem = ({
const selectableListComponentInstanceId = const selectableListComponentInstanceId =
getSingleRecordPickerSelectableListId(recordPickerComponentInstanceId); getSingleRecordPickerSelectableListId(recordPickerComponentInstanceId);
const { isSelectedItemIdSelector } = useSelectableList( const isSelectedItemId = useRecoilComponentFamilyValueV2(
isSelectedItemIdComponentFamilySelector,
record.id,
selectableListComponentInstanceId, selectableListComponentInstanceId,
); );
const isSelectedItemId = useRecoilValue(isSelectedItemIdSelector(record.id));
return ( return (
<StyledSelectableItem itemId={record.id} key={record.id}> <StyledSelectableItem itemId={record.id} key={record.id}>
<MenuItemSelectAvatar <MenuItemSelectAvatar

View File

@ -1,6 +1,5 @@
import { isNonEmptyString, isUndefined } from '@sniptt/guards'; import { isNonEmptyString, isUndefined } from '@sniptt/guards';
import { useRef } from 'react'; import { useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
@ -15,7 +14,9 @@ import { singleRecordPickerSelectedIdComponentState } from '@/object-record/reco
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId'; import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -76,12 +77,14 @@ export const SingleRecordPickerMenuItems = ({
const selectableListComponentInstanceId = const selectableListComponentInstanceId =
getSingleRecordPickerSelectableListId(recordPickerComponentInstanceId); getSingleRecordPickerSelectableListId(recordPickerComponentInstanceId);
const { isSelectedItemIdSelector, resetSelectedItem } = useSelectableList( const { resetSelectedItem } = useSelectableList(
selectableListComponentInstanceId, selectableListComponentInstanceId,
); );
const isSelectedSelectNoneButton = useRecoilValue( const isSelectedSelectNoneButton = useRecoilComponentFamilyValueV2(
isSelectedItemIdSelector('select-none'), isSelectedItemIdComponentFamilySelector,
selectableListComponentInstanceId,
'select-none',
); );
useScopedHotkeys( useScopedHotkeys(
@ -102,7 +105,7 @@ export const SingleRecordPickerMenuItems = ({
return ( return (
<StyledContainer ref={containerRef}> <StyledContainer ref={containerRef}>
<SelectableList <SelectableList
selectableListId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onEnter={(itemId) => { onEnter={(itemId) => {

View File

@ -1,5 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip'; import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip';
@ -8,9 +7,10 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation'; import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
export const MultipleSelectDropdown = ({ export const MultipleSelectDropdown = ({
@ -35,13 +35,13 @@ export const MultipleSelectDropdown = ({
loadingItems: boolean; loadingItems: boolean;
}) => { }) => {
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown();
const { selectedItemIdState } = useSelectableListStates({
selectableListScopeId: selectableListId,
});
const { resetSelectedItem } = useSelectableList(selectableListId); const { resetSelectedItem } = useSelectableList(selectableListId);
const selectedItemId = useRecoilValue(selectedItemIdState); const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
selectableListId,
);
const handleItemSelectChange = ( const handleItemSelectChange = (
itemToSelect: SelectableItem, itemToSelect: SelectableItem,
@ -90,7 +90,7 @@ export const MultipleSelectDropdown = ({
return ( return (
<SelectableList <SelectableList
selectableListId={selectableListId} selectableListInstanceId={selectableListId}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onEnter={(itemId) => { onEnter={(itemId) => {

View File

@ -1,5 +1,4 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
@ -8,14 +7,16 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
import { MenuItemMultiSelectTag } from 'twenty-ui/navigation';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
import { MenuItemMultiSelectTag } from 'twenty-ui/navigation';
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
type MultiSelectInputProps = { type MultiSelectInputProps = {
selectableListComponentInstanceId: string; selectableListComponentInstanceId: string;
@ -34,14 +35,14 @@ export const MultiSelectInput = ({
onCancel, onCancel,
onOptionSelected, onOptionSelected,
}: MultiSelectInputProps) => { }: MultiSelectInputProps) => {
const { selectedItemIdState } = useSelectableListStates({
selectableListScopeId: selectableListComponentInstanceId,
});
const { resetSelectedItem } = useSelectableList( const { resetSelectedItem } = useSelectableList(
selectableListComponentInstanceId, selectableListComponentInstanceId,
); );
const selectedItemId = useRecoilValue(selectedItemIdState); const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
selectableListComponentInstanceId,
);
const [searchFilter, setSearchFilter] = useState(''); const [searchFilter, setSearchFilter] = useState('');
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@ -97,7 +98,7 @@ export const MultiSelectInput = ({
return ( return (
<SelectableList <SelectableList
selectableListId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={optionIds} selectableItemIdArray={optionIds}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onEnter={(itemId) => { onEnter={(itemId) => {

View File

@ -31,7 +31,7 @@ export const SelectInput = ({
}: SelectInputProps) => { }: SelectInputProps) => {
return ( return (
<SelectableList <SelectableList
selectableListId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onEnter={onEnter} onEnter={onEnter}

View File

@ -1,6 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -9,10 +8,11 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { arrayToChunks } from '~/utils/array/arrayToChunks'; import { arrayToChunks } from '~/utils/array/arrayToChunks';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { IconApps, IconComponent, useIcons } from 'twenty-ui/display'; import { IconApps, IconComponent, useIcons } from 'twenty-ui/display';
import { import {
@ -64,9 +64,10 @@ const IconPickerIcon = ({
selectedIconKey, selectedIconKey,
Icon, Icon,
}: IconPickerIconProps) => { }: IconPickerIconProps) => {
const { isSelectedItemIdSelector } = useSelectableList(); const isSelectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
const isSelectedItemId = useRecoilValue(isSelectedItemIdSelector(iconKey)); iconKey,
);
return ( return (
<StyledLightIconButton <StyledLightIconButton
@ -74,7 +75,7 @@ const IconPickerIcon = ({
aria-label={convertIconKeyToLabel(iconKey)} aria-label={convertIconKeyToLabel(iconKey)}
size="medium" size="medium"
title={iconKey} title={iconKey}
isSelected={iconKey === selectedIconKey || isSelectedItemId} isSelected={iconKey === selectedIconKey || !!isSelectedItemId}
Icon={Icon} Icon={Icon}
onClick={onClick} onClick={onClick}
/> />
@ -175,7 +176,7 @@ export const IconPicker = ({
dropdownWidth={176} dropdownWidth={176}
dropdownComponents={ dropdownComponents={
<SelectableList <SelectableList
selectableListId="icon-list" selectableListInstanceId="icon-list"
selectableItemIdMatrix={iconKeys2d} selectableItemIdMatrix={iconKeys2d}
hotkeyScope={IconPickerHotkeyScope.IconPicker} hotkeyScope={IconPickerHotkeyScope.IconPicker}
onEnter={(iconKey) => { onEnter={(iconKey) => {

View File

@ -1,7 +1,7 @@
import { ReactNode, useEffect, useRef } from 'react'; import { ReactNode, useEffect, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -20,9 +20,10 @@ export const SelectableItem = ({
children, children,
className, className,
}: SelectableItemProps) => { }: SelectableItemProps) => {
const { isSelectedItemIdSelector } = useSelectableList(); const isSelectedItemId = useRecoilComponentFamilyValueV2(
isSelectedItemIdComponentFamilySelector,
const isSelectedItemId = useRecoilValue(isSelectedItemIdSelector(itemId)); itemId,
);
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);

View File

@ -1,34 +1,43 @@
import { ReactNode, useEffect } from 'react'; import { ReactNode, useEffect } from 'react';
import { useSelectableListHotKeys } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys'; import { useSelectableListHotKeys } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { SelectableListScope } from '@/ui/layout/selectable-list/scopes/SelectableListScope'; import { selectableItemIdsComponentState } from '@/ui/layout/selectable-list/states/selectableItemIdsComponentState';
import { arrayToChunks } from '~/utils/array/arrayToChunks'; import { selectableListOnEnterComponentState } from '@/ui/layout/selectable-list/states/selectableListOnEnterComponentState';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { arrayToChunks } from '~/utils/array/arrayToChunks';
type SelectableListProps = { type SelectableListProps = {
children: ReactNode; children: ReactNode;
selectableListId: string;
selectableItemIdArray?: string[]; selectableItemIdArray?: string[];
selectableItemIdMatrix?: string[][]; selectableItemIdMatrix?: string[][];
onSelect?: (selected: string) => void; onSelect?: (selected: string) => void;
hotkeyScope: string; hotkeyScope: string;
onEnter?: (itemId: string) => void; onEnter?: (itemId: string) => void;
selectableListInstanceId: string;
}; };
export const SelectableList = ({ export const SelectableList = ({
children, children,
selectableListId,
hotkeyScope, hotkeyScope,
selectableItemIdArray, selectableItemIdArray,
selectableItemIdMatrix, selectableItemIdMatrix,
selectableListInstanceId,
onEnter, onEnter,
onSelect, onSelect,
}: SelectableListProps) => { }: SelectableListProps) => {
useSelectableListHotKeys(selectableListId, hotkeyScope, onSelect); useSelectableListHotKeys(selectableListInstanceId, hotkeyScope, onSelect);
const { setSelectableItemIds, setSelectableListOnEnter, setSelectedItemId } = const setSelectableListOnEnter = useSetRecoilComponentStateV2(
useSelectableList(selectableListId); selectableListOnEnterComponentState,
selectableListInstanceId,
);
const setSelectableItemIds = useSetRecoilComponentStateV2(
selectableItemIdsComponentState,
selectableListInstanceId,
);
useEffect(() => { useEffect(() => {
setSelectableListOnEnter(() => onEnter); setSelectableListOnEnter(() => onEnter);
@ -48,16 +57,15 @@ export const SelectableList = ({
if (isDefined(selectableItemIdArray)) { if (isDefined(selectableItemIdArray)) {
setSelectableItemIds(arrayToChunks(selectableItemIdArray, 1)); setSelectableItemIds(arrayToChunks(selectableItemIdArray, 1));
} }
}, [ }, [selectableItemIdArray, selectableItemIdMatrix, setSelectableItemIds]);
selectableItemIdArray,
selectableItemIdMatrix,
setSelectableItemIds,
setSelectedItemId,
]);
return ( return (
<SelectableListScope selectableListScopeId={selectableListId}> <SelectableListComponentInstanceContext.Provider
value={{
instanceId: selectableListInstanceId,
}}
>
{children} {children}
</SelectableListScope> </SelectableListComponentInstanceContext.Provider>
); );
}; };

View File

@ -1,9 +1,12 @@
import { act } from 'react-dom/test-utils';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil'; import { act } from 'react-dom/test-utils';
import { RecoilRoot } from 'recoil';
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectableItemIdsComponentState } from '@/ui/layout/selectable-list/states/selectableItemIdsComponentState';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
const selectableListScopeId = 'testId'; const selectableListScopeId = 'testId';
const testArr = [['1'], ['2'], ['3']]; const testArr = [['1'], ['2'], ['3']];
@ -13,15 +16,15 @@ describe('useSelectableList', () => {
it('Should setSelectableItemIds', async () => { it('Should setSelectableItemIds', async () => {
const { result } = renderHook( const { result } = renderHook(
() => { () => {
const { setSelectableItemIds } = useSelectableList( const setSelectableItemIds = useSetRecoilComponentStateV2(
selectableItemIdsComponentState,
selectableListScopeId, selectableListScopeId,
); );
const { selectableItemIdsState } = useSelectableListStates({ const selectableItemIds = useRecoilComponentValueV2(
selectableItemIdsComponentState,
selectableListScopeId, selectableListScopeId,
}); );
const selectableItemIds = useRecoilValue(selectableItemIdsState);
return { return {
setSelectableItemIds, setSelectableItemIds,
@ -47,13 +50,14 @@ describe('useSelectableList', () => {
() => { () => {
const { resetSelectedItem } = useSelectableList(selectableListScopeId); const { resetSelectedItem } = useSelectableList(selectableListScopeId);
const { selectedItemIdState } = useSelectableListStates({ const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
selectableListScopeId, selectableListScopeId,
}); );
const setSelectedItemId = useSetRecoilComponentStateV2(
const [selectedItemId, setSelectedItemId] = selectedItemIdComponentState,
useRecoilState(selectedItemIdState); selectableListScopeId,
);
return { return {
resetSelectedItem, resetSelectedItem,
selectedItemId, selectedItemId,

View File

@ -2,14 +2,17 @@ import { isNonEmptyString } from '@sniptt/guards';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates'; import { selectableItemIdsComponentState } from '@/ui/layout/selectable-list/states/selectableItemIdsComponentState';
import { selectableListOnEnterComponentState } from '@/ui/layout/selectable-list/states/selectableListOnEnterComponentState';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
type Direction = 'up' | 'down' | 'left' | 'right'; type Direction = 'up' | 'down' | 'left' | 'right';
export const useSelectableListHotKeys = ( export const useSelectableListHotKeys = (
scopeId: string, instanceId: string,
hotkeyScope: string, hotkeyScope: string,
onSelect?: (itemId: string) => void, onSelect?: (itemId: string) => void,
) => { ) => {
@ -29,22 +32,20 @@ export const useSelectableListHotKeys = (
} }
}; };
const {
selectedItemIdState,
selectableItemIdsState,
isSelectedItemIdSelector,
selectableListOnEnterState,
} = useSelectableListStates({
selectableListScopeId: scopeId,
});
const handleSelect = useRecoilCallback( const handleSelect = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
(direction: Direction) => { (direction: Direction) => {
const selectedItemId = getSnapshotValue(snapshot, selectedItemIdState); const selectedItemId = getSnapshotValue(
snapshot,
selectedItemIdComponentState.atomFamily({
instanceId: instanceId,
}),
);
const selectableItemIds = getSnapshotValue( const selectableItemIds = getSnapshotValue(
snapshot, snapshot,
selectableItemIdsState, selectableItemIdsComponentState.atomFamily({
instanceId: instanceId,
}),
); );
const currentPosition = findPosition(selectableItemIds, selectedItemId); const currentPosition = findPosition(selectableItemIds, selectedItemId);
@ -104,22 +105,34 @@ export const useSelectableListHotKeys = (
if (selectedItemId !== nextId) { if (selectedItemId !== nextId) {
if (isNonEmptyString(nextId)) { if (isNonEmptyString(nextId)) {
set(isSelectedItemIdSelector(nextId), true); set(
set(selectedItemIdState, nextId); isSelectedItemIdComponentFamilySelector.selectorFamily({
instanceId: instanceId,
familyKey: nextId,
}),
true,
);
set(
selectedItemIdComponentState.atomFamily({
instanceId: instanceId,
}),
nextId,
);
onSelect?.(nextId); onSelect?.(nextId);
} }
if (isNonEmptyString(selectedItemId)) { if (isNonEmptyString(selectedItemId)) {
set(isSelectedItemIdSelector(selectedItemId), false); set(
isSelectedItemIdComponentFamilySelector.selectorFamily({
instanceId: instanceId,
familyKey: selectedItemId,
}),
false,
);
} }
} }
}, },
[ [instanceId, onSelect],
isSelectedItemIdSelector,
onSelect,
selectableItemIdsState,
selectedItemIdState,
],
); );
useScopedHotkeys(Key.ArrowUp, () => handleSelect('up'), hotkeyScope, []); useScopedHotkeys(Key.ArrowUp, () => handleSelect('up'), hotkeyScope, []);
@ -142,18 +155,22 @@ export const useSelectableListHotKeys = (
() => { () => {
const selectedItemId = getSnapshotValue( const selectedItemId = getSnapshotValue(
snapshot, snapshot,
selectedItemIdState, selectedItemIdComponentState.atomFamily({
instanceId: instanceId,
}),
); );
const onEnter = getSnapshotValue( const onEnter = getSnapshotValue(
snapshot, snapshot,
selectableListOnEnterState, selectableListOnEnterComponentState.atomFamily({
instanceId: instanceId,
}),
); );
if (isNonEmptyString(selectedItemId)) { if (isNonEmptyString(selectedItemId)) {
onEnter?.(selectedItemId); onEnter?.(selectedItemId);
} }
}, },
[selectableListOnEnterState, selectedItemIdState], [instanceId],
), ),
hotkeyScope, hotkeyScope,
[], [],

View File

@ -1,41 +0,0 @@
import { SelectableListScopeInternalContext } from '@/ui/layout/selectable-list/scopes/scope-internal-context/SelectableListScopeInternalContext';
import { selectableItemIdsComponentState } from '@/ui/layout/selectable-list/states/selectableItemIdsComponentState';
import { selectableListOnEnterComponentState } from '@/ui/layout/selectable-list/states/selectableListOnEnterComponentState';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { isSelectedItemIdFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdFamilySelector';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
type useSelectableListStatesProps = {
selectableListScopeId?: string;
};
export const useSelectableListStates = ({
selectableListScopeId,
}: useSelectableListStatesProps) => {
const scopeId = useAvailableScopeIdOrThrow(
SelectableListScopeInternalContext,
selectableListScopeId,
);
return {
scopeId,
isSelectedItemIdSelector: extractComponentFamilyState(
isSelectedItemIdFamilySelector,
scopeId,
),
selectableItemIdsState: extractComponentState(
selectableItemIdsComponentState,
scopeId,
),
selectableListOnEnterState: extractComponentState(
selectableListOnEnterComponentState,
scopeId,
),
selectedItemIdState: extractComponentState(
selectedItemIdComponentState,
scopeId,
),
};
};

View File

@ -1,4 +1,4 @@
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
@ -7,15 +7,15 @@ import { Key } from 'ts-key-enum';
export const useListenToEnterHotkeyOnListItem = ({ export const useListenToEnterHotkeyOnListItem = ({
hotkeyScope, hotkeyScope,
itemId, itemId,
onEnter, onEnter,
}: { }: {
hotkeyScope: string; hotkeyScope: string;
itemId: string; itemId: string;
onEnter: () => void; onEnter: () => void;
}) => { }) => {
const { selectedItemIdState } = useSelectableList();
useScopedHotkeys( useScopedHotkeys(
Key.Enter, Key.Enter,
useRecoilCallback( useRecoilCallback(
@ -23,17 +23,19 @@ export const useListenToEnterHotkeyOnListItem = ({
() => { () => {
const selectedItemId = getSnapshotValue( const selectedItemId = getSnapshotValue(
snapshot, snapshot,
selectedItemIdState, selectedItemIdComponentState.atomFamily({
instanceId: itemId,
}),
); );
if (isNonEmptyString(selectedItemId) && selectedItemId === itemId) { if (isNonEmptyString(selectedItemId) && selectedItemId === itemId) {
onEnter?.(); onEnter?.();
} }
}, },
[itemId, onEnter, selectedItemIdState], [itemId, onEnter],
), ),
hotkeyScope, hotkeyScope,
[selectedItemIdState, itemId, onEnter], [itemId, onEnter],
{ {
preventDefault: false, preventDefault: false,
}, },

View File

@ -1,60 +1,85 @@
import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/internal/useSelectableListStates'; import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useSelectableList = (selectableListId?: string) => { export const useSelectableList = (instanceId?: string) => {
const { const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow(
scopeId, SelectableListComponentInstanceContext,
selectableItemIdsState, instanceId,
selectableListOnEnterState,
isSelectedItemIdSelector,
selectedItemIdState,
} = useSelectableListStates({
selectableListScopeId: selectableListId,
});
const setSelectableItemIds = useSetRecoilState(selectableItemIdsState);
const setSelectableListOnEnter = useSetRecoilState(
selectableListOnEnterState,
); );
const resetSelectedItem = useRecoilCallback( const resetSelectedItem = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
() => { () => {
const selectedItemId = getSnapshotValue(snapshot, selectedItemIdState); const selectedItemId = getSnapshotValue(
snapshot,
selectedItemIdComponentState.atomFamily({
instanceId: selectableListInstanceId,
}),
);
if (isDefined(selectedItemId)) { if (isDefined(selectedItemId)) {
set(selectedItemIdState, null); set(
set(isSelectedItemIdSelector(selectedItemId), false); selectedItemIdComponentState.atomFamily({
instanceId: selectableListInstanceId,
}),
null,
);
set(
isSelectedItemIdComponentFamilySelector.selectorFamily({
instanceId: selectableListInstanceId,
familyKey: selectedItemId,
}),
false,
);
} }
}, },
[selectedItemIdState, isSelectedItemIdSelector], [selectableListInstanceId],
); );
const setSelectedItemId = useRecoilCallback( const setSelectedItemId = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
(itemId: string) => { (itemId: string) => {
const selectedItemId = getSnapshotValue(snapshot, selectedItemIdState); const selectedItemId = getSnapshotValue(
snapshot,
selectedItemIdComponentState.atomFamily({
instanceId: selectableListInstanceId,
}),
);
if (isDefined(selectedItemId)) { if (isDefined(selectedItemId)) {
set(isSelectedItemIdSelector(selectedItemId), false); set(
isSelectedItemIdComponentFamilySelector.selectorFamily({
instanceId: selectableListInstanceId,
familyKey: selectedItemId,
}),
false,
);
} }
set(selectedItemIdState, itemId); set(
set(isSelectedItemIdSelector(itemId), true); selectedItemIdComponentState.atomFamily({
instanceId: selectableListInstanceId,
}),
itemId,
);
set(
isSelectedItemIdComponentFamilySelector.selectorFamily({
instanceId: selectableListInstanceId,
familyKey: itemId,
}),
true,
);
}, },
[selectedItemIdState, isSelectedItemIdSelector], [selectableListInstanceId],
); );
return { return {
selectableListId: scopeId,
setSelectableItemIds,
isSelectedItemIdSelector,
setSelectableListOnEnter,
resetSelectedItem, resetSelectedItem,
setSelectedItemId, setSelectedItemId,
selectedItemIdState,
}; };
}; };

View File

@ -1,21 +0,0 @@
import { ReactNode } from 'react';
import { SelectableListScopeInternalContext } from './scope-internal-context/SelectableListScopeInternalContext';
type SelectableListScopeProps = {
children: ReactNode;
selectableListScopeId: string;
};
export const SelectableListScope = ({
children,
selectableListScopeId,
}: SelectableListScopeProps) => {
return (
<SelectableListScopeInternalContext.Provider
value={{ scopeId: selectableListScopeId }}
>
{children}
</SelectableListScopeInternalContext.Provider>
);
};

View File

@ -1,7 +0,0 @@
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
type SelectableListScopeInternalContextProps = RecoilComponentStateKey;
export const SelectableListScopeInternalContext =
createScopeInternalContext<SelectableListScopeInternalContextProps>();

View File

@ -0,0 +1,4 @@
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
export const SelectableListComponentInstanceContext =
createComponentInstanceContext();

View File

@ -1,9 +1,9 @@
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
export const isSelectedItemIdComponentFamilyState = createComponentFamilyState< export const isSelectedItemIdComponentFamilyState =
boolean, createComponentFamilyStateV2<boolean, string>({
string key: 'isSelectedItemIdComponentFamilyState',
>({ defaultValue: false,
key: 'isSelectedItemIdComponentFamilyState', componentInstanceContext: SelectableListComponentInstanceContext,
defaultValue: false, });
});

View File

@ -1,8 +1,10 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const selectableItemIdsComponentState = createComponentState<string[][]>( export const selectableItemIdsComponentState = createComponentStateV2<
{ string[][]
key: 'selectableItemIdsComponentState', >({
defaultValue: [[]], key: 'selectableItemIdsComponentState',
}, defaultValue: [[]],
); componentInstanceContext: SelectableListComponentInstanceContext,
});

View File

@ -1,8 +1,10 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const selectableListOnEnterComponentState = createComponentState< export const selectableListOnEnterComponentState = createComponentStateV2<
((itemId: string) => void) | undefined ((itemId: string) => void) | undefined
>({ >({
key: 'selectableListOnEnterComponentState', key: 'selectableListOnEnterComponentState',
defaultValue: undefined, defaultValue: undefined,
componentInstanceContext: SelectableListComponentInstanceContext,
}); });

View File

@ -1,8 +1,10 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const selectedItemIdComponentState = createComponentState<string | null>( export const selectedItemIdComponentState = createComponentStateV2<
{ string | null
key: 'selectedItemIdComponentState', >({
defaultValue: null, key: 'selectedItemIdComponentState',
}, defaultValue: null,
); componentInstanceContext: SelectableListComponentInstanceContext,
});

View File

@ -0,0 +1,28 @@
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-list/states/isSelectedItemIdComponentFamilyState';
import { createComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2';
export const isSelectedItemIdComponentFamilySelector =
createComponentFamilySelectorV2<boolean, string>({
key: 'isSelectedItemIdComponentFamilySelector',
componentInstanceContext: SelectableListComponentInstanceContext,
get:
({ instanceId, familyKey }: { instanceId: string; familyKey: string }) =>
({ get }) =>
get(
isSelectedItemIdComponentFamilyState.atomFamily({
instanceId: instanceId,
familyKey: familyKey,
}),
),
set:
({ instanceId, familyKey }: { instanceId: string; familyKey: string }) =>
({ set }, newValue) =>
set(
isSelectedItemIdComponentFamilyState.atomFamily({
instanceId: instanceId,
familyKey: familyKey,
}),
newValue,
),
});

View File

@ -1,28 +0,0 @@
import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-list/states/isSelectedItemIdComponentFamilyState';
import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector';
export const isSelectedItemIdFamilySelector = createComponentFamilySelector<
boolean,
string
>({
key: 'isSelectedItemIdFamilySelector',
get:
({ scopeId, familyKey }: { scopeId: string; familyKey: string }) =>
({ get }) =>
get(
isSelectedItemIdComponentFamilyState({
scopeId: scopeId,
familyKey: familyKey,
}),
),
set:
({ scopeId, familyKey }: { scopeId: string; familyKey: string }) =>
({ set }, newValue) =>
set(
isSelectedItemIdComponentFamilyState({
scopeId: scopeId,
familyKey: familyKey,
}),
newValue,
),
});

View File

@ -1,4 +1,5 @@
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2'; import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap'; import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
import { SerializableParam, useRecoilState } from 'recoil'; import { SerializableParam, useRecoilState } from 'recoil';
@ -7,7 +8,9 @@ export const useRecoilComponentFamilyStateV2 = <
StateType, StateType,
FamilyKey extends SerializableParam, FamilyKey extends SerializableParam,
>( >(
componentState: ComponentFamilyStateV2<StateType, FamilyKey>, componentState:
| ComponentFamilyStateV2<StateType, FamilyKey>
| ComponentFamilySelectorV2<StateType, FamilyKey>,
familyKey: FamilyKey, familyKey: FamilyKey,
instanceIdFromProps?: string, instanceIdFromProps?: string,
) => { ) => {
@ -26,5 +29,10 @@ export const useRecoilComponentFamilyStateV2 = <
instanceIdFromProps, instanceIdFromProps,
); );
return useRecoilState(componentState.atomFamily({ instanceId, familyKey })); const familySelector =
componentState.type === 'ComponentFamilyState'
? componentState.atomFamily({ instanceId, familyKey })
: componentState.selectorFamily({ instanceId, familyKey });
return useRecoilState(familySelector);
}; };

View File

@ -16,6 +16,7 @@ export function createComponentFamilySelectorV2<
>(options: { >(options: {
key: string; key: string;
get: SelectorGetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>; get: SelectorGetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
set?: never;
componentInstanceContext: ComponentInstanceStateContext<any> | null; componentInstanceContext: ComponentInstanceStateContext<any> | null;
}): ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>; }): ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>;