271 remove is command menu v2 enabled (#10809)

Closes https://github.com/twentyhq/core-team-issues/issues/271

This PR
- Removes the feature flag IS_COMMAND_MENU_V2_ENABLED
- Removes all old Right drawer components
- Removes the Action menu bar
- Removes unused Copilot page
This commit is contained in:
Raphaël Bosi
2025-03-12 16:26:29 +01:00
committed by GitHub
parent 1b0413bf8b
commit daa501549e
124 changed files with 281 additions and 4222 deletions

View File

@ -27,10 +27,6 @@ export const RecordActionMenuEntriesSetter = () => {
FeatureFlagKey.IsWorkflowEnabled,
);
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
if (!isDefined(contextStoreCurrentObjectMetadataItem)) {
return null;
}
@ -40,10 +36,7 @@ export const RecordActionMenuEntriesSetter = () => {
contextStoreTargetedRecordsRule,
);
const actionConfig = getActionConfig(
contextStoreCurrentObjectMetadataItem,
isCommandMenuV2Enabled,
);
const actionConfig = getActionConfig(contextStoreCurrentObjectMetadataItem);
const actionsToRegister = isDefined(viewType)
? Object.values(actionConfig ?? {}).filter((action) =>

View File

@ -1,6 +1,5 @@
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
@ -8,10 +7,9 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { t } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import { useCallback, useContext, useState } from 'react';
import { useCallback, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
import { getOsControlSymbol } from 'twenty-ui';
@ -39,8 +37,6 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
const { sortedFavorites: favorites } = useFavorites();
const { deleteFavorite } = useDeleteFavorite();
const { closeRightDrawer } = useRightDrawer();
const handleDeleteClick = useCallback(async () => {
resetTableRowSelection();
@ -63,8 +59,6 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
const isRemoteObject = objectMetadataItem.isRemote;
const { isInRightDrawer } = useContext(ActionMenuContext);
const shouldBeRegistered =
!isRemoteObject &&
isNull(selectedRecord?.deletedAt) &&
@ -91,9 +85,6 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
subtitle={t`Are you sure you want to delete this record? It can be recovered from the Command menu (${osControlSymbol} + K).`}
onConfirmClick={() => {
handleDeleteClick();
if (isInRightDrawer) {
closeRightDrawer({ emitCloseEvent: false });
}
}}
confirmButtonText={'Delete Record'}
/>

View File

@ -1,14 +1,12 @@
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { AppPath } from '@/types/AppPath';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useCallback, useContext, useState } from 'react';
import { useCallback, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useNavigateApp } from '~/hooks/useNavigateApp';
@ -35,8 +33,6 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const { closeRightDrawer } = useRightDrawer();
const handleDeleteClick = useCallback(async () => {
resetTableRowSelection();
@ -54,8 +50,6 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
const isRemoteObject = objectMetadataItem.isRemote;
const { isInRightDrawer } = useContext(ActionMenuContext);
const shouldBeRegistered =
!hasObjectReadOnlyPermission &&
!isRemoteObject &&
@ -82,9 +76,6 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
}
onConfirmClick={async () => {
await handleDeleteClick();
if (isInRightDrawer) {
closeRightDrawer();
}
}}
confirmButtonText={'Permanently Destroy Record'}
/>

View File

@ -1,6 +1,5 @@
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
@ -9,9 +8,8 @@ import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTabl
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useState } from 'react';
import { useCallback, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
@ -35,8 +33,6 @@ export const useRestoreSingleRecordAction: ActionHookWithObjectMetadataItem = ({
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const { closeRightDrawer } = useRightDrawer();
const handleRestoreClick = useCallback(async () => {
resetTableRowSelection();
@ -55,8 +51,6 @@ export const useRestoreSingleRecordAction: ActionHookWithObjectMetadataItem = ({
useRecoilComponentValueV2(contextStoreCurrentViewTypeComponentState) ===
ContextStoreViewType.ShowPage;
const { isInRightDrawer } = useContext(ActionMenuContext);
const shouldBeRegistered =
!isRemoteObject &&
isDefined(selectedRecord?.deletedAt) &&
@ -73,9 +67,6 @@ export const useRestoreSingleRecordAction: ActionHookWithObjectMetadataItem = ({
const handleConfirmClick = () => {
handleRestoreClick();
if (isInRightDrawer) {
closeRightDrawer({ emitCloseEvent: false });
}
};
return {

View File

@ -1,4 +1,3 @@
import { DEFAULT_ACTIONS_CONFIG_V1 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV1';
import { DEFAULT_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV2';
import { WORKFLOW_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowActionsConfig';
import { WORKFLOW_RUNS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig';
@ -6,10 +5,7 @@ import { WORKFLOW_VERSIONS_ACTIONS_CONFIG } from '@/action-menu/actions/record-a
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const getActionConfig = (
objectMetadataItem: ObjectMetadataItem,
isCommandMenuV2Enabled: boolean,
) => {
export const getActionConfig = (objectMetadataItem: ObjectMetadataItem) => {
switch (objectMetadataItem.nameSingular) {
case CoreObjectNameSingular.Workflow:
return WORKFLOW_ACTIONS_CONFIG;
@ -18,8 +14,6 @@ export const getActionConfig = (
case CoreObjectNameSingular.WorkflowRun:
return WORKFLOW_RUNS_ACTIONS_CONFIG;
default:
return isCommandMenuV2Enabled
? DEFAULT_ACTIONS_CONFIG_V2
: DEFAULT_ACTIONS_CONFIG_V1;
return DEFAULT_ACTIONS_CONFIG_V2;
}
};

View File

@ -1,10 +1,7 @@
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { Key } from 'ts-key-enum';
import { Button, getOsControlSymbol } from 'twenty-ui';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';
export const CmdEnterActionButton = ({
title,
@ -15,15 +12,10 @@ export const CmdEnterActionButton = ({
onClick: () => void;
disabled?: boolean;
}) => {
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
useScopedHotkeys(
[`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
() => onClick(),
isCommandMenuV2Enabled
? AppHotkeyScope.CommandMenuOpen
: RightDrawerHotkeyScope.RightDrawer,
AppHotkeyScope.CommandMenuOpen,
[onClick],
);

View File

@ -3,10 +3,8 @@ import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/
import { RecordAgnosticActionMenuEntriesSetter } from '@/action-menu/actions/record-agnostic-actions/components/RecordAgnosticActionMenuEntriesSetter';
import { RunWorkflowRecordAgnosticActionMenuEntriesSetter } from '@/action-menu/actions/record-agnostic-actions/components/RunWorkflowRecordAgnosticActionMenuEntriesSetter';
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
import { RecordIndexActionMenuButtons } from '@/action-menu/components/RecordIndexActionMenuButtons';
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { isRecordIndexLoadMoreLockedComponentState } from '@/object-record/record-index/states/isRecordIndexLoadMoreLockedComponentState';
@ -21,10 +19,6 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
contextStoreCurrentObjectMetadataItemComponentState,
);
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled,
);
@ -54,14 +48,9 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
},
}}
>
{isCommandMenuV2Enabled ? (
<>{!isMobile && <RecordIndexActionMenuButtons />}</>
) : (
<RecordIndexActionMenuBar />
)}
{!isMobile && <RecordIndexActionMenuButtons />}
<RecordIndexActionMenuDropdown />
<ActionMenuConfirmationModals />
<RecordIndexActionMenuEffect />
<RecordActionMenuEntriesSetter />
<RecordAgnosticActionMenuEntriesSetter />
{isWorkflowEnabled && (

View File

@ -1,51 +0,0 @@
import { RecordIndexActionMenuBarAllActionsButton } from '@/action-menu/components/RecordIndexActionMenuBarAllActionsButton';
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
const StyledLabel = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.medium};
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
export const RecordIndexActionMenuBar = () => {
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
);
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
ActionMenuComponentInstanceContext,
);
const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentSelector,
);
const pinnedEntries = actionMenuEntries.filter((entry) => entry.isPinned);
if (contextStoreNumberOfSelectedRecords === 0) {
return null;
}
return (
<BottomBar
bottomBarId={getActionBarIdFromActionMenuId(actionMenuId)}
bottomBarHotkeyScopeFromParent={{ scope: ActionBarHotkeyScope.ActionBar }}
>
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
{pinnedEntries.map((entry, index) => (
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
))}
<RecordIndexActionMenuBarAllActionsButton />
</BottomBar>
);
};

View File

@ -1,76 +0,0 @@
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
export const RecordIndexActionMenuEffect = () => {
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
contextStoreNumberOfSelectedRecordsComponentState,
);
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
ActionMenuComponentInstanceContext,
);
const { openActionBar, closeActionBar } = useActionMenu(actionMenuId);
// Using closeActionBar here was causing a bug because it goes back to the
// previous hotkey scope, and we don't want that here.
const setIsBottomBarOpened = useSetRecoilComponentStateV2(
isBottomBarOpenedComponentState,
getActionBarIdFromActionMenuId(actionMenuId),
);
const isDropdownOpen = useRecoilValue(
extractComponentState(
isDropdownOpenComponentState,
getActionMenuDropdownIdFromActionMenuId(actionMenuId),
),
);
const { isRightDrawerOpen } = useRightDrawer();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
useEffect(() => {
if (
contextStoreNumberOfSelectedRecords > 0 &&
!isDropdownOpen &&
!isRightDrawerOpen &&
!isCommandMenuOpened
) {
// We only handle opening the ActionMenuBar here, not the Dropdown.
// The Dropdown is already managed by sync handlers for events like
// right-click to open and click outside to close.
openActionBar();
}
if (contextStoreNumberOfSelectedRecords === 0 && isDropdownOpen) {
closeActionBar();
}
}, [
contextStoreNumberOfSelectedRecords,
openActionBar,
closeActionBar,
isDropdownOpen,
isRightDrawerOpen,
isCommandMenuOpened,
]);
useEffect(() => {
if (isRightDrawerOpen || isCommandMenuOpened) {
setIsBottomBarOpened(false);
}
}, [isRightDrawerOpen, isCommandMenuOpened, setIsBottomBarOpened]);
return null;
};

View File

@ -5,40 +5,19 @@ import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMen
import { RecordShowActionMenuButtons } from '@/action-menu/components/RecordShowActionMenuButtons';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';
import { RecordShowPageBaseHeader } from '~/pages/object-record/RecordShowPageBaseHeader';
export const RecordShowActionMenu = ({
isFavorite,
record,
objectMetadataItem,
objectNameSingular,
handleFavoriteButtonClick,
}: {
isFavorite: boolean;
record: ObjectRecord | undefined;
objectMetadataItem: ObjectMetadataItem;
objectNameSingular: string;
handleFavoriteButtonClick: () => void;
}) => {
export const RecordShowActionMenu = () => {
const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemComponentState,
);
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled,
);
// TODO: refactor RecordShowPageBaseHeader to use the context store
return (
<>
{contextStoreCurrentObjectMetadataItem && (
@ -48,19 +27,7 @@ export const RecordShowActionMenu = ({
onActionExecutedCallback: () => {},
}}
>
{isCommandMenuV2Enabled ? (
<RecordShowActionMenuButtons />
) : (
<RecordShowPageBaseHeader
{...{
isFavorite,
record,
objectMetadataItem,
objectNameSingular,
handleFavoriteButtonClick,
}}
/>
)}
<RecordShowActionMenuButtons />
<ActionMenuConfirmationModals />
<RecordActionMenuEntriesSetter />
<RecordAgnosticActionMenuEntriesSetter />

View File

@ -6,17 +6,14 @@ import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-men
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import { i18n } from '@lingui/core';
import { Key } from 'ts-key-enum';
import { Button, MenuItem, getOsControlSymbol } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated/graphql';
import { Button, getOsControlSymbol, MenuItem } from 'twenty-ui';
export const RightDrawerActionMenuDropdown = () => {
const actionMenuEntries = useRecoilComponentValueV2(
@ -31,10 +28,6 @@ export const RightDrawerActionMenuDropdown = () => {
const theme = useTheme();
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
useScopedHotkeys(
[Key.Escape, 'ctrl+o,meta+o'],
() => {
@ -53,9 +46,7 @@ export const RightDrawerActionMenuDropdown = () => {
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
);
},
isCommandMenuV2Enabled
? AppHotkeyScope.CommandMenuOpen
: RightDrawerHotkeyScope.RightDrawer,
AppHotkeyScope.CommandMenuOpen,
[openDropdown],
);

View File

@ -1,141 +0,0 @@
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
import { msg } from '@lingui/core/macro';
import { expect, jest } from '@storybook/jest';
import { Meta, StoryObj } from '@storybook/react';
import { userEvent, waitFor, within } from '@storybook/test';
import { RecoilRoot } from 'recoil';
import { IconTrash, RouterDecorator } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const deleteMock = jest.fn();
const meta: Meta<typeof RecordIndexActionMenuBar> = {
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
component: RecordIndexActionMenuBar,
decorators: [
RouterDecorator,
I18nFrontDecorator,
(Story) => (
<RecordFilterGroupsComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<RecordFiltersComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<RecordSortsComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<RecoilRoot
initializeState={({ set }) => {
set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
{
mode: 'selection',
selectedRecordIds: ['1', '2', '3'],
},
);
set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily(
{
instanceId: 'story-action-menu',
},
),
3,
);
const map = new Map<string, ActionMenuEntry>();
map.set('delete', {
isPinned: true,
scope: ActionMenuEntryScope.RecordSelection,
type: ActionMenuEntryType.Standard,
key: 'delete',
label: msg`Delete`,
position: 0,
Icon: IconTrash,
onClick: deleteMock,
});
set(
actionMenuEntriesComponentState.atomFamily({
instanceId: 'story-action-menu',
}),
map,
);
set(
isBottomBarOpenedComponentState.atomFamily({
instanceId:
getActionBarIdFromActionMenuId('story-action-menu'),
}),
true,
);
}}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'story-action-menu' }}
>
<Story />
</ActionMenuComponentInstanceContext.Provider>
</RecoilRoot>
</ContextStoreComponentInstanceContext.Provider>
</RecordSortsComponentInstanceContext.Provider>
</RecordFiltersComponentInstanceContext.Provider>
</RecordFilterGroupsComponentInstanceContext.Provider>
),
],
args: {
actionMenuId: 'story-action-menu',
},
};
export default meta;
type Story = StoryObj<typeof RecordIndexActionMenuBar>;
export const Default: Story = {
args: {
actionMenuId: 'story-action-menu',
},
};
export const WithCustomSelection: Story = {
args: {
actionMenuId: 'story-action-menu',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const selectionText = await canvas.findByText('3 selected:');
expect(selectionText).toBeInTheDocument();
},
};
export const WithButtonClicks: Story = {
args: {
actionMenuId: 'story-action-menu',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const deleteButton = await canvas.findByText('Delete');
await userEvent.click(deleteButton);
await waitFor(() => {
expect(deleteMock).toHaveBeenCalled();
});
},
};

View File

@ -1,82 +0,0 @@
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { useActionMenu } from '../useActionMenu';
const openBottomBar = jest.fn();
const closeBottomBar = jest.fn();
const openDropdown = jest.fn();
const closeDropdown = jest.fn();
jest.mock('@/ui/layout/bottom-bar/hooks/useBottomBar', () => ({
useBottomBar: jest.fn(() => ({
openBottomBar: openBottomBar,
closeBottomBar: closeBottomBar,
})),
}));
jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
useDropdownV2: jest.fn(() => ({
openDropdown: openDropdown,
closeDropdown: closeDropdown,
})),
}));
describe('useActionMenu', () => {
const actionMenuId = 'test-action-menu';
const actionBarId = getActionBarIdFromActionMenuId(actionMenuId);
const actionMenuDropdownId =
getActionMenuDropdownIdFromActionMenuId(actionMenuId);
it('should return the correct functions', () => {
const { result } = renderHook(() => useActionMenu(actionMenuId));
expect(result.current).toHaveProperty('openActionMenuDropdown');
expect(result.current).toHaveProperty('openActionBar');
expect(result.current).toHaveProperty('closeActionBar');
expect(result.current).toHaveProperty('closeActionMenuDropdown');
});
it('should call the correct functions when opening action menu dropdown', () => {
const { result } = renderHook(() => useActionMenu(actionMenuId));
act(() => {
result.current.openActionMenuDropdown();
});
expect(closeBottomBar).toHaveBeenCalledWith(actionBarId);
expect(openDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
});
it('should call the correct functions when opening action bar', () => {
const { result } = renderHook(() => useActionMenu(actionMenuId));
act(() => {
result.current.openActionBar();
});
expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
expect(openBottomBar).toHaveBeenCalledWith(actionBarId);
});
it('should call the correct function when closing action menu dropdown', () => {
const { result } = renderHook(() => useActionMenu(actionMenuId));
act(() => {
result.current.closeActionMenuDropdown();
});
expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
});
it('should call the correct function when closing action bar', () => {
const { result } = renderHook(() => useActionMenu(actionMenuId));
act(() => {
result.current.closeActionBar();
});
expect(closeBottomBar).toHaveBeenCalledWith(actionBarId);
});
});

View File

@ -1,38 +0,0 @@
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { useBottomBar } from '@/ui/layout/bottom-bar/hooks/useBottomBar';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
export const useActionMenu = (actionMenuId: string) => {
const { openDropdown, closeDropdown } = useDropdownV2();
const { openBottomBar, closeBottomBar } = useBottomBar();
const actionBarId = getActionBarIdFromActionMenuId(actionMenuId);
const actionMenuDropdownId =
getActionMenuDropdownIdFromActionMenuId(actionMenuId);
const openActionMenuDropdown = () => {
closeBottomBar(actionBarId);
openDropdown(actionMenuDropdownId);
};
const openActionBar = () => {
closeDropdown(actionMenuDropdownId);
openBottomBar(actionBarId);
};
const closeActionMenuDropdown = () => {
closeDropdown(actionMenuDropdownId);
};
const closeActionBar = () => {
closeBottomBar(actionBarId);
};
return {
openActionMenuDropdown,
openActionBar,
closeActionBar,
closeActionMenuDropdown,
};
};