Files
twenty/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop.ts
T
Charles Bochet 121788c42f Fully deprecate old recoil (#18210)
## Summary

Removes the `recoil` dependency entirely from `package.json` and
`twenty-front/package.json`, completing the migration to Jotai as the
sole state management library.

Removes all Recoil infrastructure: `RecoilRoot` wrapper from `App.tsx`
and test decorators, `RecoilDebugObserver`, Recoil-specific ESLint rules
(`use-getLoadable-and-getValue-to-get-atoms`,
`useRecoilCallback-has-dependency-array`), and legacy Recoil utility
hooks/types (`useRecoilComponentState`, `useRecoilComponentValue`,
`createComponentState`, `createFamilyState`, `getSnapshotValue`,
`cookieStorageEffect`, `localStorageEffect`, etc.).

Renames all `V2`-suffixed Jotai state files and types to their canonical
names (e.g., `ComponentStateV2` -> `ComponentState`,
`agentChatInputStateV2` -> `agentChatInputState`, `SelectorCallbacksV2`
-> `SelectorCallbacks`), and removes the now-redundant V1 counterparts.

Updates ~433 files across the codebase to use the renamed Jotai imports,
remove Recoil imports, and clean up test wrappers (`RecoilRootDecorator`
-> `JotaiRootDecorator`).
2026-02-25 12:26:42 +01:00

151 lines
4.9 KiB
TypeScript

import { type OnDragEndResponder } from '@hello-pangea/dnd';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId';
import { useSortedNavigationMenuItems } from '@/navigation-menu-item/hooks/useSortedNavigationMenuItems';
import { useUpdateNavigationMenuItem } from '@/navigation-menu-item/hooks/useUpdateNavigationMenuItem';
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition';
import { FOLDER_DROPPABLE_IDS } from '@/ui/layout/draggable-list/utils/folderDroppableIds';
import { validateAndExtractFolderId } from '@/ui/layout/draggable-list/utils/validateAndExtractFolderId';
import { usePrefetchedNavigationMenuItemsData } from './usePrefetchedNavigationMenuItemsData';
export const useHandleNavigationMenuItemDragAndDrop = () => {
const { navigationMenuItems } = usePrefetchedNavigationMenuItemsData();
const { navigationMenuItemsSorted } = useSortedNavigationMenuItems();
const { updateNavigationMenuItem } = useUpdateNavigationMenuItem();
const setOpenNavigationMenuItemFolderIds = useSetAtomState(
openNavigationMenuItemFolderIdsState,
);
const openDestinationFolder = (folderId: string | null) => {
if (!folderId) {
return;
}
setOpenNavigationMenuItemFolderIds((current) => {
if (!current.includes(folderId)) {
return [...current, folderId];
}
return current;
});
};
const handleNavigationMenuItemDragAndDrop: OnDragEndResponder = async (
result,
) => {
const { destination, source, draggableId } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
if (isWorkspaceDroppableId(destination.droppableId)) {
return;
}
const draggedNavigationMenuItem = navigationMenuItems.find(
(item) => item.id === draggableId,
);
if (!draggedNavigationMenuItem) {
return;
}
const destinationFolderId = validateAndExtractFolderId({
droppableId: destination.droppableId,
orphanDroppableId:
NavigationMenuItemDroppableIds.ORPHAN_NAVIGATION_MENU_ITEMS,
});
const sourceFolderId = validateAndExtractFolderId({
droppableId: source.droppableId,
orphanDroppableId:
NavigationMenuItemDroppableIds.ORPHAN_NAVIGATION_MENU_ITEMS,
});
if (
destination.droppableId.startsWith(
FOLDER_DROPPABLE_IDS.FOLDER_HEADER_PREFIX,
)
) {
if (destinationFolderId === null)
throw new Error('Invalid folder header ID');
const folderNavigationMenuItems = navigationMenuItemsSorted.filter(
(item) => item.folderId === destinationFolderId,
);
const newPosition =
folderNavigationMenuItems.length === 0
? 1
: folderNavigationMenuItems[folderNavigationMenuItems.length - 1]
.position + 1;
await updateNavigationMenuItem({
id: draggableId,
folderId: destinationFolderId,
position: newPosition,
});
openDestinationFolder(destinationFolderId);
return;
}
if (destination.droppableId !== source.droppableId) {
const destinationNavigationMenuItems = navigationMenuItemsSorted.filter(
(item) => item.folderId === destinationFolderId,
);
let newPosition;
if (destinationNavigationMenuItems.length === 0) {
newPosition = 1;
} else if (destination.index === 0) {
newPosition = destinationNavigationMenuItems[0].position - 1;
} else if (destination.index >= destinationNavigationMenuItems.length) {
newPosition =
destinationNavigationMenuItems[
destinationNavigationMenuItems.length - 1
].position + 1;
} else {
newPosition = calculateNewPosition({
destinationIndex: destination.index,
sourceIndex: -1,
items: destinationNavigationMenuItems,
});
}
await updateNavigationMenuItem({
id: draggableId,
folderId: destinationFolderId ?? null,
position: newPosition,
});
return;
}
const navigationMenuItemsInSameList = navigationMenuItemsSorted
.filter((item) => item.folderId === sourceFolderId)
.filter((item) => item.id !== draggableId);
const newPosition = calculateNewPosition({
destinationIndex: destination.index,
sourceIndex: source.index,
items: navigationMenuItemsInSameList,
});
await updateNavigationMenuItem({
id: draggableId,
position: newPosition,
});
};
return { handleNavigationMenuItemDragAndDrop };
};