Navbar drag drop using dnd kit (#18288)

This commit is contained in:
Abdul Rahman
2026-03-09 01:55:50 +05:30
committed by GitHub
parent 66d93c4d28
commit 5b28e59ca7
119 changed files with 3563 additions and 1297 deletions
@@ -1,11 +1,24 @@
import { Droppable, type DroppableProvided } from '@hello-pangea/dnd';
import { type ReactNode, useContext } from 'react';
import { lazy, Suspense, useContext, type ReactNode } from 'react';
import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/constants/AddToNavSourceDroppableId';
import { NavigationDragSourceContext } from '@/navigation-menu-item/contexts/NavigationDragSourceContext';
import type { AddToNavDroppableProvided } from '@/command-menu/components/CommandMenuAddToNavDroppableTypes';
const FALLBACK_PROVIDED: AddToNavDroppableProvided = {
innerRef: () => {},
droppableProps: {},
placeholder: null,
};
const CommandMenuAddToNavDroppableDndKit = lazy(() =>
import('@/command-menu/components/CommandMenuAddToNavDroppableDndKit').then(
(m) => ({ default: m.CommandMenuAddToNavDroppableDndKit }),
),
);
type SidePanelAddToNavigationDroppableProps = {
children: (provided: DroppableProvided) => ReactNode;
children: (provided: AddToNavDroppableProvided) => ReactNode;
};
export const SidePanelAddToNavigationDroppable = ({
@@ -15,11 +28,11 @@ export const SidePanelAddToNavigationDroppable = ({
const isDropDisabled = sourceDroppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID;
return (
<Droppable
droppableId={ADD_TO_NAV_SOURCE_DROPPABLE_ID}
isDropDisabled={isDropDisabled}
>
{(provided) => children(provided)}
</Droppable>
<Suspense fallback={children(FALLBACK_PROVIDED)}>
<CommandMenuAddToNavDroppableDndKit
children={children}
isDropDisabled={isDropDisabled}
/>
</Suspense>
);
};
@@ -1,23 +1,22 @@
import { styled } from '@linaria/react';
import { Draggable } from '@hello-pangea/dnd';
import { useLingui } from '@lingui/react/macro';
import { type ReactNode, useState } from 'react';
import { type ReactNode, lazy, Suspense, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { type IconComponent } from 'twenty-ui/display';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { AddToNavigationDragHandle } from '@/navigation-menu-item/components/AddToNavigationDragHandle';
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/states/addToNavPayloadRegistryState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
const StyledDraggableMenuItem = styled.div`
cursor: grab;
width: 100%;
&:active {
cursor: grabbing;
}
`;
const CommandMenuItemWithAddToNavigationDragDndKit = lazy(() =>
import(
'@/command-menu/components/CommandMenuItemWithAddToNavigationDragDndKit'
).then((m) => ({
default: m.CommandMenuItemWithAddToNavigationDragDndKit,
})),
);
type SidePanelItemWithAddToNavigationDragProps = {
icon?: IconComponent;
@@ -28,8 +27,25 @@ type SidePanelItemWithAddToNavigationDragProps = {
onClick: () => void;
payload: AddToNavigationDragPayload;
dragIndex?: number;
disabled?: boolean;
disableDrag?: boolean;
};
const StyledDraggableMenuItem = styled.div<{
$disabled?: boolean;
$disableDrag?: boolean;
}>`
cursor: ${({ $disabled, $disableDrag }) =>
$disabled || $disableDrag ? 'default' : 'grab'};
pointer-events: ${({ $disabled }) => ($disabled ? 'none' : 'auto')};
width: 100%;
&:active {
cursor: ${({ $disabled, $disableDrag }) =>
$disabled || $disableDrag ? 'default' : 'grabbing'};
}
`;
export const SidePanelItemWithAddToNavigationDrag = ({
icon,
customIconContent,
@@ -39,6 +55,8 @@ export const SidePanelItemWithAddToNavigationDrag = ({
onClick,
payload,
dragIndex,
disabled = false,
disableDrag = false,
}: SidePanelItemWithAddToNavigationDragProps) => {
const { t } = useLingui();
const setAddToNavPayloadRegistry = useSetAtomState(
@@ -46,7 +64,8 @@ export const SidePanelItemWithAddToNavigationDrag = ({
);
const [isHovered, setIsHovered] = useState(false);
const contextualDescription = isHovered
const showDragAffordance = !disabled && !disableDrag && isHovered;
const contextualDescription = showDragAffordance
? t`Drag to add to navbar`
: description;
@@ -55,23 +74,31 @@ export const SidePanelItemWithAddToNavigationDrag = ({
icon={icon}
customIconContent={customIconContent}
payload={payload}
isHovered={isHovered}
isHovered={showDragAffordance}
disabled={disabled}
disableDrag={disableDrag}
/>
);
const registerPayload = () => {
if (dragIndex !== undefined) {
if (!disabled && !disableDrag && isDefined(dragIndex)) {
setAddToNavPayloadRegistry((prev) => new Map(prev).set(id, payload));
}
};
const menuItemContent = (
<StyledDraggableMenuItem
$disabled={disabled}
$disableDrag={disableDrag}
onMouseEnter={() => {
setIsHovered(true);
registerPayload();
if (!disabled && !disableDrag) {
setIsHovered(true);
registerPayload();
}
}}
onMouseLeave={() => {
if (!disabled && !disableDrag) setIsHovered(false);
}}
onMouseLeave={() => setIsHovered(false)}
onMouseDown={registerPayload}
>
<CommandMenuItem
@@ -80,27 +107,22 @@ export const SidePanelItemWithAddToNavigationDrag = ({
description={contextualDescription}
id={id}
onClick={onClick}
disabled={disabled}
/>
</StyledDraggableMenuItem>
);
if (dragIndex !== undefined) {
return (
<Draggable draggableId={id} index={dragIndex} isDragDisabled={false}>
{(provided) => (
<div
ref={provided.innerRef}
// oxlint-disable-next-line react/jsx-props-no-spreading
{...provided.draggableProps}
// oxlint-disable-next-line react/jsx-props-no-spreading
{...provided.dragHandleProps}
>
{menuItemContent}
</div>
)}
</Draggable>
);
if (!isDefined(dragIndex) || disableDrag) {
return menuItemContent;
}
return menuItemContent;
return (
<Suspense fallback={menuItemContent}>
<CommandMenuItemWithAddToNavigationDragDndKit
id={id}
dragIndex={dragIndex}
menuItemContent={menuItemContent}
/>
</Suspense>
);
};
@@ -1,14 +1,14 @@
import { useLingui } from '@lingui/react/macro';
import { IconLink } from 'twenty-ui/display';
import { IconLink, IconWorld } from 'twenty-ui/display';
import { SidePanelPageInfoLayout } from '@/side-panel/components/SidePanelPageInfoLayout';
import { sidePanelPageInfoState } from '@/side-panel/states/sidePanelPageInfoState';
import { sidePanelShouldFocusTitleInputComponentState } from '@/side-panel/states/sidePanelShouldFocusTitleInputComponentState';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { LinkIconWithLinkOverlay } from '@/navigation-menu-item/components/LinkIconWithLinkOverlay';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
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 { 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';
@@ -59,8 +59,10 @@ export const SidePanelLinkInfo = () => {
return (
<SidePanelPageInfoLayout
icon={
<NavigationMenuItemStyleIcon
Icon={IconLink}
<LinkIconWithLinkOverlay
link={selectedItem.link}
LinkIcon={IconLink}
DefaultIcon={IconWorld}
color={selectedItem.color}
/>
}
@@ -8,7 +8,7 @@ import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { motion } from 'framer-motion';
import { useContext } from 'react';
import React, { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { ThemeContext } from 'twenty-ui/theme-constants';
@@ -21,11 +21,16 @@ export const SidePanelRouter = () => {
const sidePanelPage = useAtomStateValue(sidePanelPageState);
const sidePanelPageInfo = useAtomStateValue(sidePanelPageInfoState);
const sidePanelPageComponent = isDefined(sidePanelPage) ? (
SIDE_PANEL_PAGES_CONFIG.get(sidePanelPage)
) : (
<></>
);
const rawPageComponent = isDefined(sidePanelPage)
? SIDE_PANEL_PAGES_CONFIG.get(sidePanelPage)
: null;
const sidePanelPageComponent =
isDefined(rawPageComponent) && React.isValidElement(rawPageComponent)
? React.cloneElement(rawPageComponent, {
key: sidePanelPageInfo.instanceId,
})
: rawPageComponent;
const { theme } = useContext(ThemeContext);
@@ -3,22 +3,24 @@ import { isNonEmptyString } from '@sniptt/guards';
import { useState } from 'react';
import { getAbsoluteUrl } from 'twenty-shared/utils';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
import { extractDomainFromUrl } from '@/navigation-menu-item/utils/extractDomainFromUrl';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelEditColorOption } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditColorOption';
import {
type OrganizeActionsProps,
SidePanelEditOrganizeActions,
} from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOrganizeActions';
import { SidePanelEditOwnerSection } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOwnerSection';
import { getOrganizeActionsSelectableItemIds } from '@/side-panel/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor';
import { TextInput } from '@/ui/input/components/TextInput';
type SidePanelEditLinkItemViewProps = OrganizeActionsProps & {
selectedItem: ProcessedNavigationMenuItem;
onUpdateLink: (linkId: string, link: string) => void;
onUpdateLink: (
linkId: string,
updates: { link?: string; name?: string },
) => void;
onOpenFolderPicker: () => void;
};
@@ -36,28 +38,49 @@ export const SidePanelEditLinkItemView = ({
}: SidePanelEditLinkItemViewProps) => {
const { t } = useLingui();
const [urlEditInput, setUrlEditInput] = useState('');
const [lastAutoSetName, setLastAutoSetName] = useState<string | null>(null);
const defaultLabel = t`Link label`;
const selectableItemIds = getOrganizeActionsSelectableItemIds(true);
const currentName = selectedItem.name ?? defaultLabel;
const currentDomain = selectedItem.link
? extractDomainFromUrl(getAbsoluteUrl(selectedItem.link))
: undefined;
const canAutoUpdateName =
currentName === defaultLabel ||
currentName === currentDomain ||
currentName === lastAutoSetName;
const handleUrlChange = (value: string) => {
setUrlEditInput(value);
if (!canAutoUpdateName) return;
const trimmed = value.trim();
if (!isNonEmptyString(trimmed)) return;
const domain = extractDomainFromUrl(getAbsoluteUrl(trimmed));
if (domain !== undefined) {
setLastAutoSetName(domain);
onUpdateLink(selectedItem.id, { name: domain });
}
};
const handleUrlBlur = (event: React.FocusEvent<HTMLInputElement>) => {
const value = event.target.value.trim();
if (isNonEmptyString(value)) {
onUpdateLink(selectedItem.id, { link: getAbsoluteUrl(value) });
setUrlEditInput('');
}
};
return (
<SidePanelList commandGroups={[]} selectableItemIds={selectableItemIds}>
<SidePanelGroup heading={t`Customize`}>
<SidePanelEditColorOption
navigationMenuItemId={selectedItem.id}
color={parseThemeColor(selectedItem.color)}
/>
<TextInput
fullWidth
placeholder="www.google.com"
value={urlEditInput || selectedItem.link}
onChange={(value) => setUrlEditInput(value)}
onBlur={(event) => {
const value = event.target.value.trim();
if (isNonEmptyString(value)) {
onUpdateLink(selectedItem.id, getAbsoluteUrl(value));
setUrlEditInput('');
}
}}
onChange={handleUrlChange}
onBlur={handleUrlBlur}
/>
</SidePanelGroup>
<SidePanelEditOrganizeActions
@@ -1,14 +1,4 @@
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelEditColorOption } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditColorOption';
import { SidePanelEditFolderPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubView';
import { SidePanelEditLinkItemView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditLinkItemView';
import { SidePanelEditObjectViewBase } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditObjectViewBase';
import { SidePanelEditOrganizeActions } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOrganizeActions';
import { SidePanelEditOwnerSection } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOwnerSection';
import { useNavigationMenuItemEditOrganizeActions } from '@/side-panel/pages/navigation-menu-item/hooks/useNavigationMenuItemEditOrganizeActions';
import { getOrganizeActionsSelectableItemIds } from '@/side-panel/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useOpenAddItemToFolderPage } from '@/navigation-menu-item/hooks/useOpenAddItemToFolderPage';
@@ -18,6 +8,16 @@ import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigatio
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelEditColorOption } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditColorOption';
import { SidePanelEditFolderPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditFolderPickerSubView';
import { SidePanelEditLinkItemView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditLinkItemView';
import { SidePanelEditObjectViewBase } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditObjectViewBase';
import { SidePanelEditOrganizeActions } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOrganizeActions';
import { SidePanelEditOwnerSection } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOwnerSection';
import { useNavigationMenuItemEditOrganizeActions } from '@/side-panel/pages/navigation-menu-item/hooks/useNavigationMenuItemEditOrganizeActions';
import { getOrganizeActionsSelectableItemIds } from '@/side-panel/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { ViewKey } from '@/views/types/ViewKey';
@@ -132,7 +132,9 @@ export const SidePanelNavigationMenuItemEditPage = () => {
<SidePanelEditLinkItemView
key={selectedItem.id}
selectedItem={selectedItem}
onUpdateLink={(linkId, link) => updateLinkInDraft(linkId, { link })}
onUpdateLink={(linkId, updates) =>
updateLinkInDraft(linkId, updates)
}
onOpenFolderPicker={openFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
@@ -1,4 +1,5 @@
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import {
Avatar,
IconBuildingSkyscraper,
@@ -6,20 +7,20 @@ import {
IconLink,
IconTable,
} from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelAddToNavigationDraggablePlaceholder } from '@/side-panel/components/SidePanelAddToNavigationDraggablePlaceholder';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { useAddFolderToNavigationMenu } from '@/side-panel/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu';
import { useAddLinkToNavigationMenu } from '@/side-panel/pages/navigation-menu-item/hooks/useAddLinkToNavigationMenu';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useContext } from 'react';
import { ThemeContext } from 'twenty-ui/theme-constants';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
type SidePanelNewSidebarItemMainMenuProps = {
onSelectObject: () => void;
@@ -27,104 +28,133 @@ type SidePanelNewSidebarItemMainMenuProps = {
onSelectRecord: () => void;
};
const MAIN_MENU_ITEM_TYPES = [
NavigationMenuItemType.OBJECT,
NavigationMenuItemType.VIEW,
NavigationMenuItemType.RECORD,
NavigationMenuItemType.FOLDER,
NavigationMenuItemType.LINK,
] as const;
export const SidePanelNewSidebarItemMainMenu = ({
onSelectObject,
onSelectView,
onSelectRecord,
}: SidePanelNewSidebarItemMainMenuProps) => {
const { t } = useLingui();
const { theme } = useContext(ThemeContext);
const addMenuItemInsertionContext = useAtomStateValue(
addMenuItemInsertionContextState,
);
const { handleAddFolder } = useAddFolderToNavigationMenu();
const { handleAddLink } = useAddLinkToNavigationMenu();
const isAddingToFolder = isDefined(
addMenuItemInsertionContext?.targetFolderId,
);
const isDragDisabled = addMenuItemInsertionContext?.disableDrag === true;
const selectableItemIds = isAddingToFolder
? MAIN_MENU_ITEM_TYPES.filter(
(type) => type !== NavigationMenuItemType.FOLDER,
)
: [...MAIN_MENU_ITEM_TYPES];
return (
<SidePanelAddToNavigationDroppable>
{({ innerRef, droppableProps, placeholder }) => (
<SidePanelList
commandGroups={[]}
selectableItemIds={['object', 'view', 'record', 'folder', 'link']}
>
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
<SidePanelList commandGroups={[]} selectableItemIds={selectableItemIds}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<div ref={innerRef} {...droppableProps}>
<SidePanelGroup heading={t`Data`}>
<SidePanelAddToNavigationDraggablePlaceholder index={0}>
<SelectableListItem itemId="object" onEnter={onSelectObject}>
<CommandMenuItem
Icon={() => (
<NavigationMenuItemStyleIcon
Icon={IconBuildingSkyscraper}
color="blue"
/>
)}
label={t`Object`}
id="object"
hasSubMenu={true}
onClick={onSelectObject}
/>
</SelectableListItem>
</SidePanelAddToNavigationDraggablePlaceholder>
<SidePanelAddToNavigationDraggablePlaceholder index={1}>
<SelectableListItem itemId="view" onEnter={onSelectView}>
<CommandMenuItem
Icon={() => (
<NavigationMenuItemStyleIcon
Icon={IconTable}
color="gray"
/>
)}
label={t`View`}
id="view"
hasSubMenu={true}
onClick={onSelectView}
/>
</SelectableListItem>
</SidePanelAddToNavigationDraggablePlaceholder>
<SidePanelAddToNavigationDraggablePlaceholder index={2}>
<SelectableListItem itemId="record" onEnter={onSelectRecord}>
<CommandMenuItem
Icon={() => (
<Avatar
placeholder="L"
type="rounded"
backgroundColor={theme.color.green4}
/>
)}
label={t`Record`}
id="record"
hasSubMenu={true}
onClick={onSelectRecord}
/>
</SelectableListItem>
</SidePanelAddToNavigationDraggablePlaceholder>
<SelectableListItem
itemId={NavigationMenuItemType.OBJECT}
onEnter={onSelectObject}
>
<CommandMenuItem
Icon={() => (
<NavigationMenuItemStyleIcon
Icon={IconBuildingSkyscraper}
color="blue"
/>
)}
label={t`Object`}
id={NavigationMenuItemType.OBJECT}
hasSubMenu={true}
onClick={onSelectObject}
/>
</SelectableListItem>
<SelectableListItem
itemId={NavigationMenuItemType.VIEW}
onEnter={onSelectView}
>
<CommandMenuItem
Icon={() => (
<NavigationMenuItemStyleIcon
Icon={IconTable}
color="gray"
/>
)}
label={t`View`}
id={NavigationMenuItemType.VIEW}
hasSubMenu={true}
onClick={onSelectView}
/>
</SelectableListItem>
<SelectableListItem
itemId={NavigationMenuItemType.RECORD}
onEnter={onSelectRecord}
>
<CommandMenuItem
Icon={() => (
<Avatar
placeholder="L"
type="rounded"
backgroundColor={themeCssVariables.color.green4}
/>
)}
label={t`Record`}
id={NavigationMenuItemType.RECORD}
hasSubMenu={true}
onClick={onSelectRecord}
/>
</SelectableListItem>
</SidePanelGroup>
<SidePanelGroup heading={t`Other`}>
<SelectableListItem itemId="folder" onEnter={handleAddFolder}>
<SelectableListItem
itemId={NavigationMenuItemType.FOLDER}
onEnter={isAddingToFolder ? undefined : handleAddFolder}
>
<SidePanelItemWithAddToNavigationDrag
icon={IconFolder}
label={t`Folder`}
id="folder"
id={NavigationMenuItemType.FOLDER}
onClick={handleAddFolder}
dragIndex={3}
dragIndex={isDragDisabled ? undefined : 3}
payload={{
type: NavigationMenuItemType.FOLDER,
folderId: 'new',
name: t`New folder`,
}}
disabled={isAddingToFolder}
disableDrag={isDragDisabled}
/>
</SelectableListItem>
<SelectableListItem itemId="link" onEnter={handleAddLink}>
<SelectableListItem
itemId={NavigationMenuItemType.LINK}
onEnter={handleAddLink}
>
<SidePanelItemWithAddToNavigationDrag
icon={IconLink}
label={t`Link`}
id="link"
id={NavigationMenuItemType.LINK}
onClick={handleAddLink}
dragIndex={4}
dragIndex={isDragDisabled ? undefined : 4}
payload={{
type: NavigationMenuItemType.LINK,
linkId: 'new',
name: t`Link label`,
link: 'https://www.example.com',
}}
disableDrag={isDragDisabled}
/>
</SelectableListItem>
</SidePanelGroup>
@@ -1,21 +1,22 @@
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { SidePanelObjectPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelObjectPickerSubView';
import { SidePanelSystemObjectPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelSystemObjectPickerSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/side-panel/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem';
import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft';
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInSidePanel';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SidePanelObjectPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelObjectPickerSubView';
import { SidePanelSystemObjectPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelSystemObjectPickerSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/side-panel/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { ViewKey } from '@/views/types/ViewKey';
import { useIcons } from 'twenty-ui/display';
type SidePanelNewSidebarItemObjectFlowProps = {
onBack: () => void;
@@ -25,7 +26,7 @@ export const SidePanelNewSidebarItemObjectFlow = ({
onBack,
}: SidePanelNewSidebarItemObjectFlowProps) => {
const { t } = useLingui();
const { closeSidePanelMenu } = useSidePanelMenu();
const { getIcon } = useIcons();
const [objectSearchInput, setObjectSearchInput] = useState('');
const [systemObjectSearchInput, setSystemObjectSearchInput] = useState('');
const [isInSystemPicker, setIsInSystemPicker] = useState(false);
@@ -33,6 +34,8 @@ export const SidePanelNewSidebarItemObjectFlow = ({
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const { addObjectToDraft } = useAddObjectToNavigationMenuDraft();
const { openNavigationMenuItemInSidePanel } =
useOpenNavigationMenuItemInSidePanel();
const { activeNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const addMenuItemInsertionContext = useAtomStateValue(
@@ -68,7 +71,7 @@ export const SidePanelNewSidebarItemObjectFlow = ({
if (objectMetadataIdsInWorkspace.has(objectMetadataItem.id)) {
return;
}
addObjectToDraft(
const itemId = addObjectToDraft(
objectMetadataItem.id,
defaultViewId,
currentDraft,
@@ -77,7 +80,11 @@ export const SidePanelNewSidebarItemObjectFlow = ({
getStandardObjectIconColor(objectMetadataItem.nameSingular),
);
setAddMenuItemInsertionContext(null);
closeSidePanelMenu();
openNavigationMenuItemInSidePanel({
itemId,
pageTitle: objectMetadataItem.labelSingular,
pageIcon: getIcon(objectMetadataItem.icon),
});
};
const handleBackToObjectList = () => {
@@ -85,6 +92,8 @@ export const SidePanelNewSidebarItemObjectFlow = ({
setSystemObjectSearchInput('');
};
const disableDrag = addMenuItemInsertionContext?.disableDrag === true;
if (isInSystemPicker) {
return (
<SidePanelSystemObjectPickerSubView
@@ -96,6 +105,7 @@ export const SidePanelNewSidebarItemObjectFlow = ({
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
emptyNoResultsText={t`All system objects are already in the sidebar`}
disableDrag={disableDrag}
/>
);
}
@@ -110,6 +120,7 @@ export const SidePanelNewSidebarItemObjectFlow = ({
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
disableDrag={disableDrag}
/>
);
};
@@ -1,14 +1,21 @@
import { useState } from 'react';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { SidePanelNewSidebarItemMainMenu } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemMainMenu';
import { SidePanelNewSidebarItemObjectFlow } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemObjectFlow';
import { SidePanelNewSidebarItemRecordSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemRecordSubView';
import { SidePanelNewSidebarItemViewFlow } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemViewFlow';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
type SelectedOption = 'object' | 'record' | 'view' | null;
export const SidePanelNewSidebarItemPage = () => {
const [selectedOption, setSelectedOption] = useState<SelectedOption>(null);
const addMenuItemInsertionContext = useAtomStateValue(
addMenuItemInsertionContextState,
);
const disableDrag = isDefined(addMenuItemInsertionContext);
const handleBackToMain = () => {
setSelectedOption(null);
@@ -20,7 +27,12 @@ export const SidePanelNewSidebarItemPage = () => {
case 'object':
return <SidePanelNewSidebarItemObjectFlow onBack={handleBackToMain} />;
case 'record':
return <SidePanelNewSidebarItemRecordSubView onBack={handleBackToMain} />;
return (
<SidePanelNewSidebarItemRecordSubView
onBack={handleBackToMain}
disableDrag={disableDrag}
/>
);
default:
return (
<SidePanelNewSidebarItemMainMenu
@@ -1,17 +1,17 @@
import { Avatar } from 'twenty-ui/display';
import { Avatar, IconFolder, useIcons } from 'twenty-ui/display';
import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
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 { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInSidePanel';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { CoreObjectNameSingular } from 'twenty-shared/types';
type SearchRecord = {
recordId: string;
@@ -23,13 +23,15 @@ type SearchRecord = {
type SidePanelNewSidebarItemRecordItemProps = {
record: SearchRecord;
dragIndex?: number;
disableDrag?: boolean;
};
export const SidePanelNewSidebarItemRecordItem = ({
record,
dragIndex,
disableDrag = false,
}: SidePanelNewSidebarItemRecordItemProps) => {
const { closeSidePanelMenu } = useSidePanelMenu();
const { getIcon } = useIcons();
const { addRecordToDraft } = useAddRecordToNavigationMenuDraft();
const { currentDraft } = useDraftNavigationMenuItems();
const addMenuItemInsertionContext = useAtomStateValue(
@@ -38,6 +40,8 @@ export const SidePanelNewSidebarItemRecordItem = ({
const setAddMenuItemInsertionContext = useSetAtomState(
addMenuItemInsertionContextState,
);
const { openNavigationMenuItemInSidePanel } =
useOpenNavigationMenuItemInSidePanel();
const { objectMetadataItems } = useObjectMetadataItems();
const objectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === record.objectNameSingular,
@@ -52,7 +56,7 @@ export const SidePanelNewSidebarItemRecordItem = ({
};
const handleSelectRecord = () => {
addRecordToDraft(
const itemId = addRecordToDraft(
{
recordId: record.recordId,
objectNameSingular: record.objectNameSingular,
@@ -64,7 +68,13 @@ export const SidePanelNewSidebarItemRecordItem = ({
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
closeSidePanelMenu();
openNavigationMenuItemInSidePanel({
itemId,
pageTitle: record.label,
pageIcon: objectMetadataItem
? getIcon(objectMetadataItem.icon)
: IconFolder,
});
};
return (
@@ -89,6 +99,7 @@ export const SidePanelNewSidebarItemRecordItem = ({
id={record.recordId}
onClick={handleSelectRecord}
dragIndex={dragIndex}
disableDrag={disableDrag}
payload={recordPayload}
/>
</SelectableListItem>
@@ -3,17 +3,19 @@ import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useDebounce } from 'use-debounce';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelSubViewWithSearch } from '@/side-panel/components/SidePanelSubViewWithSearch';
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
import { SidePanelNewSidebarItemRecordItem } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemRecordItem';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelSubViewWithSearch } from '@/side-panel/components/SidePanelSubViewWithSearch';
import { SidePanelNewSidebarItemRecordItem } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemRecordItem';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSearchQuery } from '~/generated/graphql';
type SearchRecordBase = {
@@ -25,12 +27,19 @@ type SearchRecordBase = {
type SidePanelNewSidebarItemRecordSubViewProps = {
onBack: () => void;
disableDrag?: boolean;
};
export const SidePanelNewSidebarItemRecordSubView = ({
onBack,
disableDrag: disableDragProp,
}: SidePanelNewSidebarItemRecordSubViewProps) => {
const { t } = useLingui();
const addMenuItemInsertionContext = useAtomStateValue(
addMenuItemInsertionContextState,
);
const disableDrag =
disableDragProp ?? addMenuItemInsertionContext?.disableDrag === true;
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const [recordSearchInput, setRecordSearchInput] = useState('');
@@ -105,7 +114,8 @@ export const SidePanelNewSidebarItemRecordSubView = ({
<SidePanelNewSidebarItemRecordItem
key={record.recordId}
record={record}
dragIndex={index}
dragIndex={disableDrag ? undefined : index}
disableDrag={disableDrag}
/>
))}
</SidePanelGroup>
@@ -1,15 +1,16 @@
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { SidePanelNewSidebarItemViewObjectPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemViewObjectPickerSubView';
import { SidePanelNewSidebarItemViewPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemViewPickerSubView';
import { SidePanelNewSidebarItemViewSystemSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemViewSystemSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/side-panel/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 { SidePanelNewSidebarItemViewObjectPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemViewObjectPickerSubView';
import { SidePanelNewSidebarItemViewPickerSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemViewPickerSubView';
import { SidePanelNewSidebarItemViewSystemSubView } from '@/side-panel/pages/navigation-menu-item/components/SidePanelNewSidebarItemViewSystemSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/side-panel/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem';
import { ViewKey } from '@/views/types/ViewKey';
import { ViewType } from '@/views/types/ViewType';
type SidePanelNewSidebarItemViewFlowProps = {
onBack: () => void;
@@ -33,7 +34,10 @@ export const SidePanelNewSidebarItemViewFlow = ({
const objectMetadataIdsWithDisplayableViews = new Set(
views
.filter((view) => view.key !== ViewKey.Index)
.filter(
(view) =>
view.key !== ViewKey.Index && view.type !== ViewType.FieldsWidget,
)
.map((view) => view.objectMetadataId),
);
@@ -1,27 +1,29 @@
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelSubViewWithSearch } from '@/side-panel/components/SidePanelSubViewWithSearch';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { useSidePanelFilteredPickerItems } from '@/side-panel/hooks/useSidePanelFilteredPickerItems';
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInSidePanel';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelSubViewWithSearch } from '@/side-panel/components/SidePanelSubViewWithSearch';
import { useSidePanelFilteredPickerItems } from '@/side-panel/hooks/useSidePanelFilteredPickerItems';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { type View } from '@/views/types/View';
import { ViewKey } from '@/views/types/ViewKey';
import { ViewType } from '@/views/types/ViewType';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { ViewVisibility } from '~/generated-metadata/graphql';
type SidePanelNewSidebarItemViewPickerSubViewProps = {
selectedObjectMetadataIdForView: string;
@@ -35,7 +37,6 @@ export const SidePanelNewSidebarItemViewPickerSubView = ({
const { t } = useLingui();
const { getIcon } = useIcons();
const [searchValue, setSearchValue] = useState('');
const { closeSidePanelMenu } = useSidePanelMenu();
const { addViewToDraft } = useAddViewToNavigationMenuDraft();
const { currentDraft } = useDraftNavigationMenuItems();
const addMenuItemInsertionContext = useAtomStateValue(
@@ -44,6 +45,8 @@ export const SidePanelNewSidebarItemViewPickerSubView = ({
const setAddMenuItemInsertionContext = useSetAtomState(
addMenuItemInsertionContextState,
);
const { openNavigationMenuItemInSidePanel } =
useOpenNavigationMenuItemInSidePanel();
const { objectMetadataItems } = useObjectMetadataItems();
const { views } = useNavigationMenuObjectMetadataFromDraft(currentDraft);
@@ -51,7 +54,9 @@ export const SidePanelNewSidebarItemViewPickerSubView = ({
.filter(
(view) =>
view.objectMetadataId === selectedObjectMetadataIdForView &&
view.key !== ViewKey.Index,
view.key !== ViewKey.Index &&
view.type !== ViewType.FieldsWidget &&
view.visibility === ViewVisibility.WORKSPACE,
)
.sort((a, b) => a.position - b.position);
@@ -75,8 +80,10 @@ export const SidePanelNewSidebarItemViewPickerSubView = ({
? t`No results found`
: t`No custom views available`;
const isDragDisabled = addMenuItemInsertionContext?.disableDrag === true;
const handleSelectView = (view: View) => {
addViewToDraft(
const itemId = addViewToDraft(
view.id,
currentDraft,
addMenuItemInsertionContext?.targetFolderId ?? null,
@@ -86,7 +93,11 @@ export const SidePanelNewSidebarItemViewPickerSubView = ({
: undefined,
);
setAddMenuItemInsertionContext(null);
closeSidePanelMenu();
openNavigationMenuItemInSidePanel({
itemId,
pageTitle: view.name,
pageIcon: getIcon(view.icon),
});
};
return (
@@ -136,7 +147,8 @@ export const SidePanelNewSidebarItemViewPickerSubView = ({
label={view.name}
id={view.id}
onClick={() => handleSelectView(view)}
dragIndex={index}
dragIndex={isDragDisabled ? undefined : index}
disableDrag={isDragDisabled}
payload={{
type: NavigationMenuItemType.VIEW,
viewId: view.id,
@@ -3,13 +3,13 @@ import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SidePanelItemWithAddToNavigationDrag } from '@/side-panel/components/SidePanelItemWithAddToNavigationDrag';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector';
@@ -22,6 +22,7 @@ type SidePanelObjectMenuItemProps = {
) => void;
variant: 'add' | 'edit';
dragIndex?: number;
disableDrag?: boolean;
};
export const SidePanelObjectMenuItem = ({
@@ -29,6 +30,7 @@ export const SidePanelObjectMenuItem = ({
onSelect,
variant,
dragIndex,
disableDrag = false,
}: SidePanelObjectMenuItemProps) => {
const { t } = useLingui();
const { getIcon } = useIcons();
@@ -66,11 +68,13 @@ export const SidePanelObjectMenuItem = ({
id={objectMetadataItem.id}
onClick={handleClick}
dragIndex={dragIndex}
disableDrag={disableDrag}
payload={{
type: NavigationMenuItemType.OBJECT,
objectMetadataId: objectMetadataItem.id,
defaultViewId: defaultViewId ?? '',
label: objectMetadataItem.labelPlural,
iconColor,
}}
/>
) : (
@@ -2,10 +2,10 @@ import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { SidePanelObjectMenuItem } from '@/side-panel/pages/navigation-menu-item/components/SidePanelObjectMenuItem';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SidePanelObjectMenuItem } from '@/side-panel/pages/navigation-menu-item/components/SidePanelObjectMenuItem';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
type SidePanelObjectPickerItemProps = {
@@ -18,6 +18,7 @@ type SidePanelObjectPickerItemProps = {
) => void;
objectMenuItemVariant?: 'add' | 'edit';
dragIndex?: number;
disableDrag?: boolean;
};
export const SidePanelObjectPickerItem = ({
@@ -27,6 +28,7 @@ export const SidePanelObjectPickerItem = ({
onChangeObject,
objectMenuItemVariant = 'edit',
dragIndex,
disableDrag = false,
}: SidePanelObjectPickerItemProps) => {
const { getIcon } = useIcons();
@@ -59,6 +61,7 @@ export const SidePanelObjectPickerItem = ({
onSelect={onChangeObject}
variant={objectMenuItemVariant}
dragIndex={dragIndex}
disableDrag={disableDrag}
/>
);
};
@@ -1,16 +1,15 @@
import { useLingui } from '@lingui/react/macro';
import { IconSettings } from 'twenty-ui/display';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelAddToNavigationDraggablePlaceholder } from '@/side-panel/components/SidePanelAddToNavigationDraggablePlaceholder';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelSubViewWithSearch } from '@/side-panel/components/SidePanelSubViewWithSearch';
import { useSidePanelFilteredPickerItems } from '@/side-panel/hooks/useSidePanelFilteredPickerItems';
import { SidePanelObjectPickerItem } from '@/side-panel/pages/navigation-menu-item/components/SidePanelObjectPickerItem';
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
type SidePanelObjectPickerSubViewProps = {
@@ -27,6 +26,7 @@ type SidePanelObjectPickerSubViewProps = {
) => void;
objectMenuItemVariant?: 'add' | 'edit';
emptyNoResultsText?: string;
disableDrag?: boolean;
};
export const SidePanelObjectPickerSubView = ({
@@ -40,6 +40,7 @@ export const SidePanelObjectPickerSubView = ({
onChangeObject,
objectMenuItemVariant = 'edit',
emptyNoResultsText,
disableDrag = false,
}: SidePanelObjectPickerSubViewProps) => {
const { t } = useLingui();
const { filteredItems, selectableItemIds, isEmpty, hasSearchQuery } =
@@ -66,34 +67,19 @@ export const SidePanelObjectPickerSubView = ({
onSelectObjectForViewEdit={onSelectObjectForViewEdit}
onChangeObject={onChangeObject}
objectMenuItemVariant={objectMenuItemVariant}
dragIndex={isAddVariant ? index : undefined}
dragIndex={isAddVariant && !disableDrag ? index : undefined}
disableDrag={disableDrag}
/>
))}
{isAddVariant ? (
<SidePanelAddToNavigationDraggablePlaceholder
index={filteredItems.length}
>
<SelectableListItem itemId="system" onEnter={onOpenSystemPicker}>
<CommandMenuItem
Icon={() => <NavigationMenuItemStyleIcon Icon={IconSettings} />}
label={t`System objects`}
id="system"
hasSubMenu
onClick={onOpenSystemPicker}
/>
</SelectableListItem>
</SidePanelAddToNavigationDraggablePlaceholder>
) : (
<SelectableListItem itemId="system" onEnter={onOpenSystemPicker}>
<CommandMenuItem
Icon={() => <NavigationMenuItemStyleIcon Icon={IconSettings} />}
label={t`System objects`}
id="system"
hasSubMenu
onClick={onOpenSystemPicker}
/>
</SelectableListItem>
)}
<SelectableListItem itemId="system" onEnter={onOpenSystemPicker}>
<CommandMenuItem
Icon={() => <NavigationMenuItemStyleIcon Icon={IconSettings} />}
label={t`System objects`}
id="system"
hasSubMenu
onClick={onOpenSystemPicker}
/>
</SelectableListItem>
</SidePanelGroup>
);
@@ -1,11 +1,12 @@
import { useLingui } from '@lingui/react/macro';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SidePanelAddToNavigationDroppable } from '@/side-panel/components/SidePanelAddToNavigationDroppable';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { SidePanelObjectPickerItem } from '@/side-panel/pages/navigation-menu-item/components/SidePanelObjectPickerItem';
import { SidePanelSubViewWithSearch } from '@/side-panel/components/SidePanelSubViewWithSearch';
import { useSidePanelFilteredPickerItems } from '@/side-panel/hooks/useSidePanelFilteredPickerItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SidePanelObjectPickerItem } from '@/side-panel/pages/navigation-menu-item/components/SidePanelObjectPickerItem';
type SidePanelSystemObjectPickerSubViewProps = {
systemObjects: ObjectMetadataItem[];
@@ -20,6 +21,7 @@ type SidePanelSystemObjectPickerSubViewProps = {
) => void;
objectMenuItemVariant?: 'add' | 'edit';
emptyNoResultsText?: string;
disableDrag?: boolean;
};
export const SidePanelSystemObjectPickerSubView = ({
@@ -32,6 +34,7 @@ export const SidePanelSystemObjectPickerSubView = ({
onChangeObject,
objectMenuItemVariant = 'edit',
emptyNoResultsText,
disableDrag = false,
}: SidePanelSystemObjectPickerSubViewProps) => {
const { t } = useLingui();
const { filteredItems, selectableItemIds, isEmpty, hasSearchQuery } =
@@ -45,6 +48,25 @@ export const SidePanelSystemObjectPickerSubView = ({
? t`No results found`
: (emptyNoResultsText ?? t`No system objects available`);
const isAddVariant = objectMenuItemVariant === 'add';
const listContent = (
<SidePanelGroup heading={t`System objects`}>
{filteredItems.map((objectMetadataItem, index) => (
<SidePanelObjectPickerItem
key={objectMetadataItem.id}
objectMetadataItem={objectMetadataItem}
isViewItem={isViewItem}
onSelectObjectForViewEdit={onSelectObjectForViewEdit}
onChangeObject={onChangeObject}
objectMenuItemVariant={objectMenuItemVariant}
dragIndex={isAddVariant && !disableDrag ? index : undefined}
disableDrag={disableDrag}
/>
))}
</SidePanelGroup>
);
return (
<SidePanelSubViewWithSearch
backBarTitle={t`System objects`}
@@ -53,25 +75,33 @@ export const SidePanelSystemObjectPickerSubView = ({
searchValue={searchValue}
onSearchChange={onSearchChange}
>
<SidePanelList
commandGroups={[]}
selectableItemIds={selectableItemIds}
noResults={isEmpty}
noResultsText={noResultsText}
>
<SidePanelGroup heading={t`System objects`}>
{filteredItems.map((objectMetadataItem) => (
<SidePanelObjectPickerItem
key={objectMetadataItem.id}
objectMetadataItem={objectMetadataItem}
isViewItem={isViewItem}
onSelectObjectForViewEdit={onSelectObjectForViewEdit}
onChangeObject={onChangeObject}
objectMenuItemVariant={objectMenuItemVariant}
/>
))}
</SidePanelGroup>
</SidePanelList>
{isAddVariant ? (
<SidePanelAddToNavigationDroppable>
{({ innerRef, droppableProps, placeholder }) => (
<SidePanelList
commandGroups={[]}
selectableItemIds={selectableItemIds}
noResults={isEmpty}
noResultsText={noResultsText}
>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<div ref={innerRef} {...droppableProps}>
{listContent}
{placeholder}
</div>
</SidePanelList>
)}
</SidePanelAddToNavigationDroppable>
) : (
<SidePanelList
commandGroups={[]}
selectableItemIds={selectableItemIds}
noResults={isEmpty}
noResultsText={noResultsText}
>
{listContent}
</SidePanelList>
)}
</SidePanelSubViewWithSearch>
);
};
@@ -7,7 +7,6 @@ import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/u
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInSidePanel';
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 { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
@@ -18,9 +17,6 @@ export const useAddFolderToNavigationMenu = () => {
const navigationMenuItemsDraft = useAtomStateValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetAtomState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInSidePanel } =
useOpenNavigationMenuItemInSidePanel();
const addMenuItemInsertionContext = useAtomStateValue(
@@ -46,8 +42,8 @@ export const useAddFolderToNavigationMenu = () => {
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(itemId);
openNavigationMenuItemInSidePanel({
itemId,
pageTitle: t`Edit folder`,
pageIcon: IconFolder,
focusTitleInput: true,
@@ -7,7 +7,6 @@ import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/u
import { useOpenNavigationMenuItemInSidePanel } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInSidePanel';
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 { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
@@ -18,9 +17,6 @@ export const useAddLinkToNavigationMenu = () => {
const navigationMenuItemsDraft = useAtomStateValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetAtomState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInSidePanel } =
useOpenNavigationMenuItemInSidePanel();
const addMenuItemInsertionContext = useAtomStateValue(
@@ -47,8 +43,8 @@ export const useAddLinkToNavigationMenu = () => {
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(itemId);
openNavigationMenuItemInSidePanel({
itemId,
pageTitle: t`Edit link`,
pageIcon: IconLink,
focusTitleInput: true,
@@ -41,6 +41,7 @@ const getAddMenuItemInsertionContext = (
return {
targetFolderId,
targetIndex: selectedIndexInFolder + offset,
disableDrag: true,
};
};
@@ -20,9 +20,9 @@ export const getAvailableObjectMetadataForNewSidebarItem = ({
objectMetadataIdsWithIndexView,
objectMetadataIdsWithDisplayableViews,
}: GetAvailableObjectMetadataForNewSidebarItemProps): GetAvailableObjectMetadataForNewSidebarItemResult => {
const availableObjectMetadataItems = [
...activeNonSystemObjectMetadataItems,
].sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableObjectMetadataItems = activeNonSystemObjectMetadataItems
.filter((item) => objectMetadataIdsWithIndexView.has(item.id))
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const activeSystemObjectMetadataItems = objectMetadataItems
.filter((item) => item.isActive && item.isSystem)