Files
twenty/packages/twenty-front/src/modules/side-panel/components/SidePanelLinkInfo.tsx
T
Charles Bochet 9f7c29bce8 refactor(twenty-front): unify Favorites and Workspace navigation menu item code (#18697)
## Summary

- Consolidate duplicated Favorites and Workspace navigation menu item
frontend code into a single unified codebase within
`navigation-menu-item/`
- Move all DnD-related code from `navigation/` module into
`navigation-menu-item/display/dnd/`, renaming `workspaceDndKit*` files
to `navigationMenuItemDndKit*`
- Unify duplicated components (DnD providers, DnD hooks, folder
components, orphan items, section shell) using a `NavigationSections`
enum to parameterize section-specific behavior
- Rename residual workspace-prefixed symbols
(`WorkspaceDndKitSortableItem`, `WorkspaceDndKitDroppableSlot`,
`useWorkspaceSectionItems`, etc.) to `navigationMenuItem`-prefixed
equivalents
- Clean `navigation/` module to only contain app-level concerns (drawer
layout, settings, routing)

### Key changes

| Before (duplicated) | After (unified) |
|---|---|
| `FavoritesDndKitProvider` + `WorkspaceDndKitProvider` |
`NavigationMenuItemDndKitProvider` with `section` prop |
| `useFavoritesDndKit` + `useWorkspaceDndKit` |
`useNavigationMenuItemDndKit(section)` |
| `FavoritesFolderItem` + `WorkspaceNavigationMenuItemsFolder` |
`NavigationMenuItemFolder` with `section` prop |
| `FavoritesOrphanItems` | `NavigationMenuItemOrphanItems` with
`section` prop |
| Separate section shells | Shared `NavigationMenuItemSection` with thin
wrappers |
| `WorkspaceDndKitSortableItem` | `NavigationMenuItemSortableItem` |
| `WorkspaceDndKitDroppableSlot` | `NavigationMenuItemDroppableSlot` |
| `useWorkspaceSectionItems` | `useNavigationMenuItemSectionItems` |
| `useWorkspaceFolderOpenState` | `useNavigationMenuItemFolderOpenState`
|

Net result: **~1650 lines deleted** across 51 files.

## Test plan

- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx lint twenty-front` passes (0 errors)
- [x] `npx nx test twenty-front` passes (763 suites, 4467 tests)
- [ ] Smoke test: Favorites section renders, DnD reorder works, folder
create/rename/delete works
- [ ] Smoke test: Workspace section renders, edit mode works, DnD
reorder works
- [ ] Smoke test: Add-to-navigation from side panel works for both
sections

Made with [Cursor](https://cursor.com)
2026-03-17 19:01:09 +01:00

89 lines
3.2 KiB
TypeScript

import { useLingui } from '@lingui/react/macro';
import { IconLink, IconWorld } from 'twenty-ui/display';
import { LinkIconWithLinkOverlay } from '@/navigation-menu-item/display/link/components/LinkIconWithLinkOverlay';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/edit/link/hooks/useUpdateLinkInDraft';
import { useNavigationMenuItemSectionItems } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/common/states/selectedNavigationMenuItemInEditModeState';
import { SidePanelPageInfoLayout } from '@/side-panel/components/SidePanelPageInfoLayout';
import { sidePanelPageInfoState } from '@/side-panel/states/sidePanelPageInfoState';
import { sidePanelShouldFocusTitleInputComponentState } from '@/side-panel/states/sidePanelShouldFocusTitleInputComponentState';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
export const SidePanelLinkInfo = () => {
const { t } = useLingui();
const sidePanelPageInfo = useAtomStateValue(sidePanelPageInfoState);
const [sidePanelShouldFocusTitleInput, setSidePanelShouldFocusTitleInput] =
useAtomComponentState(
sidePanelShouldFocusTitleInputComponentState,
sidePanelPageInfo.instanceId,
);
const selectedNavigationMenuItemInEditMode = useAtomStateValue(
selectedNavigationMenuItemInEditModeState,
);
const items = useNavigationMenuItemSectionItems();
const { updateLinkInDraft } = useUpdateLinkInDraft();
const defaultLabel = t`Link label`;
const placeholder = t`Link label`;
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find(
(item) =>
item.type === NavigationMenuItemType.LINK &&
item.id === selectedNavigationMenuItemInEditMode,
)
: undefined;
if (!selectedItem) return null;
const itemId = selectedItem.id;
const itemName = selectedItem.name ?? defaultLabel;
const handleChange = (text: string) => {
updateLinkInDraft(itemId, { name: text });
};
const handleSave = () => {
const trimmed = itemName.trim();
const finalName = trimmed.length > 0 ? trimmed : defaultLabel;
if (finalName !== itemName) {
updateLinkInDraft(itemId, { name: finalName });
}
};
return (
<SidePanelPageInfoLayout
icon={
<LinkIconWithLinkOverlay
link={selectedItem.link}
LinkIcon={IconLink}
DefaultIcon={IconWorld}
color={selectedItem.color}
/>
}
title={
<TitleInput
instanceId={`link-label-${itemId}`}
sizeVariant="sm"
value={itemName}
onChange={handleChange}
placeholder={placeholder}
onEnter={handleSave}
onEscape={handleSave}
onClickOutside={handleSave}
onTab={handleSave}
onShiftTab={handleSave}
shouldFocus={sidePanelShouldFocusTitleInput}
onFocus={() => setSidePanelShouldFocusTitleInput(false)}
/>
}
label={t`Link`}
/>
);
};