Navbar customization improvements (#17863)

- Add folder icon editing support
- And other navbar customization fixes/improvements.

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Abdul Rahman
2026-02-18 01:23:52 +05:30
committed by GitHub
parent 3d362e6e01
commit 015ccbf0a7
44 changed files with 601 additions and 141 deletions
@@ -12,18 +12,21 @@ import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext';
import { useHandleFavoriteDragAndDrop } from '@/favorites/hooks/useHandleFavoriteDragAndDrop';
import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/constants/AddToNavSourceDroppableId';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationDragSourceContext } from '@/navigation-menu-item/contexts/NavigationDragSourceContext';
import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext';
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
import { useHandleAddToNavigationDrop } from '@/navigation-menu-item/hooks/useHandleAddToNavigationDrop';
import { useHandleNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop';
import { useHandleWorkspaceNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { addToNavPayloadRegistryStateV2 } from '@/navigation-menu-item/states/addToNavPayloadRegistryStateV2';
import { getDropTargetIdFromDestination } from '@/navigation-menu-item/utils/getDropTargetIdFromDestination';
import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId';
import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared/utils';
type PageDragDropProviderProps = {
children: ReactNode;
@@ -45,7 +48,12 @@ export const PageDragDropProvider = ({
const [forbiddenDropTargetId, setForbiddenDropTargetId] = useState<
string | null
>(null);
const [
addToNavigationFallbackDestination,
setAddToNavigationFallbackDestination,
] = useState<{ droppableId: string; index: number } | null>(null);
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const { handleAddToNavigationDrop } = useHandleAddToNavigationDrop();
const { handleNavigationMenuItemDragAndDrop } =
useHandleNavigationMenuItemDragAndDrop();
@@ -53,9 +61,22 @@ export const PageDragDropProvider = ({
useHandleWorkspaceNavigationMenuItemDragAndDrop();
const { handleFavoriteDragAndDrop } = useHandleFavoriteDragAndDrop();
const orphanItemCount = workspaceNavigationMenuItems.filter(
(item) => !isDefined(item.folderId),
).length;
const handleDragStart = (dragStart: DragStart) => {
setIsDragging(true);
setSourceDroppableId(dragStart.source.droppableId);
if (dragStart.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
const defaultDestination = {
droppableId:
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS,
index: orphanItemCount,
};
setAddToNavigationFallbackDestination(defaultDestination);
setActiveDropTargetId(getDropTargetIdFromDestination(defaultDestination));
}
};
const handleDragUpdate = useRecoilCallback(
@@ -65,36 +86,53 @@ export const PageDragDropProvider = ({
if (source.droppableId !== ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
return;
}
if (!destination || !isWorkspaceDroppableId(destination.droppableId)) {
setActiveDropTargetId(null);
setForbiddenDropTargetId(null);
return;
}
const dropTargetId = getDropTargetIdFromDestination(destination);
setActiveDropTargetId(dropTargetId);
if (
destination !== null &&
isWorkspaceDroppableId(destination.droppableId)
) {
setAddToNavigationFallbackDestination(destination);
const dropTargetId = getDropTargetIdFromDestination(destination);
setActiveDropTargetId(dropTargetId);
const payload =
jotaiStore
.get(addToNavPayloadRegistryStateV2.atom)
.get(update.draggableId) ?? null;
const folderId = validateAndExtractWorkspaceFolderId(
destination.droppableId,
);
const isFolderOverFolder =
payload?.type === 'folder' && folderId !== null;
setForbiddenDropTargetId(isFolderOverFolder ? dropTargetId : null);
const payload =
jotaiStore
.get(addToNavPayloadRegistryStateV2.atom)
.get(update.draggableId) ?? null;
const folderId = validateAndExtractWorkspaceFolderId(
destination.droppableId,
);
const isFolderOverFolder =
payload?.type === 'folder' && folderId !== null;
setForbiddenDropTargetId(isFolderOverFolder ? dropTargetId : null);
} else {
setForbiddenDropTargetId(null);
const fallback = addToNavigationFallbackDestination;
setActiveDropTargetId(
fallback ? getDropTargetIdFromDestination(fallback) : null,
);
}
}) as OnDragUpdateResponder,
[],
[addToNavigationFallbackDestination],
);
const handleDragEnd = (result: DropResult, provided: ResponderProvided) => {
const isAddToNavigationSource =
result.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID;
const effectiveResult: DropResult =
isAddToNavigationSource &&
!result.destination &&
addToNavigationFallbackDestination
? { ...result, destination: addToNavigationFallbackDestination }
: result;
setIsDragging(false);
setSourceDroppableId(null);
setActiveDropTargetId(null);
setForbiddenDropTargetId(null);
setAddToNavigationFallbackDestination(null);
if (result.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
handleAddToNavigationDrop(result, provided);
if (isAddToNavigationSource) {
handleAddToNavigationDrop(effectiveResult, provided);
return;
}
@@ -122,6 +160,7 @@ export const PageDragDropProvider = ({
setActiveDropTargetId,
forbiddenDropTargetId,
setForbiddenDropTargetId,
addToNavigationFallbackDestination,
}}
>
<DragDropContext