Navbar customization followup (#17848)

Addresses review comments from the [first navbar customization
PR](https://github.com/twentyhq/twenty/pull/17728)

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
This commit is contained in:
Abdul Rahman
2026-02-12 08:05:43 +05:30
committed by GitHub
parent 91bfd45dc5
commit 3fb2352c78
67 changed files with 993 additions and 686 deletions
@@ -1,30 +1,21 @@
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconFolder, IconLink } from 'twenty-ui/display';
import { IconFolder } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
import { useUpdateFolderNameInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderNameInDraft';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
const ICON_CONFIG = {
folder: { Icon: IconFolder, colorKey: 'folder' },
link: { Icon: IconLink, colorKey: 'link' },
} as const;
export const CommandMenuFolderLinkInfo = ({
type,
}: {
type: 'folder' | 'link';
}) => {
export const CommandMenuFolderInfo = () => {
const theme = useTheme();
const { t } = useLingui();
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
@@ -38,15 +29,14 @@ export const CommandMenuFolderLinkInfo = ({
);
const items = useWorkspaceSectionItems();
const { updateFolderNameInDraft } = useUpdateFolderNameInDraft();
const { updateLinkInDraft } = useUpdateLinkInDraft();
const defaultLabel = type === 'folder' ? t`New folder` : t`Link label`;
const placeholder = type === 'folder' ? t`Folder name` : t`Link label`;
const defaultLabel = t`New folder`;
const placeholder = t`Folder name`;
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find(
(item) =>
item.itemType === type &&
item.itemType === NavigationMenuItemType.FOLDER &&
item.id === selectedNavigationMenuItemInEditMode,
)
: undefined;
@@ -57,11 +47,7 @@ export const CommandMenuFolderLinkInfo = ({
const itemName = selectedItem.name ?? defaultLabel;
const handleChange = (text: string) => {
if (type === 'folder') {
updateFolderNameInDraft(itemId, text);
} else {
updateLinkInDraft(itemId, { name: text });
}
updateFolderNameInDraft(itemId, text);
};
const handleSave = () => {
@@ -69,23 +55,17 @@ export const CommandMenuFolderLinkInfo = ({
const finalName = trimmed.length > 0 ? trimmed : defaultLabel;
if (finalName !== itemName) {
if (type === 'folder') {
updateFolderNameInDraft(itemId, finalName);
} else {
updateLinkInDraft(itemId, { name: finalName });
}
updateFolderNameInDraft(itemId, finalName);
}
};
const { Icon, colorKey } = ICON_CONFIG[type];
return (
<CommandMenuPageInfoLayout
icon={
<StyledNavigationMenuItemIconContainer
$backgroundColor={getNavigationMenuItemIconColors(theme)[colorKey]}
$backgroundColor={getNavigationMenuItemIconColors(theme).folder}
>
<Icon
<IconFolder
size={theme.spacing(3.5)}
color={theme.grayScale.gray1}
stroke={theme.icon.stroke.md}
@@ -94,9 +74,7 @@ export const CommandMenuFolderLinkInfo = ({
}
title={
<TitleInput
instanceId={
type === 'folder' ? `folder-name-${itemId}` : `link-label-${itemId}`
}
instanceId={`folder-name-${itemId}`}
sizeVariant="sm"
value={itemName}
onChange={handleChange}
@@ -110,7 +88,6 @@ export const CommandMenuFolderLinkInfo = ({
onFocus={() => setShouldFocusTitleInput(false)}
/>
}
label={type === 'link' ? t`link` : undefined}
/>
);
};
@@ -0,0 +1,94 @@
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconLink } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
export const CommandMenuLinkInfo = () => {
const theme = useTheme();
const { t } = useLingui();
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const [shouldFocusTitleInput, setShouldFocusTitleInput] =
useRecoilComponentState(
commandMenuShouldFocusTitleInputComponentState,
commandMenuPageInfo.instanceId,
);
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();
const { updateLinkInDraft } = useUpdateLinkInDraft();
const defaultLabel = t`Link label`;
const placeholder = t`Link label`;
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find(
(item) =>
item.itemType === 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 (
<CommandMenuPageInfoLayout
icon={
<StyledNavigationMenuItemIconContainer
$backgroundColor={getNavigationMenuItemIconColors(theme).link}
>
<IconLink
size={theme.spacing(3.5)}
color={theme.grayScale.gray1}
stroke={theme.icon.stroke.md}
/>
</StyledNavigationMenuItemIconContainer>
}
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={shouldFocusTitleInput}
onFocus={() => setShouldFocusTitleInput(false)}
/>
}
label={t`link`}
/>
);
};
@@ -2,27 +2,35 @@ import { useLingui } from '@lingui/react/macro';
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { ViewKey } from '@/views/types/ViewKey';
export const CommandMenuObjectViewRecordInfo = () => {
const { t } = useLingui();
const { processedItem, selectedItemLabel } =
useSelectedNavigationMenuItemEditData();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel();
const processedItem =
selectedItem && selectedItem.itemType !== NavigationMenuItemType.FOLDER
? selectedItem
: undefined;
if (!processedItem || !selectedItemLabel) {
return null;
}
const isViewOrRecord =
processedItem.itemType === 'view' || processedItem.itemType === 'record';
processedItem.itemType === NavigationMenuItemType.VIEW ||
processedItem.itemType === NavigationMenuItemType.RECORD;
if (!isViewOrRecord) {
return null;
}
const label =
processedItem.itemType === 'record'
processedItem.itemType === NavigationMenuItemType.RECORD
? t`record`
: processedItem.viewKey === ViewKey.Index
? t`object`
@@ -3,13 +3,15 @@ import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
import { CommandMenuFolderLinkInfo } from '@/command-menu/components/CommandMenuFolderLinkInfo';
import { CommandMenuFolderInfo } from '@/command-menu/components/CommandMenuFolderInfo';
import { CommandMenuLinkInfo } from '@/command-menu/components/CommandMenuLinkInfo';
import { CommandMenuMultipleRecordsInfo } from '@/command-menu/components/CommandMenuMultipleRecordsInfo';
import { CommandMenuObjectViewRecordInfo } from '@/command-menu/components/CommandMenuObjectViewRecordInfo';
import { CommandMenuPageLayoutInfo } from '@/command-menu/components/CommandMenuPageLayoutInfo';
import { CommandMenuRecordInfo } from '@/command-menu/components/CommandMenuRecordInfo';
import { CommandMenuWorkflowStepInfo } from '@/command-menu/components/CommandMenuWorkflowStepInfo';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
@@ -44,11 +46,18 @@ export const CommandMenuPageInfo = ({ pageChip }: CommandMenuPageInfoProps) => {
if (isNavigationMenuItemEditPage && isDefined(selectedNavItem)) {
const itemType = selectedNavItem.itemType;
if (itemType === 'folder' || itemType === 'link') {
return <CommandMenuFolderLinkInfo type={itemType} />;
if (itemType === NavigationMenuItemType.FOLDER) {
return <CommandMenuFolderInfo />;
}
if (itemType === 'view' || itemType === 'record') {
if (itemType === NavigationMenuItemType.LINK) {
return <CommandMenuLinkInfo />;
}
if (
itemType === NavigationMenuItemType.VIEW ||
itemType === NavigationMenuItemType.RECORD
) {
return <CommandMenuObjectViewRecordInfo />;
}
}
@@ -0,0 +1,8 @@
export enum CommandMenuNavigationItemActions {
MOVE_UP = 'move-up',
MOVE_DOWN = 'move-down',
MOVE_TO_FOLDER = 'move-to-folder',
ADD_BEFORE = 'add-before',
ADD_AFTER = 'add-after',
REMOVE = 'remove',
}
@@ -20,7 +20,7 @@ export const CommandMenuEditFolderPickerSubView = ({
const { t } = useLingui();
const [searchValue, setSearchValue] = useState('');
const { foldersToShow, includeNoFolderOption, handleSelectFolder } =
useFolderPickerSelectionData();
useFolderPickerSelectionData({ onCloseSubView: onBack });
const filteredFolders = filterBySearchQuery({
items: foldersToShow,
@@ -10,6 +10,7 @@ import {
import { CommandGroup } from '@/command-menu/components/CommandGroup';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuNavigationItemActions } from '@/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
export type OrganizeActionsProps = {
@@ -45,65 +46,77 @@ export const CommandMenuEditOrganizeActions = ({
return (
<CommandGroup heading={t`Organize`}>
<SelectableListItem
itemId="move-up"
itemId={CommandMenuNavigationItemActions.MOVE_UP}
onEnter={canMoveUp ? onMoveUp : undefined}
>
<CommandMenuItem
Icon={IconChevronUp}
label={t`Move up`}
id="move-up"
id={CommandMenuNavigationItemActions.MOVE_UP}
onClick={onMoveUp}
disabled={!canMoveUp}
/>
</SelectableListItem>
<SelectableListItem
itemId="move-down"
itemId={CommandMenuNavigationItemActions.MOVE_DOWN}
onEnter={canMoveDown ? onMoveDown : undefined}
>
<CommandMenuItem
Icon={IconChevronDown}
label={t`Move down`}
id="move-down"
id={CommandMenuNavigationItemActions.MOVE_DOWN}
onClick={onMoveDown}
disabled={!canMoveDown}
/>
</SelectableListItem>
{showMoveToFolder && onMoveToFolder && (
<SelectableListItem itemId="move-to-folder" onEnter={onMoveToFolder}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.MOVE_TO_FOLDER}
onEnter={onMoveToFolder}
>
<CommandMenuItem
Icon={IconFolderSymlink}
label={t`Move to folder`}
id="move-to-folder"
id={CommandMenuNavigationItemActions.MOVE_TO_FOLDER}
hasSubMenu={moveToFolderHasSubMenu}
onClick={onMoveToFolder}
/>
</SelectableListItem>
)}
{onAddBefore && (
<SelectableListItem itemId="add-before" onEnter={onAddBefore}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.ADD_BEFORE}
onEnter={onAddBefore}
>
<CommandMenuItem
Icon={IconRowInsertTop}
label={t`Add menu item before`}
id="add-before"
id={CommandMenuNavigationItemActions.ADD_BEFORE}
onClick={onAddBefore}
/>
</SelectableListItem>
)}
{onAddAfter && (
<SelectableListItem itemId="add-after" onEnter={onAddAfter}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.ADD_AFTER}
onEnter={onAddAfter}
>
<CommandMenuItem
Icon={IconRowInsertBottom}
label={t`Add menu item after`}
id="add-after"
id={CommandMenuNavigationItemActions.ADD_AFTER}
onClick={onAddAfter}
/>
</SelectableListItem>
)}
<SelectableListItem itemId="remove" onEnter={onRemove}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.REMOVE}
onEnter={onRemove}
>
<CommandMenuItem
Icon={IconTrash}
label={t`Remove from sidebar`}
id="remove"
id={CommandMenuNavigationItemActions.REMOVE}
onClick={onRemove}
/>
</SelectableListItem>
@@ -4,8 +4,8 @@ import { IconApps } from 'twenty-ui/display';
import { CommandGroup } from '@/command-menu/components/CommandGroup';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useFindOneApplicationQuery } from '~/generated-metadata/graphql';
@@ -18,8 +18,8 @@ export const CommandMenuEditOwnerSection = ({
}: CommandMenuEditOwnerSectionProps) => {
const { t } = useLingui();
const { selectedItem } = useSelectedNavigationMenuItemEditData();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const { currentDraft } = useDraftNavigationMenuItems();
const applicationIdFromDraft =
selectedItem && currentDraft
@@ -5,14 +5,16 @@ import { CommandMenuEditObjectViewBase } from '@/command-menu/pages/navigation-m
import { CommandMenuEditOrganizeActions } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions';
import { CommandMenuEditOwnerSection } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuEditOwnerSection';
import { useNavigationMenuItemEditOrganizeActions } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditOrganizeActions';
import { useNavigationMenuItemEditSubView } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { getOrganizeActionsSelectableItemIds } from '@/command-menu/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel';
import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -31,15 +33,14 @@ export const CommandMenuNavigationMenuItemEditPage = () => {
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const {
selectedItemLabel,
selectedItem,
selectedItemObjectMetadata,
selectedItemType,
} = useSelectedNavigationMenuItemEditData();
const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const { selectedItemObjectMetadata } =
useSelectedNavigationMenuItemEditItemObjectMetadata();
const selectedItemType = selectedItem?.itemType ?? null;
const { editSubView, setFolderPicker, clearSubView } =
useNavigationMenuItemEditSubView();
const [isFolderPickerOpen, setIsFolderPickerOpen] = useState(false);
const setFolderPicker = () => setIsFolderPickerOpen(true);
const {
canMoveUp,
@@ -63,64 +64,20 @@ export const CommandMenuNavigationMenuItemEditPage = () => {
);
}
if (editSubView === 'folder-picker') {
return <CommandMenuEditFolderPickerSubView onBack={clearSubView} />;
}
if (
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.VIEW &&
!selectedItemObjectMetadata
) {
return null;
}
if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.VIEW) {
if (isFolderPickerOpen) {
return (
<CommandMenuEditObjectViewBase
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
<CommandMenuEditFolderPickerSubView
onBack={() => setIsFolderPickerOpen(false)}
/>
);
}
if (
isDefined(selectedItem) &&
selectedItem.itemType === NAVIGATION_MENU_ITEM_TYPE.LINK
) {
return (
<CommandMenuEditLinkItemView
key={selectedItem.id}
selectedItem={selectedItem}
onUpdateLink={(linkId, link) => updateLinkInDraft(linkId, { link })}
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
);
}
if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK) {
return null;
}
if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER) {
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(false)}
>
<CommandMenuEditOrganizeActions
switch (selectedItemType) {
case NavigationMenuItemType.VIEW:
if (!selectedItemObjectMetadata) return null;
return (
<CommandMenuEditObjectViewBase
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
@@ -129,27 +86,65 @@ export const CommandMenuNavigationMenuItemEditPage = () => {
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
<CommandMenuEditOwnerSection />
</CommandMenuList>
);
);
case NavigationMenuItemType.LINK:
if (
isDefined(selectedItem) &&
selectedItem.itemType === NavigationMenuItemType.LINK
) {
return (
<CommandMenuEditLinkItemView
key={selectedItem.id}
selectedItem={selectedItem}
onUpdateLink={(linkId, link) => updateLinkInDraft(linkId, { link })}
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
);
}
return null;
case NavigationMenuItemType.FOLDER:
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(false)}
>
<CommandMenuEditOrganizeActions
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
<CommandMenuEditOwnerSection />
</CommandMenuList>
);
default:
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(true)}
>
<CommandMenuEditOrganizeActions
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
showMoveToFolder
onMoveToFolder={setFolderPicker}
/>
</CommandMenuList>
);
}
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(true)}
>
<CommandMenuEditOrganizeActions
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
showMoveToFolder
onMoveToFolder={setFolderPicker}
/>
</CommandMenuList>
);
};
@@ -13,24 +13,25 @@ import { CommandMenuAddToNavDroppable } from '@/command-menu/components/CommandM
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag';
import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { useAddFolderToNavigationMenu } from '@/command-menu/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu';
import { useAddLinkToNavigationMenu } from '@/command-menu/pages/navigation-menu-item/hooks/useAddLinkToNavigationMenu';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
type CommandMenuNewSidebarItemMainMenuProps = {
onSelectObject: () => void;
onSelectView: () => void;
onSelectRecord: () => void;
onAddFolder: () => void;
onAddLink: () => void;
};
export const CommandMenuNewSidebarItemMainMenu = ({
onSelectObject,
onSelectView,
onSelectRecord,
onAddFolder,
onAddLink,
}: CommandMenuNewSidebarItemMainMenuProps) => {
const { t } = useLingui();
const { handleAddFolder } = useAddFolderToNavigationMenu();
const { handleAddLink } = useAddLinkToNavigationMenu();
return (
<CommandMenuAddToNavDroppable>
@@ -77,29 +78,29 @@ export const CommandMenuNewSidebarItemMainMenu = ({
</CommandMenuAddToNavDraggablePlaceholder>
</CommandGroup>
<CommandGroup heading={t`Other`}>
<SelectableListItem itemId="folder" onEnter={onAddFolder}>
<SelectableListItem itemId="folder" onEnter={handleAddFolder}>
<CommandMenuItemWithAddToNavigationDrag
icon={IconFolder}
label={t`Folder`}
id="folder"
onClick={onAddFolder}
onClick={handleAddFolder}
dragIndex={3}
payload={{
type: 'folder',
type: NavigationMenuItemType.FOLDER,
folderId: 'new',
name: t`New folder`,
}}
/>
</SelectableListItem>
<SelectableListItem itemId="link" onEnter={onAddLink}>
<SelectableListItem itemId="link" onEnter={handleAddLink}>
<CommandMenuItemWithAddToNavigationDrag
icon={IconLink}
label={t`Link`}
id="link"
onClick={onAddLink}
onClick={handleAddLink}
dragIndex={4}
payload={{
type: 'link',
type: NavigationMenuItemType.LINK,
linkId: 'new',
name: t`Link label`,
link: 'https://www.example.com',
@@ -0,0 +1,106 @@
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuObjectPickerSubView';
import { CommandMenuSystemObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuSystemObjectPickerSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem';
import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ViewKey } from '@/views/types/ViewKey';
type CommandMenuNewSidebarItemObjectFlowProps = {
onBack: () => void;
};
export const CommandMenuNewSidebarItemObjectFlow = ({
onBack,
}: CommandMenuNewSidebarItemObjectFlowProps) => {
const { t } = useLingui();
const { closeCommandMenu } = useCommandMenu();
const [objectSearchInput, setObjectSearchInput] = useState('');
const [systemObjectSearchInput, setSystemObjectSearchInput] = useState('');
const [isInSystemPicker, setIsInSystemPicker] = useState(false);
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const { addObjectToDraft } = useAddObjectToNavigationMenuDraft();
const { activeNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const { views, objectMetadataIdsWithIndexView } =
useNavigationMenuObjectMetadataFromDraft(currentDraft);
const objectMetadataIdsWithDisplayableViews = new Set(
views
.filter((view) => view.key !== ViewKey.Index)
.map((view) => view.objectMetadataId),
);
const { availableObjectMetadataItems, availableSystemObjectMetadataItems } =
getAvailableObjectMetadataForNewSidebarItem({
objectMetadataItems,
activeNonSystemObjectMetadataItems,
objectMetadataIdsWithIndexView,
objectMetadataIdsWithDisplayableViews,
});
const handleSelectObject = (
objectMetadataItem: ObjectMetadataItem,
defaultViewId: string,
) => {
addObjectToDraft(
objectMetadataItem.id,
defaultViewId,
currentDraft,
addMenuItemInsertionContext?.targetFolderId,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
closeCommandMenu();
};
const handleBackToObjectList = () => {
setIsInSystemPicker(false);
setSystemObjectSearchInput('');
};
if (isInSystemPicker) {
return (
<CommandMenuSystemObjectPickerSubView
systemObjects={availableSystemObjectMetadataItems}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackToObjectList}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
emptyNoResultsText={t`All system objects are already in the sidebar`}
/>
);
}
return (
<CommandMenuObjectPickerSubView
objects={availableObjectMetadataItems}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={onBack}
onOpenSystemPicker={() => setIsInSystemPicker(true)}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
/>
);
};
@@ -1,252 +1,24 @@
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFolder, IconLink } from 'twenty-ui/display';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuNewSidebarItemMainMenu } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu';
import { CommandMenuNewSidebarItemObjectFlow } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemObjectFlow';
import { CommandMenuNewSidebarItemRecordSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordSubView';
import { CommandMenuNewSidebarItemViewObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewObjectPickerSubView';
import { CommandMenuNewSidebarItemViewPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView';
import { CommandMenuNewSidebarItemViewSystemSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewSystemSubView';
import { CommandMenuObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuObjectPickerSubView';
import { CommandMenuSystemObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuSystemObjectPickerSubView';
import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft';
import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft';
import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ViewKey } from '@/views/types/ViewKey';
import { CommandMenuNewSidebarItemViewFlow } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewFlow';
type SelectedOption =
| 'object'
| 'record'
| 'system'
| 'view'
| 'view-system'
| null;
type SelectedOption = 'object' | 'record' | 'view' | null;
export const CommandMenuNewSidebarItemPage = () => {
const { t } = useLingui();
const { closeCommandMenu } = useCommandMenu();
const [selectedOption, setSelectedOption] = useState<SelectedOption>(null);
const [selectedObjectMetadataIdForView, setSelectedObjectMetadataIdForView] =
useState<string | null>(null);
const [objectSearchInput, setObjectSearchInput] = useState('');
const [systemObjectSearchInput, setSystemObjectSearchInput] = useState('');
const { objectMetadataItems } = useObjectMetadataItems();
const { addObjectToDraft } = useAddObjectToNavigationMenuDraft();
const { addFolderToDraft } = useAddFolderToNavigationMenuDraft();
const { addLinkToDraft } = useAddLinkToNavigationMenuDraft();
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetRecoilState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInCommandMenu } =
useOpenNavigationMenuItemInCommandMenu();
const { activeNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const currentDraft = isDefined(navigationMenuItemsDraft)
? navigationMenuItemsDraft
: workspaceNavigationMenuItems;
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const { views, objectMetadataIdsWithIndexView } =
useNavigationMenuObjectMetadataFromDraft(currentDraft);
const objectMetadataIdsWithDisplayableViews = new Set(
views
.filter((view) => view.key !== ViewKey.Index)
.map((view) => view.objectMetadataId),
);
const availableObjectMetadataItems = [
...activeNonSystemObjectMetadataItems,
].sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const activeSystemObjectMetadataItems = objectMetadataItems
.filter((item) => item.isActive && item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItems =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithIndexView.has(item.id),
);
const objectMetadataItemsWithViews = objectMetadataItems
.filter(
(item) =>
item.isActive && objectMetadataIdsWithDisplayableViews.has(item.id),
)
.filter((item) => !item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItemsForView =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithDisplayableViews.has(item.id),
);
const handleSelectObject = (
objectMetadataItem: ObjectMetadataItem,
defaultViewId: string,
) => {
addObjectToDraft(
objectMetadataItem.id,
defaultViewId,
currentDraft,
addMenuItemInsertionContext?.targetFolderId,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
closeCommandMenu();
};
const handleBackToMain = () => {
setSelectedOption(null);
setSelectedObjectMetadataIdForView(null);
setObjectSearchInput('');
setSystemObjectSearchInput('');
};
const handleBackToObjectList = () => {
setSelectedOption('object');
setSystemObjectSearchInput('');
};
const handleBackToViewObjectList = () => {
const selectedObjectMetadataItem = isDefined(
selectedObjectMetadataIdForView,
)
? objectMetadataItems.find(
(item) => item.id === selectedObjectMetadataIdForView,
)
: undefined;
const cameFromSystemObjects = selectedObjectMetadataItem?.isSystem ?? false;
setSelectedObjectMetadataIdForView(null);
if (cameFromSystemObjects) {
setSelectedOption('view-system');
}
};
const handleBackToViewObjectListFromSystem = () => {
setSelectedOption('view');
setSystemObjectSearchInput('');
};
const handleAddFolderAndOpenEdit = () => {
const newFolderId = addFolderToDraft(
t`New folder`,
currentDraft,
addMenuItemInsertionContext?.targetFolderId ?? null,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(newFolderId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit folder`,
pageIcon: IconFolder,
focusTitleInput: true,
});
};
const handleAddLinkAndOpenEdit = () => {
const newLinkId = addLinkToDraft(
t`Link label`,
'www.example.com',
currentDraft,
addMenuItemInsertionContext?.targetFolderId ?? null,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(newLinkId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit link`,
pageIcon: IconLink,
focusTitleInput: true,
});
};
switch (selectedOption) {
case 'view':
if (isDefined(selectedObjectMetadataIdForView)) {
return (
<CommandMenuNewSidebarItemViewPickerSubView
selectedObjectMetadataIdForView={selectedObjectMetadataIdForView}
onBack={handleBackToViewObjectList}
/>
);
}
return (
<CommandMenuNewSidebarItemViewObjectPickerSubView
objects={objectMetadataItemsWithViews}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={handleBackToMain}
onOpenSystemPicker={() => setSelectedOption('view-system')}
onSelectObject={(item) => setSelectedObjectMetadataIdForView(item.id)}
showSystemObjectsOption={
availableSystemObjectMetadataItemsForView.length > 0
}
/>
);
case 'view-system':
return (
<CommandMenuNewSidebarItemViewSystemSubView
systemObjects={availableSystemObjectMetadataItemsForView}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackToViewObjectListFromSystem}
onSelectObject={(item) => {
setSelectedObjectMetadataIdForView(item.id);
setSelectedOption('view');
}}
/>
);
return <CommandMenuNewSidebarItemViewFlow onBack={handleBackToMain} />;
case 'object':
return (
<CommandMenuObjectPickerSubView
objects={availableObjectMetadataItems}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={handleBackToMain}
onOpenSystemPicker={() => setSelectedOption('system')}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
/>
);
case 'system':
return (
<CommandMenuSystemObjectPickerSubView
systemObjects={availableSystemObjectMetadataItems}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackToObjectList}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
emptyNoResultsText={t`All system objects are already in the sidebar`}
/>
);
return <CommandMenuNewSidebarItemObjectFlow onBack={handleBackToMain} />;
case 'record':
return (
<CommandMenuNewSidebarItemRecordSubView onBack={handleBackToMain} />
@@ -254,11 +26,9 @@ export const CommandMenuNewSidebarItemPage = () => {
default:
return (
<CommandMenuNewSidebarItemMainMenu
onSelectRecord={() => setSelectedOption('record')}
onSelectObject={() => setSelectedOption('object')}
onSelectView={() => setSelectedOption('view')}
onSelectRecord={() => setSelectedOption('record')}
onAddFolder={handleAddFolderAndOpenEdit}
onAddLink={handleAddLinkAndOpenEdit}
/>
);
}
@@ -3,7 +3,8 @@ import { Avatar } from 'twenty-ui/display';
import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useAddRecordToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload';
@@ -29,7 +30,7 @@ export const CommandMenuNewSidebarItemRecordItem = ({
}: CommandMenuNewSidebarItemRecordItemProps) => {
const { closeCommandMenu } = useCommandMenu();
const { addRecordToDraft } = useAddRecordToNavigationMenuDraft();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { currentDraft } = useDraftNavigationMenuItems();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
@@ -41,7 +42,7 @@ export const CommandMenuNewSidebarItemRecordItem = ({
(item) => item.nameSingular === record.objectNameSingular,
);
const recordPayload: AddToNavigationDragPayload = {
type: 'record',
type: NavigationMenuItemType.RECORD,
recordId: record.recordId,
objectMetadataId: objectMetadataItem?.id ?? '',
objectNameSingular: record.objectNameSingular,
@@ -9,7 +9,7 @@ import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandMenuSubViewWithSearch';
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
import { CommandMenuNewSidebarItemRecordItem } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordItem';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
@@ -31,7 +31,7 @@ export const CommandMenuNewSidebarItemRecordSubView = ({
onBack,
}: CommandMenuNewSidebarItemRecordSubViewProps) => {
const { t } = useLingui();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const [recordSearchInput, setRecordSearchInput] = useState('');
const [deferredRecordSearchInput] = useDebounce(recordSearchInput, 300);
@@ -0,0 +1,111 @@
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { CommandMenuNewSidebarItemViewObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewObjectPickerSubView';
import { CommandMenuNewSidebarItemViewPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView';
import { CommandMenuNewSidebarItemViewSystemSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewSystemSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { ViewKey } from '@/views/types/ViewKey';
type CommandMenuNewSidebarItemViewFlowProps = {
onBack: () => void;
};
export const CommandMenuNewSidebarItemViewFlow = ({
onBack,
}: CommandMenuNewSidebarItemViewFlowProps) => {
const [selectedObjectMetadataIdForView, setSelectedObjectMetadataIdForView] =
useState<string | null>(null);
const [objectSearchInput, setObjectSearchInput] = useState('');
const [systemObjectSearchInput, setSystemObjectSearchInput] = useState('');
const [isInSystemPicker, setIsInSystemPicker] = useState(false);
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const { activeNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const { views, objectMetadataIdsWithIndexView } =
useNavigationMenuObjectMetadataFromDraft(currentDraft);
const objectMetadataIdsWithDisplayableViews = new Set(
views
.filter((view) => view.key !== ViewKey.Index)
.map((view) => view.objectMetadataId),
);
const {
objectMetadataItemsWithViews,
availableSystemObjectMetadataItemsForView,
} = getAvailableObjectMetadataForNewSidebarItem({
objectMetadataItems,
activeNonSystemObjectMetadataItems,
objectMetadataIdsWithIndexView,
objectMetadataIdsWithDisplayableViews,
});
const handleBackFromViewPicker = () => {
const selectedObjectMetadataItem = isDefined(
selectedObjectMetadataIdForView,
)
? objectMetadataItems.find(
(item) => item.id === selectedObjectMetadataIdForView,
)
: undefined;
const cameFromSystemObjects = selectedObjectMetadataItem?.isSystem ?? false;
setSelectedObjectMetadataIdForView(null);
setIsInSystemPicker(cameFromSystemObjects);
};
const handleBackFromSystemPicker = () => {
setIsInSystemPicker(false);
};
const handleSelectObject = (objectId: string) => {
setSelectedObjectMetadataIdForView(objectId);
};
const handleSelectObjectFromSystem = (objectId: string) => {
setSelectedObjectMetadataIdForView(objectId);
setIsInSystemPicker(false);
};
if (isDefined(selectedObjectMetadataIdForView) && !isInSystemPicker) {
return (
<CommandMenuNewSidebarItemViewPickerSubView
selectedObjectMetadataIdForView={selectedObjectMetadataIdForView}
onBack={handleBackFromViewPicker}
/>
);
}
if (isInSystemPicker) {
return (
<CommandMenuNewSidebarItemViewSystemSubView
systemObjects={availableSystemObjectMetadataItemsForView}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackFromSystemPicker}
onSelectObject={(item) => handleSelectObjectFromSystem(item.id)}
/>
);
}
return (
<CommandMenuNewSidebarItemViewObjectPickerSubView
objects={objectMetadataItemsWithViews}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={onBack}
onOpenSystemPicker={() => setIsInSystemPicker(true)}
onSelectObject={(item) => handleSelectObject(item.id)}
showSystemObjectsOption={
availableSystemObjectMetadataItemsForView.length > 0
}
/>
);
};
@@ -10,7 +10,8 @@ import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandMenuSubViewWithSearch';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useFilteredPickerItems } from '@/command-menu/hooks/useFilteredPickerItems';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
@@ -33,7 +34,7 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({
const [searchValue, setSearchValue] = useState('');
const { closeCommandMenu } = useCommandMenu();
const { addViewToDraft } = useAddViewToNavigationMenuDraft();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { currentDraft } = useDraftNavigationMenuItems();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
@@ -114,7 +115,7 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({
onClick={() => handleSelectView(view)}
dragIndex={index}
payload={{
type: 'view',
type: NavigationMenuItemType.VIEW,
viewId: view.id,
label: view.name,
}}
@@ -6,6 +6,7 @@ import { useIcons } from 'twenty-ui/display';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag';
import { IconWithBackground } from '@/navigation-menu-item/components/IconWithBackground';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -55,7 +56,7 @@ export const CommandMenuObjectMenuItem = ({
onClick={handleClick}
dragIndex={dragIndex}
payload={{
type: 'object' as const,
type: NavigationMenuItemType.OBJECT,
objectMetadataId: objectMetadataItem.id,
defaultViewId: defaultViewId ?? '',
label: objectMetadataItem.labelPlural,
@@ -0,0 +1,57 @@
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFolder } from 'twenty-ui/display';
import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
export const useAddFolderToNavigationMenu = () => {
const { t } = useLingui();
const { addFolderToDraft } = useAddFolderToNavigationMenuDraft();
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetRecoilState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInCommandMenu } =
useOpenNavigationMenuItemInCommandMenu();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const currentDraft = isDefined(navigationMenuItemsDraft)
? navigationMenuItemsDraft
: workspaceNavigationMenuItems;
const handleAddFolder = () => {
const targetFolderId = addMenuItemInsertionContext?.targetFolderId ?? null;
const targetIndex = addMenuItemInsertionContext?.targetIndex;
const itemId = addFolderToDraft(
t`New folder`,
currentDraft,
targetFolderId,
targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(itemId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit folder`,
pageIcon: IconFolder,
focusTitleInput: true,
});
};
return { handleAddFolder };
};
@@ -0,0 +1,58 @@
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconLink } from 'twenty-ui/display';
import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
export const useAddLinkToNavigationMenu = () => {
const { t } = useLingui();
const { addLinkToDraft } = useAddLinkToNavigationMenuDraft();
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetRecoilState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInCommandMenu } =
useOpenNavigationMenuItemInCommandMenu();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const currentDraft = isDefined(navigationMenuItemsDraft)
? navigationMenuItemsDraft
: workspaceNavigationMenuItems;
const handleAddLink = () => {
const targetFolderId = addMenuItemInsertionContext?.targetFolderId ?? null;
const targetIndex = addMenuItemInsertionContext?.targetIndex;
const itemId = addLinkToDraft(
t`Link label`,
'www.example.com',
currentDraft,
targetFolderId,
targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(itemId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit link`,
pageIcon: IconLink,
focusTitleInput: true,
});
};
return { handleAddLink };
};
@@ -2,12 +2,12 @@ import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useNavigationMenuItemEditSubView } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { useDraftNavigationMenuItemsAllFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsAllFolders';
import { useDraftNavigationMenuItemsWorkspaceFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsWorkspaceFolders';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { useNavigationMenuItemMoveRemove } from '@/navigation-menu-item/hooks/useNavigationMenuItemMoveRemove';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
type FolderOption = {
id: string;
@@ -38,42 +38,47 @@ const excludeCurrentFolder = <T extends { id: string }>(
? folders
: folders.filter((folder) => folder.id !== currentFolderId);
export const useFolderPickerSelectionData = () => {
type UseFolderPickerSelectionDataParams = {
onCloseSubView: () => void;
};
export const useFolderPickerSelectionData = ({
onCloseSubView,
}: UseFolderPickerSelectionDataParams) => {
const { closeCommandMenu } = useCommandMenu();
const { clearSubView } = useNavigationMenuItemEditSubView();
const { moveToFolder } = useNavigationMenuItemMoveRemove();
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const { selectedItem, selectedItemType } =
useSelectedNavigationMenuItemEditData();
const { allFolders, workspaceFolders } =
useNavigationMenuItemEditFolderData();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const selectedItemType = selectedItem?.itemType ?? null;
const { allFolders } = useDraftNavigationMenuItemsAllFolders();
const { workspaceFolders } = useDraftNavigationMenuItemsWorkspaceFolders();
const selectedFolderId =
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER
selectedItemType === NavigationMenuItemType.FOLDER
? selectedNavigationMenuItemInEditMode
: null;
const currentFolderId =
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER
selectedItemType === NavigationMenuItemType.FOLDER
? (selectedItem?.id ?? null)
: (selectedItem?.folderId ?? null);
const descendantFolderIds =
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER &&
selectedItemType === NavigationMenuItemType.FOLDER &&
isDefined(selectedFolderId)
? getDescendantFolderIds(selectedFolderId, allFolders)
: new Set<string>();
const includeNoFolderOption =
(selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER &&
(selectedItemType === NavigationMenuItemType.FOLDER &&
isDefined(selectedFolderId)) ||
(selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK &&
(selectedItemType === NavigationMenuItemType.LINK &&
isDefined(currentFolderId));
const folders =
includeNoFolderOption &&
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER &&
selectedItemType === NavigationMenuItemType.FOLDER &&
isDefined(selectedFolderId)
? allFolders.filter(
(folder) =>
@@ -89,7 +94,7 @@ export const useFolderPickerSelectionData = () => {
const handleSelectFolder = (folderId: string | null) => {
if (isDefined(selectedNavigationMenuItemInEditMode)) {
moveToFolder(selectedNavigationMenuItemInEditMode, folderId);
clearSubView();
onCloseSubView();
closeCommandMenu();
}
};
@@ -1,37 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
export const useNavigationMenuItemEditFolderData = () => {
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const { workspaceNavigationMenuItemsByFolder } =
useNavigationMenuItemsByFolder();
const currentDraft = navigationMenuItemsDraft ?? workspaceNavigationMenuItems;
const workspaceFolders = workspaceNavigationMenuItemsByFolder.map(
(folder) => ({
id: folder.id,
name: folder.folderName,
}),
);
const allFolders =
currentDraft?.filter(isNavigationMenuItemFolder).map((item) => ({
id: item.id,
name: item.name ?? 'Folder',
folderId: item.folderId ?? undefined,
})) ?? [];
return {
allFolders,
workspaceFolders,
currentDraft,
};
};
@@ -1,16 +0,0 @@
import { useState } from 'react';
export type EditSubView = 'folder-picker' | null;
export const useNavigationMenuItemEditSubView = () => {
const [editSubView, setEditSubView] = useState<EditSubView>(null);
const setFolderPicker = () => setEditSubView('folder-picker');
const clearSubView = () => setEditSubView(null);
return {
editSubView,
setFolderPicker,
clearSubView,
};
};
@@ -1,52 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { coreViewsState } from '@/views/states/coreViewState';
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
export const useSelectedNavigationMenuItemEditData = () => {
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();
const coreViews = useRecoilValue(coreViewsState);
const views = coreViews.map(convertCoreViewToView);
const { objectMetadataItems } = useObjectMetadataItems();
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find((item) => item.id === selectedNavigationMenuItemInEditMode)
: undefined;
const selectedItemType = selectedItem?.itemType ?? null;
const selectedItemObjectMetadata = selectedItem
? getObjectMetadataForNavigationMenuItem(
selectedItem,
objectMetadataItems,
views,
)
: null;
const selectedItemLabel = selectedItem
? selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER
? (selectedItem.name ?? 'Folder')
: selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK
? (selectedItem.name ?? 'Link')
: (selectedItemObjectMetadata?.labelPlural ?? '')
: null;
const processedItem =
selectedItem && selectedItem.itemType !== NAVIGATION_MENU_ITEM_TYPE.FOLDER
? selectedItem
: undefined;
return {
selectedItem,
selectedItemType,
selectedItemObjectMetadata,
selectedItemLabel,
processedItem,
};
};
@@ -0,0 +1,55 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
type GetAvailableObjectMetadataForNewSidebarItemProps = {
objectMetadataItems: ObjectMetadataItem[];
activeNonSystemObjectMetadataItems: ObjectMetadataItem[];
objectMetadataIdsWithIndexView: Set<string>;
objectMetadataIdsWithDisplayableViews: Set<string>;
};
type GetAvailableObjectMetadataForNewSidebarItemResult = {
availableObjectMetadataItems: ObjectMetadataItem[];
availableSystemObjectMetadataItems: ObjectMetadataItem[];
objectMetadataItemsWithViews: ObjectMetadataItem[];
availableSystemObjectMetadataItemsForView: ObjectMetadataItem[];
};
export const getAvailableObjectMetadataForNewSidebarItem = ({
objectMetadataItems,
activeNonSystemObjectMetadataItems,
objectMetadataIdsWithIndexView,
objectMetadataIdsWithDisplayableViews,
}: GetAvailableObjectMetadataForNewSidebarItemProps): GetAvailableObjectMetadataForNewSidebarItemResult => {
const availableObjectMetadataItems = [
...activeNonSystemObjectMetadataItems,
].sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const activeSystemObjectMetadataItems = objectMetadataItems
.filter((item) => item.isActive && item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItems =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithIndexView.has(item.id),
);
const objectMetadataItemsWithViews = objectMetadataItems
.filter(
(item) =>
item.isActive && objectMetadataIdsWithDisplayableViews.has(item.id),
)
.filter((item) => !item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItemsForView =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithDisplayableViews.has(item.id),
);
return {
availableObjectMetadataItems,
availableSystemObjectMetadataItems,
objectMetadataItemsWithViews,
availableSystemObjectMetadataItemsForView,
};
};
@@ -1,10 +1,27 @@
import { CommandMenuNavigationItemActions } from '@/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions';
const ORGANIZE_ACTIONS_WITHOUT_MOVE_TO_FOLDER: CommandMenuNavigationItemActions[] =
[
CommandMenuNavigationItemActions.MOVE_UP,
CommandMenuNavigationItemActions.MOVE_DOWN,
CommandMenuNavigationItemActions.ADD_BEFORE,
CommandMenuNavigationItemActions.ADD_AFTER,
CommandMenuNavigationItemActions.REMOVE,
];
const ORGANIZE_ACTIONS_WITH_MOVE_TO_FOLDER: CommandMenuNavigationItemActions[] =
[
CommandMenuNavigationItemActions.MOVE_UP,
CommandMenuNavigationItemActions.MOVE_DOWN,
CommandMenuNavigationItemActions.MOVE_TO_FOLDER,
CommandMenuNavigationItemActions.ADD_BEFORE,
CommandMenuNavigationItemActions.ADD_AFTER,
CommandMenuNavigationItemActions.REMOVE,
];
export const getOrganizeActionsSelectableItemIds = (
includeMoveToFolder: boolean,
) => [
'move-up',
'move-down',
...(includeMoveToFolder ? ['move-to-folder'] : []),
'add-before',
'add-after',
'remove',
];
): CommandMenuNavigationItemActions[] =>
includeMoveToFolder
? ORGANIZE_ACTIONS_WITH_MOVE_TO_FOLDER
: ORGANIZE_ACTIONS_WITHOUT_MOVE_TO_FOLDER;