diff --git a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx
index 7b407eaee6..9c6349a52d 100644
--- a/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/components/RecordIndexCommandMenu.tsx
@@ -1,8 +1,7 @@
-import { PinnedCommandMenuItemButtons } from '@/command-menu-item/display/components/PinnedCommandMenuItemButtons';
import { RecordIndexCommandMenuDropdown } from '@/command-menu-item/components/RecordIndexCommandMenuDropdown';
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
+import { PinnedCommandMenuItemButtons } from '@/command-menu-item/display/components/PinnedCommandMenuItemButtons';
import { CommandMenuItemEditButton } from '@/command-menu-item/edit/components/CommandMenuItemEditButton';
-import { PinnedCommandMenuItemButtonsEditMode } from '@/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
@@ -21,23 +20,18 @@ export const RecordIndexCommandMenu = () => {
isLayoutCustomizationModeEnabledState,
);
- const showEditModePinnedButtons = isLayoutCustomizationModeEnabled;
-
return (
<>
{contextStoreCurrentObjectMetadataItemId && (
<>
- {!isMobile && showEditModePinnedButtons ? (
-
- ) : (
-
- {!isMobile && }
-
- )}
+
+ {!isMobile && }
+
{
@@ -23,6 +25,9 @@ export const RecordShowCommandMenu = () => {
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1;
const isMobile = useIsMobile();
+ const isLayoutCustomizationModeEnabled = useAtomStateValue(
+ isLayoutCustomizationModeEnabledState,
+ );
return (
<>
@@ -32,6 +37,7 @@ export const RecordShowCommandMenu = () => {
isInSidePanel={false}
displayType="button"
containerType="show-page-header"
+ isInPreviewMode={isLayoutCustomizationModeEnabled}
>
{!isMobile && }
diff --git a/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx b/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx
index 40d1acb708..c818f0aded 100644
--- a/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx
@@ -125,6 +125,7 @@ export const StandalonePageCommandMenu = () => {
containerType: 'standalone-page-header',
commandMenuItems: filteredCommandMenuItems,
commandMenuContextApi,
+ isInPreviewMode: false,
}}
>
{!isMobile && }
diff --git a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx
index 5c8e6683a0..8788ef4bd8 100644
--- a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordIndexCommandMenuDropdown.stories.tsx
@@ -45,6 +45,7 @@ const meta: Meta = {
containerType: 'index-page-dropdown',
commandMenuItems: createMockCommandMenuItems(),
commandMenuContextApi: EMPTY_COMMAND_MENU_CONTEXT_API,
+ isInPreviewMode: false,
}}
>
diff --git a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx
index 2d91768e8f..68b128bd90 100644
--- a/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/components/__stories__/RecordShowSidePanelCommandMenuDropdown.stories.tsx
@@ -3,13 +3,13 @@ import { Provider as JotaiProvider } from 'jotai';
import { userEvent, within } from 'storybook/test';
import { RecordPageSidePanelCommandMenuDropdown } from '@/command-menu-item/components/RecordPageSidePanelCommandMenuDropdown';
+import { EMPTY_COMMAND_MENU_CONTEXT_API } from '@/command-menu-item/constants/EmptyCommandMenuContextApi';
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
-import { EMPTY_COMMAND_MENU_CONTEXT_API } from '@/command-menu-item/constants/EmptyCommandMenuContextApi';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
@@ -44,6 +44,7 @@ const meta: Meta = {
...EMPTY_COMMAND_MENU_CONTEXT_API,
isInSidePanel: true,
},
+ isInPreviewMode: false,
}}
>
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts
index 2af4c363af..e5fad7c515 100644
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts
+++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContext.ts
@@ -9,6 +9,7 @@ export type CommandMenuContextType = {
containerType: CommandMenuItemContainerType;
commandMenuItems: CommandMenuItemFieldsFragment[];
commandMenuContextApi: CommandMenuContextApi;
+ isInPreviewMode: boolean;
};
export const CommandMenuContext = createContext({
@@ -16,4 +17,5 @@ export const CommandMenuContext = createContext({
displayType: 'button',
commandMenuItems: [],
commandMenuContextApi: EMPTY_COMMAND_MENU_CONTEXT_API,
+ isInPreviewMode: false,
});
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx
index 355e38e19d..84b16e60bc 100644
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProvider.tsx
@@ -2,7 +2,7 @@ import { CoreObjectNameSingular } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type CommandMenuContextType } from '@/command-menu-item/contexts/CommandMenuContext';
-import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
+import { useCurrentCommandMenuContextApi } from '@/command-menu-item/hooks/useCurrentCommandMenuContextApi';
import { CommandMenuContextProviderContent } from './CommandMenuContextProviderContent';
import { CommandMenuContextProviderWithWorkflowEnrichment } from './CommandMenuContextProviderWithWorkflowEnrichment';
@@ -12,6 +12,7 @@ type CommandMenuContextProviderProps = {
displayType: CommandMenuContextType['displayType'];
containerType: CommandMenuContextType['containerType'];
children: React.ReactNode;
+ isInPreviewMode?: boolean;
};
export const CommandMenuContextProvider = ({
@@ -19,8 +20,9 @@ export const CommandMenuContextProvider = ({
displayType,
containerType,
children,
+ isInPreviewMode = false,
}: CommandMenuContextProviderProps) => {
- const commandMenuContextApiFromHook = useCommandMenuContextApi();
+ const commandMenuContextApiFromHook = useCurrentCommandMenuContextApi();
const commandMenuContextApi = isInSidePanel
? { ...commandMenuContextApiFromHook, isInSidePanel: true }
@@ -45,6 +47,7 @@ export const CommandMenuContextProvider = ({
containerType={containerType}
commandMenuContextApi={commandMenuContextApi}
selectedWorkflowRecordIds={selectedWorkflowRecordIds}
+ isInPreviewMode={isInPreviewMode}
>
{children}
@@ -56,6 +59,7 @@ export const CommandMenuContextProvider = ({
displayType={displayType}
containerType={containerType}
commandMenuContextApi={commandMenuContextApi}
+ isInPreviewMode={isInPreviewMode}
>
{children}
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx
index 2c9ea657a0..2c5e265191 100644
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderContent.tsx
@@ -2,10 +2,12 @@ import {
CommandMenuContext,
type CommandMenuContextType,
} from '@/command-menu-item/contexts/CommandMenuContext';
+import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState';
import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector';
import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId';
import { doesCommandMenuItemMatchPageLayoutId } from '@/command-menu-item/utils/doesCommandMenuItemMatchPageLayoutId';
import { doesCommandMenuItemMatchPageType } from '@/command-menu-item/utils/doesCommandMenuItemMatchPageType';
+import { doesCommandMenuItemMatchSelectionState } from '@/command-menu-item/utils/doesCommandMenuItemMatchSelectionState';
import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useMemo } from 'react';
@@ -17,6 +19,7 @@ type CommandMenuContextProviderContentProps = {
containerType: CommandMenuContextType['containerType'];
children: React.ReactNode;
commandMenuContextApi: CommandMenuContextApi;
+ isInPreviewMode: boolean;
};
export const CommandMenuContextProviderContent = ({
@@ -24,19 +27,27 @@ export const CommandMenuContextProviderContent = ({
containerType,
children,
commandMenuContextApi,
+ isInPreviewMode,
}: CommandMenuContextProviderContentProps) => {
const commandMenuItems = useAtomStateValue(commandMenuItemsSelector);
+ const commandMenuItemsDraft = useAtomStateValue(commandMenuItemsDraftState);
const currentPageLayoutId = useAtomStateValue(currentPageLayoutIdState);
const filteredCommandMenuItems = useMemo(() => {
const currentObjectMetadataItemId =
commandMenuContextApi.objectMetadataItem.id;
+ const hasSelectedRecords =
+ commandMenuContextApi.numberOfSelectedRecords > 0;
+ const commandMenuItemsToDisplay = isInPreviewMode
+ ? (commandMenuItemsDraft ?? commandMenuItems)
+ : commandMenuItems;
- return commandMenuItems
+ return commandMenuItemsToDisplay
.filter(
doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId),
)
.filter(doesCommandMenuItemMatchPageType(commandMenuContextApi.pageType))
+ .filter(doesCommandMenuItemMatchSelectionState(hasSelectedRecords))
.filter(doesCommandMenuItemMatchPageLayoutId(currentPageLayoutId))
.filter((item) =>
evaluateConditionalAvailabilityExpression(
@@ -47,7 +58,13 @@ export const CommandMenuContextProviderContent = ({
.sort(
(firstItem, secondItem) => firstItem.position - secondItem.position,
);
- }, [commandMenuItems, commandMenuContextApi, currentPageLayoutId]);
+ }, [
+ commandMenuContextApi,
+ commandMenuItems,
+ commandMenuItemsDraft,
+ currentPageLayoutId,
+ isInPreviewMode,
+ ]);
return (
{children}
diff --git a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx
index 3242645aff..96d84c001b 100644
--- a/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/contexts/CommandMenuContextProviderWithWorkflowEnrichment.tsx
@@ -12,6 +12,7 @@ type CommandMenuContextProviderWithWorkflowEnrichmentProps = {
children: React.ReactNode;
commandMenuContextApi: CommandMenuContextApi;
selectedWorkflowRecordIds: string[];
+ isInPreviewMode: boolean;
};
export const CommandMenuContextProviderWithWorkflowEnrichment = ({
@@ -20,6 +21,7 @@ export const CommandMenuContextProviderWithWorkflowEnrichment = ({
children,
commandMenuContextApi,
selectedWorkflowRecordIds,
+ isInPreviewMode,
}: CommandMenuContextProviderWithWorkflowEnrichmentProps) => {
const workflowsWithCurrentVersions = useWorkflowsWithCurrentVersions(
selectedWorkflowRecordIds,
@@ -54,6 +56,7 @@ export const CommandMenuContextProviderWithWorkflowEnrichment = ({
displayType={displayType}
containerType={containerType}
commandMenuContextApi={enrichedCommandMenuContextApi}
+ isInPreviewMode={isInPreviewMode}
>
{children}
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx
index 7753cceee6..db16d7c6db 100644
--- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx
@@ -11,6 +11,7 @@ import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-lis
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
+import { styled } from '@linaria/react';
import { useContext } from 'react';
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
@@ -18,6 +19,14 @@ import { Loader } from 'twenty-ui/feedback';
import { MenuItem } from 'twenty-ui/navigation';
import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql';
+const StyledPreviewWrapper = styled.div`
+ cursor: not-allowed;
+
+ & * {
+ pointer-events: none;
+ }
+`;
+
type CommandMenuItemRendererProps = {
item: CommandMenuItemFieldsFragment;
};
@@ -27,7 +36,8 @@ type CommandMenuItemButtonRendererProps = CommandMenuItemRendererProps;
const CommandMenuItemButtonRenderer = ({
item,
}: CommandMenuItemButtonRendererProps) => {
- const { commandMenuContextApi } = useContext(CommandMenuContext);
+ const { commandMenuContextApi, isInPreviewMode } =
+ useContext(CommandMenuContext);
const { getIcon } = useIcons();
const { iconKey, label, shortLabel } = interpolateCommandMenuItemFields(
@@ -43,14 +53,19 @@ const CommandMenuItemButtonRenderer = ({
label,
});
+ const command = { key: item.id, label, shortLabel, Icon };
+
+ if (isInPreviewMode) {
+ return (
+
+
+
+ );
+ }
+
return (
diff --git a/packages/twenty-front/src/modules/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts b/packages/twenty-front/src/modules/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts
index c8fe027876..d9ea6e45a2 100644
--- a/packages/twenty-front/src/modules/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts
+++ b/packages/twenty-front/src/modules/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout.ts
@@ -2,6 +2,7 @@ import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/display/const
import { commandMenuPinnedInlineLayoutState } from '@/command-menu-item/display/states/commandMenuPinnedInlineLayoutState';
import { getVisibleCommandMenuItemCountForContainerWidth } from '@/command-menu-item/display/utils/getVisibleCommandMenuItemCountForContainerWidth';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
+import { isNumber } from '@sniptt/guards';
import { useCallback, useMemo } from 'react';
import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql';
@@ -25,18 +26,37 @@ export const usePinnedCommandMenuItemsInlineLayout = ({
[pinnedCommandMenuItems],
);
+ const hasKnownPinnedInlineLayout = useMemo(
+ () =>
+ commandMenuPinnedInlineLayout.containerWidth > 0 &&
+ pinnedCommandMenuItemKeysInDisplayOrder.every((commandMenuItemKey) =>
+ isNumber(
+ commandMenuPinnedInlineLayout.commandMenuItemWidthsByKey[
+ commandMenuItemKey
+ ],
+ ),
+ ),
+ [commandMenuPinnedInlineLayout, pinnedCommandMenuItemKeysInDisplayOrder],
+ );
+
const visiblePinnedCommandMenuItemCount = useMemo(
() =>
- getVisibleCommandMenuItemCountForContainerWidth({
- commandMenuItemKeysInDisplayOrder:
- pinnedCommandMenuItemKeysInDisplayOrder,
- commandMenuItemWidthsByKey:
- commandMenuPinnedInlineLayout.commandMenuItemWidthsByKey,
- commandMenuItemsContainerWidth:
- commandMenuPinnedInlineLayout.containerWidth,
- commandMenuItemsGapWidth: PINNED_COMMAND_MENU_ITEMS_GAP,
- }),
- [commandMenuPinnedInlineLayout, pinnedCommandMenuItemKeysInDisplayOrder],
+ hasKnownPinnedInlineLayout
+ ? getVisibleCommandMenuItemCountForContainerWidth({
+ commandMenuItemKeysInDisplayOrder:
+ pinnedCommandMenuItemKeysInDisplayOrder,
+ commandMenuItemWidthsByKey:
+ commandMenuPinnedInlineLayout.commandMenuItemWidthsByKey,
+ commandMenuItemsContainerWidth:
+ commandMenuPinnedInlineLayout.containerWidth,
+ commandMenuItemsGapWidth: PINNED_COMMAND_MENU_ITEMS_GAP,
+ })
+ : 0,
+ [
+ commandMenuPinnedInlineLayout,
+ hasKnownPinnedInlineLayout,
+ pinnedCommandMenuItemKeysInDisplayOrder,
+ ],
);
const pinnedInlineCommandMenuItems = useMemo(
diff --git a/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx
index 9e4b62f3ac..9e7e1cb965 100644
--- a/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown.tsx
@@ -83,7 +83,7 @@ export const CommandMenuItemEditRecordSelectionDropdown = ({
const TriggerIcon = isNoneSelected ? IconSquareX : IconSquareCheck;
const triggerLabel = isNoneSelected
? t`No record selected`
- : t`Records selected`;
+ : t`Record(s) selected`;
return (
}
dropdownPlacement="bottom-start"
+ dropdownOffset={{ y: 4 }}
dropdownComponents={
handleSelectMode('selection')}
/>
diff --git a/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx
deleted file mode 100644
index ecbdeba6a6..0000000000
--- a/packages/twenty-front/src/modules/command-menu-item/edit/components/PinnedCommandMenuItemButtonsEditMode.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { PinnedCommandMenuItemsInlineMeasurements } from '@/command-menu-item/display/components/PinnedCommandMenuItemsInlineMeasurements';
-import { PINNED_COMMAND_MENU_ITEMS_GAP } from '@/command-menu-item/display/constants/PinnedCommandMenuItemsGap';
-import { usePinnedCommandMenuItemsInlineLayout } from '@/command-menu-item/display/hooks/usePinnedCommandMenuItemsInlineLayout';
-import { interpolateCommandMenuItemFields } from '@/command-menu-item/display/utils/interpolateCommandMenuItemFields';
-import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState';
-import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
-import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId';
-import { CommandMenuButton } from '@/command-menu/components/CommandMenuButton';
-import { mainContextStoreHasSelectedRecordsSelector } from '@/context-store/states/selectors/mainContextStoreHasSelectedRecordsSelector';
-import { NodeDimension } from '@/ui/utilities/dimensions/components/NodeDimension';
-import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
-import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
-import { styled } from '@linaria/react';
-import { motion } from 'framer-motion';
-import { useContext, useMemo } from 'react';
-import { useIcons } from 'twenty-ui/display';
-import { ThemeContext } from 'twenty-ui/theme-constants';
-import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql';
-
-const StyledCommandMenuItemContainer = styled(motion.div)`
- align-items: center;
- display: flex;
- justify-content: center;
-`;
-
-const StyledWrapper = styled.div`
- flex: 1 1 0;
- min-width: 0;
- overflow: hidden;
-`;
-
-const StyledContainer = styled.div`
- display: flex;
- justify-content: flex-end;
- min-width: 0;
- width: 100%;
-`;
-
-const StyledItemsContainer = styled.div`
- display: flex;
- gap: ${PINNED_COMMAND_MENU_ITEMS_GAP}px;
- max-width: 100%;
- overflow: hidden;
-`;
-
-export const PinnedCommandMenuItemButtonsEditMode = () => {
- const { theme } = useContext(ThemeContext);
- const { getIcon } = useIcons();
- const commandMenuContextApi = useCommandMenuContextApi();
-
- const currentObjectMetadataItemId =
- commandMenuContextApi.objectMetadataItem.id;
-
- const commandMenuItemsDraft =
- useAtomStateValue(commandMenuItemsDraftState) ?? [];
-
- const mainContextStoreHasSelectedRecords = useAtomStateValue(
- mainContextStoreHasSelectedRecordsSelector,
- );
-
- const allowedAvailabilityTypes = useMemo(
- () =>
- new Set([
- CommandMenuItemAvailabilityType.GLOBAL,
- CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT,
- mainContextStoreHasSelectedRecords
- ? CommandMenuItemAvailabilityType.RECORD_SELECTION
- : CommandMenuItemAvailabilityType.FALLBACK,
- ]),
- [mainContextStoreHasSelectedRecords],
- );
-
- const pinnedCommandMenuItems = commandMenuItemsDraft
- .filter(
- doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId),
- )
- .filter((item) => allowedAvailabilityTypes.has(item.availabilityType))
- .filter((item) => item.isPinned);
-
- const {
- pinnedInlineCommandMenuItems,
- pinnedOverflowCommandMenuItems,
- onContainerDimensionChange,
- onCommandMenuItemDimensionChange,
- } = usePinnedCommandMenuItemsInlineLayout({
- pinnedCommandMenuItems,
- });
-
- return (
- <>
-
-
-
-
-
- {pinnedInlineCommandMenuItems.map((item) => {
- const { iconKey, label, shortLabel } =
- interpolateCommandMenuItemFields(item, commandMenuContextApi);
-
- const Icon = getIcon(iconKey, COMMAND_MENU_DEFAULT_ICON);
-
- return (
-
-
-
- );
- })}
-
-
-
-
- >
- );
-};
diff --git a/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx b/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx
index 8ab4af27e4..0260555308 100644
--- a/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/edit/components/SidePanelCommandMenuItemEditPage.tsx
@@ -1,15 +1,13 @@
import { CommandMenuItemEditRecordSelectionDropdown } from '@/command-menu-item/edit/components/CommandMenuItemEditRecordSelectionDropdown';
import { CommandMenuItemOptionsDropdown } from '@/command-menu-item/edit/components/CommandMenuItemOptionsDropdown';
+import { useEditableCommandMenuItems } from '@/command-menu-item/edit/hooks/useEditableCommandMenuItems';
import { useReorderCommandMenuItemsInDraft } from '@/command-menu-item/edit/hooks/useReorderCommandMenuItemsInDraft';
import { useResetCommandMenuItemsDraft } from '@/command-menu-item/edit/hooks/useResetCommandMenuItemsDraft';
import { useUpdateCommandMenuItemInDraft } from '@/command-menu-item/edit/hooks/useUpdateCommandMenuItemInDraft';
-import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState';
-import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
+import { useCurrentCommandMenuContextApi } from '@/command-menu-item/hooks/useCurrentCommandMenuContextApi';
import { commandMenuItemsSelector } from '@/command-menu-item/states/commandMenuItemsSelector';
-import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId';
import { groupCommandMenuItems } from '@/command-menu-item/utils/groupCommandMenuItems';
import { COMMAND_MENU_CLICK_OUTSIDE_ID } from '@/command-menu/constants/CommandMenuClickOutsideId';
-import { mainContextStoreHasSelectedRecordsSelector } from '@/context-store/states/selectors/mainContextStoreHasSelectedRecordsSelector';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
@@ -36,10 +34,7 @@ import {
import { Button } from 'twenty-ui/input';
import { MenuItem, MenuItemDraggable } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
-import {
- CommandMenuItemAvailabilityType,
- type CommandMenuItemFieldsFragment,
-} from '~/generated-metadata/graphql';
+import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql';
import { normalizeSearchText } from '~/utils/normalizeSearchText';
const StyledContainer = styled.div`
@@ -66,7 +61,7 @@ const StyledContent = styled.div`
export const SidePanelCommandMenuItemEditPage = () => {
const { t } = useLingui();
const { getIcon } = useIcons();
- const commandMenuContextApi = useCommandMenuContextApi();
+ const commandMenuContextApi = useCurrentCommandMenuContextApi();
const currentObjectMetadataItemId =
commandMenuContextApi.objectMetadataItem.id;
@@ -76,44 +71,20 @@ export const SidePanelCommandMenuItemEditPage = () => {
const isRecordPage =
commandMenuContextApi.pageType === ContextStorePageType.Record;
- const isIndexPage =
- commandMenuContextApi.pageType === ContextStorePageType.Index;
-
- const mainContextStoreHasSelectedRecords = useAtomStateValue(
- mainContextStoreHasSelectedRecordsSelector,
- );
-
const sidePanelSearch = useAtomStateValue(sidePanelSearchState);
const commandMenuItems = useAtomStateValue(commandMenuItemsSelector);
const serverItemsById = new Map(
commandMenuItems.map((item) => [item.id, item]),
);
- const commandMenuItemsDraft =
- useAtomStateValue(commandMenuItemsDraftState) ?? [];
const { updateCommandMenuItemInDraft } = useUpdateCommandMenuItemInDraft();
const { reorderCommandMenuItemInDraft } = useReorderCommandMenuItemsInDraft();
const { resetCommandMenuItemsDraft } = useResetCommandMenuItemsDraft();
- const allowedAvailabilityTypes = new Set([
- CommandMenuItemAvailabilityType.GLOBAL,
- ...(isIndexPage || isRecordPage
- ? [CommandMenuItemAvailabilityType.GLOBAL_OBJECT_CONTEXT]
- : []),
- ...(mainContextStoreHasSelectedRecords
- ? [CommandMenuItemAvailabilityType.RECORD_SELECTION]
- : []),
- ]);
-
- const filteredCommandMenuItems = commandMenuItemsDraft
- .filter(
- doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId),
- )
- .filter((item) => allowedAvailabilityTypes.has(item.availabilityType))
- .sort((firstItem, secondItem) => firstItem.position - secondItem.position);
+ const editableCommandMenuItems = useEditableCommandMenuItems();
const filteredCommandMenuItemIds = new Set(
- filteredCommandMenuItems.map((item) => item.id),
+ editableCommandMenuItems.map((item) => item.id),
);
const getDisplayLabel = (item: CommandMenuItemFieldsFragment) =>
@@ -123,7 +94,7 @@ export const SidePanelCommandMenuItemEditPage = () => {
}) ?? item.label;
const { pinned: allPinnedItems, other: allOtherItems } =
- groupCommandMenuItems(filteredCommandMenuItems);
+ groupCommandMenuItems(editableCommandMenuItems);
const normalizedSearch =
sidePanelSearch.length > 0
diff --git a/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useEditableCommandMenuItems.ts b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useEditableCommandMenuItems.ts
new file mode 100644
index 0000000000..c9b5e701eb
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/edit/hooks/useEditableCommandMenuItems.ts
@@ -0,0 +1,38 @@
+import { commandMenuItemsDraftState } from '@/command-menu-item/edit/states/commandMenuItemsDraftState';
+import { useCurrentCommandMenuContextApi } from '@/command-menu-item/hooks/useCurrentCommandMenuContextApi';
+import { doesCommandMenuItemMatchObjectMetadataId } from '@/command-menu-item/utils/doesCommandMenuItemMatchObjectMetadataId';
+import { doesCommandMenuItemMatchPageLayoutId } from '@/command-menu-item/utils/doesCommandMenuItemMatchPageLayoutId';
+import { doesCommandMenuItemMatchPageType } from '@/command-menu-item/utils/doesCommandMenuItemMatchPageType';
+import { doesCommandMenuItemMatchSelectionState } from '@/command-menu-item/utils/doesCommandMenuItemMatchSelectionState';
+import { currentPageLayoutIdState } from '@/page-layout/states/currentPageLayoutIdState';
+import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
+import { useMemo } from 'react';
+import { CommandMenuItemAvailabilityType } from '~/generated-metadata/graphql';
+
+export const useEditableCommandMenuItems = () => {
+ const commandMenuContextApi = useCurrentCommandMenuContextApi();
+ const commandMenuItemsDraft = useAtomStateValue(commandMenuItemsDraftState);
+ const currentPageLayoutId = useAtomStateValue(currentPageLayoutIdState);
+
+ return useMemo(() => {
+ const currentObjectMetadataItemId =
+ commandMenuContextApi.objectMetadataItem.id;
+ const hasSelectedRecords =
+ commandMenuContextApi.numberOfSelectedRecords > 0;
+
+ return (commandMenuItemsDraft ?? [])
+ .filter(
+ doesCommandMenuItemMatchObjectMetadataId(currentObjectMetadataItemId),
+ )
+ .filter(doesCommandMenuItemMatchPageType(commandMenuContextApi.pageType))
+ .filter(doesCommandMenuItemMatchSelectionState(hasSelectedRecords))
+ .filter(
+ (item) =>
+ item.availabilityType !== CommandMenuItemAvailabilityType.FALLBACK,
+ )
+ .filter(doesCommandMenuItemMatchPageLayoutId(currentPageLayoutId))
+ .sort(
+ (firstItem, secondItem) => firstItem.position - secondItem.position,
+ );
+ }, [commandMenuItemsDraft, commandMenuContextApi, currentPageLayoutId]);
+};
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
index f79235c02c..502468875a 100644
--- a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
@@ -70,6 +70,7 @@ const getWrapper =
objectMetadataItem: {},
objectMetadataLabel: '',
},
+ isInPreviewMode: false,
}}
>
{children}
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCurrentCommandMenuContextApi.ts
similarity index 99%
rename from packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts
rename to packages/twenty-front/src/modules/command-menu-item/hooks/useCurrentCommandMenuContextApi.ts
index 4e49638fad..430dce88d0 100644
--- a/packages/twenty-front/src/modules/command-menu-item/hooks/useCommandMenuContextApi.ts
+++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCurrentCommandMenuContextApi.ts
@@ -30,7 +30,7 @@ import {
} from 'twenty-shared/types';
import { isDefined, resolveObjectMetadataLabel } from 'twenty-shared/utils';
-export const useCommandMenuContextApi = (): CommandMenuContextApi => {
+export const useCurrentCommandMenuContextApi = (): CommandMenuContextApi => {
const store = useStore();
const contextStoreInstanceId = useAvailableComponentInstanceIdOrThrow(
diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/doesCommandMenuItemMatchSelectionState.test.ts b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/doesCommandMenuItemMatchSelectionState.test.ts
new file mode 100644
index 0000000000..1f07bdf725
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/utils/__tests__/doesCommandMenuItemMatchSelectionState.test.ts
@@ -0,0 +1,36 @@
+import { doesCommandMenuItemMatchSelectionState } from '@/command-menu-item/utils/doesCommandMenuItemMatchSelectionState';
+import {
+ CommandMenuItemAvailabilityType,
+ type CommandMenuItemFieldsFragment,
+} from '~/generated-metadata/graphql';
+
+const buildCommandMenuItem = (
+ availabilityType: CommandMenuItemAvailabilityType,
+) =>
+ ({
+ availabilityType,
+ }) as CommandMenuItemFieldsFragment;
+
+describe('doesCommandMenuItemMatchSelectionState', () => {
+ it('should keep a non-record-selection item when no records are selected', () => {
+ const item = buildCommandMenuItem(CommandMenuItemAvailabilityType.GLOBAL);
+
+ expect(doesCommandMenuItemMatchSelectionState(false)(item)).toBe(true);
+ });
+
+ it('should hide a record-selection item when no records are selected', () => {
+ const item = buildCommandMenuItem(
+ CommandMenuItemAvailabilityType.RECORD_SELECTION,
+ );
+
+ expect(doesCommandMenuItemMatchSelectionState(false)(item)).toBe(false);
+ });
+
+ it('should keep a record-selection item when records are selected', () => {
+ const item = buildCommandMenuItem(
+ CommandMenuItemAvailabilityType.RECORD_SELECTION,
+ );
+
+ expect(doesCommandMenuItemMatchSelectionState(true)(item)).toBe(true);
+ });
+});
diff --git a/packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchSelectionState.ts b/packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchSelectionState.ts
new file mode 100644
index 0000000000..388a3ac089
--- /dev/null
+++ b/packages/twenty-front/src/modules/command-menu-item/utils/doesCommandMenuItemMatchSelectionState.ts
@@ -0,0 +1,9 @@
+import {
+ CommandMenuItemAvailabilityType,
+ type CommandMenuItemFieldsFragment,
+} from '~/generated-metadata/graphql';
+
+export const doesCommandMenuItemMatchSelectionState =
+ (hasSelectedRecords: boolean) => (item: CommandMenuItemFieldsFragment) =>
+ item.availabilityType !==
+ CommandMenuItemAvailabilityType.RECORD_SELECTION || hasSelectedRecords;
diff --git a/packages/twenty-front/src/modules/object-record/record-update-multiple/components/__stories__/UpdateMultipleRecordsContainer.stories.tsx b/packages/twenty-front/src/modules/object-record/record-update-multiple/components/__stories__/UpdateMultipleRecordsContainer.stories.tsx
index 2e300a6d98..fa7e7d66aa 100644
--- a/packages/twenty-front/src/modules/object-record/record-update-multiple/components/__stories__/UpdateMultipleRecordsContainer.stories.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-update-multiple/components/__stories__/UpdateMultipleRecordsContainer.stories.tsx
@@ -1,11 +1,10 @@
-import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { EMPTY_COMMAND_MENU_CONTEXT_API } from '@/command-menu-item/constants/EmptyCommandMenuContextApi';
+import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
import { UpdateMultipleRecordsContainer } from '@/object-record/record-update-multiple/components/UpdateMultipleRecordsContainer';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
-import { useEffect } from 'react';
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { MockLink } from '@apollo/client/testing';
import {
@@ -14,6 +13,7 @@ import {
type StoryObj,
} from '@storybook/react-vite';
import gql from 'graphql-tag';
+import { useEffect } from 'react';
import { expect, userEvent, within } from 'storybook/test';
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
@@ -80,6 +80,7 @@ const meta: Meta = {
containerType: 'index-page-dropdown',
displayType: 'dropdownItem',
commandMenuContextApi: EMPTY_COMMAND_MENU_CONTEXT_API,
+ isInPreviewMode: false,
}}
>