77315b7f6e
## PR description The previous approach used a detached flag to represent selection mode, which could was not synced sync with actual UI selection state. This change ties the edit mode's selection/fallback behavior directly to whether records are truly selected in the record index. - Removes `commandMenuItemEditSelectionModeState` (a standalone 'none' | 'selection' atom) and replaces it with `mainContextStoreHasSelectedRecordsSelector`, which derives selection state from the real record selection in the context store. - Adds `useSelectFirstRecordForEditMode` to programmatically select the first record (table row or kanban card) when toggling to selection mode, and useResetRecordIndexSelection to clear selection when toggling off or exiting edit mode. - Ensures record selection is properly cleaned up when exiting layout customization mode or closing the side panel. ## Video QA ### Table https://github.com/user-attachments/assets/1d845809-8369-4872-b3a9-a0281b8e464b ### Board https://github.com/user-attachments/assets/325c2d58-4ca0-408a-ab4a-66b97d9d70de
119 lines
4.5 KiB
TypeScript
119 lines
4.5 KiB
TypeScript
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
|
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
|
import { useResetRecordIndexSelection } from '@/object-record/record-index/hooks/useResetRecordIndexSelection';
|
|
import { selectedNavigationMenuItemIdInEditModeState } from '@/navigation-menu-item/common/states/selectedNavigationMenuItemIdInEditModeState';
|
|
import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId';
|
|
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
|
|
import { isSidePanelClosingState } from '@/side-panel/states/isSidePanelClosingState';
|
|
import { sidePanelNavigationStackState } from '@/side-panel/states/sidePanelNavigationStackState';
|
|
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
|
|
import { sidePanelSearchObjectFilterState } from '@/side-panel/states/sidePanelSearchObjectFilterState';
|
|
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
|
|
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
|
|
import { emitSidePanelOpenEvent } from '@/ui/layout/side-panel/utils/emitSidePanelOpenEvent';
|
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
|
import { t } from '@lingui/core/macro';
|
|
import { useStore } from 'jotai';
|
|
import { useCallback } from 'react';
|
|
import { SidePanelPages } from 'twenty-shared/types';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import { IconColumnInsertRight, IconDotsVertical } from 'twenty-ui/display';
|
|
|
|
export const useSidePanelMenu = () => {
|
|
const store = useStore();
|
|
const { navigateSidePanel } = useNavigateSidePanel();
|
|
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
|
|
const { resetRecordIndexSelection } = useResetRecordIndexSelection(
|
|
MAIN_CONTEXT_STORE_INSTANCE_ID,
|
|
);
|
|
|
|
const { removeFocusItemFromFocusStackById } =
|
|
useRemoveFocusItemFromFocusStackById();
|
|
|
|
const closeSidePanelMenu = useCallback(() => {
|
|
const isSidePanelOpened = store.get(isSidePanelOpenedState.atom);
|
|
|
|
if (isSidePanelOpened) {
|
|
const isLayoutCustomizationModeEnabled = store.get(
|
|
isLayoutCustomizationModeEnabledState.atom,
|
|
);
|
|
|
|
if (isLayoutCustomizationModeEnabled) {
|
|
resetRecordIndexSelection();
|
|
}
|
|
|
|
store.set(sidePanelNavigationStackState.atom, []);
|
|
store.set(isSidePanelOpenedState.atom, false);
|
|
store.set(isSidePanelClosingState.atom, true);
|
|
closeAnyOpenDropdown();
|
|
removeFocusItemFromFocusStackById({
|
|
focusId: SIDE_PANEL_FOCUS_ID,
|
|
});
|
|
}
|
|
}, [
|
|
closeAnyOpenDropdown,
|
|
removeFocusItemFromFocusStackById,
|
|
store,
|
|
resetRecordIndexSelection,
|
|
]);
|
|
|
|
const openSidePanelMenu = useCallback(() => {
|
|
emitSidePanelOpenEvent();
|
|
closeAnyOpenDropdown();
|
|
const isLayoutCustomizationModeEnabled = store.get(
|
|
isLayoutCustomizationModeEnabledState.atom,
|
|
);
|
|
|
|
const selectedNavigationItemId = store.get(
|
|
selectedNavigationMenuItemIdInEditModeState.atom,
|
|
);
|
|
if (
|
|
isLayoutCustomizationModeEnabled &&
|
|
isDefined(selectedNavigationItemId)
|
|
) {
|
|
navigateSidePanel({
|
|
page: SidePanelPages.NavigationMenuItemEdit,
|
|
pageTitle: t`Edit`,
|
|
pageIcon: IconDotsVertical,
|
|
resetNavigationStack: true,
|
|
});
|
|
} else if (isLayoutCustomizationModeEnabled) {
|
|
navigateSidePanel({
|
|
page: SidePanelPages.NavigationMenuAddItem,
|
|
pageTitle: t`New menu item`,
|
|
pageIcon: IconColumnInsertRight,
|
|
resetNavigationStack: true,
|
|
});
|
|
} else {
|
|
navigateSidePanel({
|
|
page: SidePanelPages.CommandMenuDisplay,
|
|
pageTitle: t`Command Menu`,
|
|
pageIcon: IconDotsVertical,
|
|
resetNavigationStack: true,
|
|
});
|
|
}
|
|
}, [closeAnyOpenDropdown, navigateSidePanel, store]);
|
|
|
|
const navigateSidePanelMenu = navigateSidePanel;
|
|
|
|
const toggleSidePanelMenu = useCallback(() => {
|
|
const isSidePanelOpened = store.get(isSidePanelOpenedState.atom);
|
|
|
|
store.set(sidePanelSearchState.atom, '');
|
|
store.set(sidePanelSearchObjectFilterState.atom, null);
|
|
|
|
if (isSidePanelOpened) {
|
|
closeSidePanelMenu();
|
|
} else {
|
|
openSidePanelMenu();
|
|
}
|
|
}, [closeSidePanelMenu, openSidePanelMenu, store]);
|
|
|
|
return {
|
|
openSidePanelMenu,
|
|
closeSidePanelMenu,
|
|
navigateSidePanelMenu,
|
|
toggleSidePanelMenu,
|
|
};
|
|
};
|