Add focus-safe side panel shortcuts (#22499)

## Summary

- Add side-panel-owned Escape and Backspace behavior for the side-panel
search input.
- Keep side-panel Escape scoped to side-panel focus and avoid
left-content fallback behavior.
- Add a Side Panel group to the keyboard shortcut menu.
- Reuse the side-panel focus id for AI chat thread-list shortcuts.


## Demo


https://github.com/user-attachments/assets/80a632d6-7ff7-496b-905f-a3f95f9cfc14

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas des Francs
2026-07-05 01:19:39 +02:00
committed by GitHub
parent f14e62ec0c
commit a4ed561e11
15 changed files with 410 additions and 39 deletions
@@ -1,17 +1,13 @@
import { AI_CHAT_THREADS_LIST_FOCUS_ID } from '@/ai/constants/AiChatThreadsListFocusId';
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId';
import { useHandleSidePanelEscape } from '@/side-panel/hooks/useHandleSidePanelEscape';
import { useOpenAskAiPageInSidePanel } from '@/side-panel/hooks/useOpenAskAiPageInSidePanel';
import { useOpenRecordsSearchPageInSidePanel } from '@/side-panel/hooks/useOpenRecordsSearchPageInSidePanel';
import { useSidePanelHistory } from '@/side-panel/hooks/useSidePanelHistory';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
import { useGlobalHotkeys } from '@/ui/utilities/hotkey/hooks/useGlobalHotkeys';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isNonEmptyString } from '@sniptt/guards';
import { Key } from 'ts-key-enum';
import { SidePanelPages } from 'twenty-shared/types';
export const useCommandMenuHotKeys = () => {
const { toggleSidePanelMenu } = useSidePanelMenu();
@@ -20,15 +16,10 @@ export const useCommandMenuHotKeys = () => {
const { openAskAiPage } = useOpenAskAiPageInSidePanel();
const { goBackFromSidePanel, goBackOneSubPageOrMainPage } =
useSidePanelHistory();
const sidePanelSearch = useAtomStateValue(sidePanelSearchState);
const handleSidePanelEscape = useHandleSidePanelEscape();
const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
const sidePanelPage = useAtomStateValue(sidePanelPageState);
useGlobalHotkeys({
keys: ['ctrl+k', 'meta+k'],
callback: () => {
@@ -66,30 +57,23 @@ export const useCommandMenuHotKeys = () => {
useHotkeysOnFocusedElement({
keys: [Key.Escape],
callback: () => {
goBackFromSidePanel();
handleSidePanelEscape();
},
focusId: SIDE_PANEL_FOCUS_ID,
dependencies: [goBackFromSidePanel],
dependencies: [handleSidePanelEscape],
options: {
enableOnFormTags: false,
},
});
useHotkeysOnFocusedElement({
keys: [Key.Backspace, Key.Delete],
keys: [Key.Escape],
callback: () => {
if (isNonEmptyString(sidePanelSearch)) {
return;
}
if (sidePanelPage !== SidePanelPages.CommandMenuDisplay) {
goBackOneSubPageOrMainPage();
}
handleSidePanelEscape();
},
focusId: SIDE_PANEL_FOCUS_ID,
dependencies: [sidePanelPage, sidePanelSearch, goBackOneSubPageOrMainPage],
focusId: AI_CHAT_THREADS_LIST_FOCUS_ID,
dependencies: [handleSidePanelEscape],
options: {
preventDefault: false,
enableOnFormTags: false,
},
});