Replace hotkey scopes by focus stack (Part 2 - Record Table, Rows and Cells) (#12798)

# Replace hotkey scopes by focus stack (Part 2 - Record Table, Rows and
Cells)

This PR is the second part of a refactoring aiming to deprecate the
hotkey scopes api in favor of the new focus stack api which is more
robust.
Part 1: https://github.com/twentyhq/twenty/pull/12673

The record table shortcuts are no longer centralized in the record
table, they now split and the focused element is in charge of applying
the desired shortcuts. (For instance: The rows are in charge of the row
navigation and the cells of the cells navigation).

## Video QA:


https://github.com/user-attachments/assets/f0bb9eed-8a2a-4b6d-a82f-1998e929f122


## Bugfixes:

### Fix record table click outside not working after opening and closing
a cell

Introduced by https://github.com/twentyhq/twenty/pull/11644

#### Before


https://github.com/user-attachments/assets/d28deda8-15e9-4ffe-b60a-e8b54625f8e5


#### After


https://github.com/user-attachments/assets/3f7e1ffc-15d9-4336-aeb0-bebd8ae0cbe0


### Fix ObjectFilterDropdownFilterInput hotkeys

Introduced by https://github.com/twentyhq/twenty/pull/12673

#### Before


https://github.com/user-attachments/assets/ab2039bd-ebe1-49ba-8377-a6b300664469


#### After


https://github.com/user-attachments/assets/90597453-dab2-426b-a134-0a24b0de0a6b
This commit is contained in:
Raphaël Bosi
2025-06-25 15:18:51 +02:00
committed by GitHub
parent 6450e11f1e
commit 1ab51d41aa
80 changed files with 1246 additions and 847 deletions

View File

@ -23,7 +23,7 @@ import { IconList } from 'twenty-ui/display';
const mockCloseDropdown = jest.fn();
const mockResetContextStoreStates = jest.fn();
const mockResetSelectedItem = jest.fn();
const mockEmitRightDrawerCloseEvent = jest.fn();
const mockEmitSidePanelCloseEvent = jest.fn();
jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
useDropdownV2: () => ({
@ -43,9 +43,9 @@ jest.mock('@/ui/layout/selectable-list/hooks/useSelectableList', () => ({
}),
}));
jest.mock('@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent', () => ({
emitRightDrawerCloseEvent: () => {
mockEmitRightDrawerCloseEvent();
jest.mock('@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent', () => ({
emitSidePanelCloseEvent: () => {
mockEmitSidePanelCloseEvent();
},
}));
@ -224,7 +224,7 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
expect(mockCloseDropdown).toHaveBeenCalledTimes(1);
expect(mockResetContextStoreStates).toHaveBeenCalledTimes(2);
expect(mockResetSelectedItem).toHaveBeenCalledTimes(1);
expect(mockEmitRightDrawerCloseEvent).toHaveBeenCalledTimes(1);
expect(mockEmitSidePanelCloseEvent).toHaveBeenCalledTimes(1);
expect(mockCloseDropdown).toHaveBeenCalledWith(
COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID,

View File

@ -2,14 +2,14 @@ import { useRecoilCallback } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { emitSidePanelOpenEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { useCallback } from 'react';
import { IconDotsVertical } from 'twenty-ui/display';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
@ -18,7 +18,8 @@ export const useCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack();
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const closeCommandMenu = useRecoilCallback(
({ set, snapshot }) =>
@ -32,16 +33,16 @@ export const useCommandMenu = () => {
set(isCommandMenuClosingState, true);
set(isDragSelectionStartEnabledState, true);
closeAnyOpenDropdown();
removeFocusItemFromFocusStack({
removeFocusItemFromFocusStackById({
focusId: SIDE_PANEL_FOCUS_ID,
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
}
},
[closeAnyOpenDropdown, removeFocusItemFromFocusStack],
[closeAnyOpenDropdown, removeFocusItemFromFocusStackById],
);
const openCommandMenu = useCallback(() => {
emitSidePanelOpenEvent();
closeAnyOpenDropdown();
navigateCommandMenu({
page: CommandMenuPages.Root,

View File

@ -14,7 +14,7 @@ import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpe
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
import { emitSidePanelCloseEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
@ -52,7 +52,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
resetSelectedItem();
set(hasUserSelectedCommandState, false);
emitRightDrawerCloseEvent();
emitSidePanelCloseEvent();
set(isCommandMenuClosingState, false);
set(
activeTabIdComponentState.atomFamily({