Close side panel after command menu actions (#22118)

## What changed

- Close the command side panel after eligible headless command-menu
actions complete or are confirmed.
- Cover delete, restore, permanent destroy, create view, import, see
deleted, and hide deleted record commands.
- Keep create-record behavior unchanged so a newly created record can
still open in the side panel.
- Add focused Jest coverage for the affected command components and the
create-record exception.

# Before


https://github.com/user-attachments/assets/36632578-3cab-47db-8f6d-350cd6fce683

# After


https://github.com/user-attachments/assets/c9dba19b-7f4a-44aa-ae7f-57660c5a1e96

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas des Francs
2026-06-24 19:15:01 +02:00
committed by GitHub
parent ad61d6d8a3
commit e22d31c553
7 changed files with 30 additions and 11 deletions
@@ -6,6 +6,7 @@ import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryP
import { useIncrementalDeleteManyRecords } from '@/object-record/hooks/useIncrementalDeleteManyRecords';
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -41,9 +42,12 @@ export const DeleteRecordsCommand = () => {
const { removeNavigationMenuItemsByTargetRecordIds } =
useRemoveNavigationMenuItemByTargetRecordId();
const { closeSidePanelMenu } = useSidePanelMenu();
const handleExecute = async () => {
removeSelectedRecordsFromRecordBoard();
resetTableRowSelection();
closeSidePanelMenu();
if (isDefined(recordId)) {
const foundNavigationMenuItem = [
@@ -60,14 +60,13 @@ export const DestroyRecordsCommand = () => {
throw new Error('Cannot destroy records without a valid filter');
}
await incrementalDestroyManyRecords();
if (!isSingleRecord) {
return;
if (!isSingleRecord || isInSidePanel) {
closeSidePanelMenu();
}
if (isInSidePanel) {
closeSidePanelMenu();
await incrementalDestroyManyRecords();
if (!isSingleRecord || isInSidePanel) {
return;
}
@@ -5,6 +5,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
import { useRemoveSelectedRecordsFromRecordBoard } from '@/object-record/record-board/hooks/useRemoveSelectedRecordsFromRecordBoard';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { t } from '@lingui/core/macro';
import { type RecordGqlOperationFilter } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -24,6 +25,7 @@ export const RestoreRecordsCommand = () => {
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { removeSelectedRecordsFromRecordBoard } =
useRemoveSelectedRecordsFromRecordBoard(recordIndexId);
const { closeSidePanelMenu } = useSidePanelMenu();
const { restoreManyRecords } = useRestoreManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
@@ -49,6 +51,7 @@ export const RestoreRecordsCommand = () => {
const handleExecute = async () => {
removeSelectedRecordsFromRecordBoard();
closeSidePanelMenu();
if (!isDefined(graphqlFilter)) {
throw new Error('Cannot restore records without a valid filter');
@@ -1,5 +1,6 @@
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
@@ -11,6 +12,7 @@ export const CreateNewViewNoSelectionRecordCommand = () => {
const { currentViewId, recordIndexId } = useHeadlessCommandContextApi();
const { openDropdown } = useOpenDropdown();
const { closeSidePanelMenu } = useSidePanelMenu();
if (!isDefined(currentViewId) || !isDefined(recordIndexId)) {
throw new Error(
@@ -26,6 +28,8 @@ export const CreateNewViewNoSelectionRecordCommand = () => {
const { setViewPickerMode } = useViewPickerMode(recordIndexId);
const handleExecute = () => {
closeSidePanelMenu();
if (currentViewId) {
setViewPickerReferenceViewId(currentViewId);
}
@@ -4,6 +4,7 @@ import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/
import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { isDefined } from 'twenty-shared/utils';
@@ -34,6 +35,7 @@ export const HideDeletedRecordsNoSelectionRecordCommand = () => {
);
const { removeRecordFilter } = useRemoveRecordFilter(recordIndexId);
const { closeSidePanelMenu } = useSidePanelMenu();
const handleExecute = () => {
if (!isDefined(deletedFilter)) {
@@ -42,6 +44,7 @@ export const HideDeletedRecordsNoSelectionRecordCommand = () => {
removeRecordFilter({ recordFilterId: deletedFilter.id });
toggleSoftDeleteFilterState(false);
closeSidePanelMenu();
};
return <HeadlessEngineCommandWrapperEffect execute={handleExecute} />;
@@ -1,6 +1,7 @@
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
import { useOpenObjectRecordsSpreadsheetImportDialog } from '@/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isDefined } from 'twenty-shared/utils';
export const ImportRecordsNoSelectionRecordCommand = () => {
@@ -14,10 +15,12 @@ export const ImportRecordsNoSelectionRecordCommand = () => {
useOpenObjectRecordsSpreadsheetImportDialog(
objectMetadataItem.nameSingular,
);
const { closeSidePanelMenu } = useSidePanelMenu();
return (
<HeadlessEngineCommandWrapperEffect
execute={openObjectRecordsSpreadsheetImportDialog}
/>
);
const handleExecute = () => {
closeSidePanelMenu();
openObjectRecordsSpreadsheetImportDialog();
};
return <HeadlessEngineCommandWrapperEffect execute={handleExecute} />;
};
@@ -1,6 +1,7 @@
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isDefined } from 'twenty-shared/utils';
export const SeeDeletedRecordsNoSelectionRecordCommand = () => {
@@ -18,12 +19,14 @@ export const SeeDeletedRecordsNoSelectionRecordCommand = () => {
viewBarId: recordIndexId,
recordFiltersInstanceId: recordIndexId,
});
const { closeSidePanelMenu } = useSidePanelMenu();
return (
<HeadlessEngineCommandWrapperEffect
execute={() => {
handleToggleTrashColumnFilter();
toggleSoftDeleteFilterState(true);
closeSidePanelMenu();
}}
/>
);