608 fix hotkey scope and dropdown issues in the command menu (#11121)

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

- Introduces a new hotkey scope `CommandMenuFocused`
- Fixes hotkey scopes issues in the side panel
This commit is contained in:
Raphaël Bosi
2025-03-24 15:34:09 +01:00
committed by GitHub
parent 6898a40ac3
commit 0084946b76
10 changed files with 45 additions and 36 deletions

View File

@ -12,7 +12,6 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { i18n } from '@lingui/core'; import { i18n } from '@lingui/core';
import { Key } from 'ts-key-enum';
import { Button, getOsControlSymbol, MenuItem } from 'twenty-ui'; import { Button, getOsControlSymbol, MenuItem } from 'twenty-ui';
export const CommandMenuActionMenuDropdown = () => { export const CommandMenuActionMenuDropdown = () => {
@ -24,25 +23,14 @@ export const CommandMenuActionMenuDropdown = () => {
ActionMenuComponentInstanceContext, ActionMenuComponentInstanceContext,
); );
const { closeDropdown, openDropdown } = useDropdownV2(); const { toggleDropdown } = useDropdownV2();
const theme = useTheme(); const theme = useTheme();
useScopedHotkeys(
[Key.Escape, 'ctrl+o,meta+o'],
() => {
closeDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
);
},
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown,
[closeDropdown],
);
useScopedHotkeys( useScopedHotkeys(
['ctrl+o,meta+o'], ['ctrl+o,meta+o'],
() => { () => {
openDropdown( toggleDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId), getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
{ {
scope: scope:
@ -51,7 +39,7 @@ export const CommandMenuActionMenuDropdown = () => {
); );
}, },
AppHotkeyScope.CommandMenuOpen, AppHotkeyScope.CommandMenuOpen,
[openDropdown], [toggleDropdown],
); );
return ( return (
@ -81,7 +69,7 @@ export const CommandMenuActionMenuDropdown = () => {
key={index} key={index}
LeftIcon={actionMenuEntry.Icon} LeftIcon={actionMenuEntry.Icon}
onClick={() => { onClick={() => {
closeDropdown( toggleDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId( getRightDrawerActionMenuDropdownIdFromActionMenuId(
actionMenuId, actionMenuId,
), ),

View File

@ -1,4 +1,5 @@
import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -6,10 +7,12 @@ import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState'; import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@ -63,6 +66,12 @@ export const RecordShowRightDrawerOpenRecordButton = ({
const navigate = useNavigateApp(); const navigate = useNavigateApp();
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
ActionMenuComponentInstanceContext,
);
const { closeDropdown } = useDropdownV2();
const handleOpenRecord = useCallback(() => { const handleOpenRecord = useCallback(() => {
const tabIdToOpen = const tabIdToOpen =
activeTabIdInRightDrawer === 'home' activeTabIdInRightDrawer === 'home'
@ -79,10 +88,16 @@ export const RecordShowRightDrawerOpenRecordButton = ({
objectRecordId: recordId, objectRecordId: recordId,
}); });
closeDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
);
closeCommandMenu(); closeCommandMenu();
}, [ }, [
actionMenuId,
activeTabIdInRightDrawer, activeTabIdInRightDrawer,
closeCommandMenu, closeCommandMenu,
closeDropdown,
navigate, navigate,
objectNameSingular, objectNameSingular,
recordId, recordId,
@ -96,13 +111,6 @@ export const RecordShowRightDrawerOpenRecordButton = ({
[closeCommandMenu, navigate, objectNameSingular, recordId], [closeCommandMenu, navigate, objectNameSingular, recordId],
); );
useScopedHotkeys(
['ctrl+Enter,meta+Enter'],
handleOpenRecord,
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown,
[closeCommandMenu, navigate, objectNameSingular, recordId],
);
if (!isDefined(record)) { if (!isDefined(record)) {
return null; return null;
} }

View File

@ -17,7 +17,6 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey'; import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
@ -26,6 +25,7 @@ import { useDebouncedCallback } from 'use-debounce';
import { ActivityRichTextEditorChangeOnActivityIdEffect } from '@/activities/components/ActivityRichTextEditorChangeOnActivityIdEffect'; import { ActivityRichTextEditorChangeOnActivityIdEffect } from '@/activities/components/ActivityRichTextEditorChangeOnActivityIdEffect';
import { Note } from '@/activities/types/Note'; import { Note } from '@/activities/types/Note';
import { Task } from '@/activities/types/Task'; import { Task } from '@/activities/types/Task';
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor'; import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
import { PartialBlock } from '@blocknote/core'; import { PartialBlock } from '@blocknote/core';
import '@blocknote/core/fonts/inter.css'; import '@blocknote/core/fonts/inter.css';
@ -298,7 +298,7 @@ export const ActivityRichTextEditor = ({
editor.setTextCursorPosition(newBlockId, 'end'); editor.setTextCursorPosition(newBlockId, 'end');
editor.focus(); editor.focus();
}, },
AppHotkeyScope.CommandMenuOpen, CommandMenuHotkeyScope.CommandMenuFocused,
[], [],
{ {
preventDefault: false, preventDefault: false,

View File

@ -14,6 +14,7 @@ import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKey
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant'; import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
@ -23,7 +24,6 @@ import { RecordFiltersComponentInstanceContext } from '@/object-record/record-fi
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
@ -74,7 +74,7 @@ export const CommandMenuContainer = ({
.getLoadable(currentHotkeyScopeState) .getLoadable(currentHotkeyScopeState)
.getValue(); .getValue();
if (hotkeyScope.scope === AppHotkeyScope.CommandMenuOpen) { if (hotkeyScope?.scope === CommandMenuHotkeyScope.CommandMenuFocused) {
closeCommandMenu(); closeCommandMenu();
} }
}, },

View File

@ -3,12 +3,12 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui'; import { MenuItem } from 'twenty-ui';
import { import {
CommandMenuContextChip, CommandMenuContextChip,
CommandMenuContextChipProps, CommandMenuContextChipProps,
} from './CommandMenuContextChip'; } from './CommandMenuContextChip';
import { isDefined } from 'twenty-shared/utils';
export const CommandMenuContextChipGroups = ({ export const CommandMenuContextChipGroups = ({
contextChips, contextChips,

View File

@ -5,6 +5,7 @@ import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useO
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext'; import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu'; import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
@ -62,7 +63,7 @@ export const useCommandMenuHotKeys = () => {
() => { () => {
goBackFromCommandMenu(); goBackFromCommandMenu();
}, },
AppHotkeyScope.CommandMenuOpen, CommandMenuHotkeyScope.CommandMenuFocused,
[goBackFromCommandMenu], [goBackFromCommandMenu],
); );
@ -87,7 +88,7 @@ export const useCommandMenuHotKeys = () => {
goBackFromCommandMenu(); goBackFromCommandMenu();
} }
}, },
AppHotkeyScope.CommandMenuOpen, CommandMenuHotkeyScope.CommandMenuFocused,
[ [
commandMenuPage, commandMenuPage,
commandMenuSearch, commandMenuSearch,

View File

@ -9,11 +9,11 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState'; import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState'; import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState'; import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { IconComponent } from 'twenty-ui'; import { IconComponent } from 'twenty-ui';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -49,7 +49,12 @@ export const useNavigateCommandMenu = () => {
commandMenuCloseAnimationCompleteCleanup(); commandMenuCloseAnimationCompleteCleanup();
} }
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen); setHotkeyScopeAndMemorizePreviousScope(
CommandMenuHotkeyScope.CommandMenuFocused,
{
commandMenuOpen: true,
},
);
if (isCommandMenuOpened) { if (isCommandMenuOpened) {
return; return;

View File

@ -0,0 +1,3 @@
export enum CommandMenuHotkeyScope {
CommandMenuFocused = 'command-menu-focused',
}

View File

@ -62,7 +62,7 @@ export const useDropdownV2 = () => {
const toggleDropdown = useRecoilCallback( const toggleDropdown = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(specificComponentId: string) => { (specificComponentId: string, customHotkeyScope?: HotkeyScope) => {
const scopeId = getScopeIdFromComponentId(specificComponentId); const scopeId = getScopeIdFromComponentId(specificComponentId);
const isDropdownOpen = snapshot const isDropdownOpen = snapshot
.getLoadable(isDropdownOpenComponentState({ scopeId })) .getLoadable(isDropdownOpenComponentState({ scopeId }))
@ -71,7 +71,7 @@ export const useDropdownV2 = () => {
if (isDropdownOpen) { if (isDropdownOpen) {
closeDropdown(specificComponentId); closeDropdown(specificComponentId);
} else { } else {
openDropdown(specificComponentId); openDropdown(specificComponentId, customHotkeyScope);
} }
}, },
[closeDropdown, openDropdown], [closeDropdown, openDropdown],

View File

@ -3,13 +3,13 @@ import { useRecoilCallback } from 'recoil';
import { DEBUG_HOTKEY_SCOPE } from '@/ui/utilities/hotkey/hooks/useScopedHotkeyCallback'; import { DEBUG_HOTKEY_SCOPE } from '@/ui/utilities/hotkey/hooks/useScopedHotkeyCallback';
import { logDebug } from '~/utils/logDebug'; import { logDebug } from '~/utils/logDebug';
import { isDefined } from 'twenty-shared/utils';
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants/DefaultHotkeysScopeCustomScopes'; import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants/DefaultHotkeysScopeCustomScopes';
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState'; import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState'; import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
import { AppHotkeyScope } from '../types/AppHotkeyScope'; import { AppHotkeyScope } from '../types/AppHotkeyScope';
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope'; import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
import { HotkeyScope } from '../types/HotkeyScope'; import { HotkeyScope } from '../types/HotkeyScope';
import { isDefined } from 'twenty-shared/utils';
const isCustomScopesEqual = ( const isCustomScopesEqual = (
customScopesA: CustomHotkeyScopes | undefined, customScopesA: CustomHotkeyScopes | undefined,
@ -69,6 +69,10 @@ export const useSetHotkeyScope = () =>
scopesToSet.push(AppHotkeyScope.CommandMenu); scopesToSet.push(AppHotkeyScope.CommandMenu);
} }
if (newHotkeyScope.customScopes?.commandMenuOpen === true) {
scopesToSet.push(AppHotkeyScope.CommandMenuOpen);
}
if (newHotkeyScope?.customScopes?.goto === true) { if (newHotkeyScope?.customScopes?.goto === true) {
scopesToSet.push(AppHotkeyScope.Goto); scopesToSet.push(AppHotkeyScope.Goto);
} }