Replace hotkey scopes by focus stack (Part 6 - Remove Hotkey scopes 🫳🎤) (#13127)
# Replace hotkey scopes by focus stack (Part 6 - Remove Hotkey scopes) This PR is the last 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 Part 2: https://github.com/twentyhq/twenty/pull/12798 Part 3: https://github.com/twentyhq/twenty/pull/12910 Part 4: https://github.com/twentyhq/twenty/pull/12933 Part 5: https://github.com/twentyhq/twenty/pull/13106 In this part, we completely remove the hotkey scopes.
This commit is contained in:
@ -0,0 +1,324 @@
|
||||
import { DEFAULT_RECORD_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig';
|
||||
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys';
|
||||
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
|
||||
import { ActionConfig } from '@/action-menu/actions/types/ActionConfig';
|
||||
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
|
||||
import { ActionType } from '@/action-menu/actions/types/ActionType';
|
||||
import { DefaultRecordActionConfigKeys } from '@/action-menu/actions/types/DefaultRecordActionConfigKeys';
|
||||
import { IconHeart, IconPlus } from 'twenty-ui/display';
|
||||
import { inheritActionsFromDefaultConfig } from '../inheritActionsFromDefaultConfig';
|
||||
|
||||
const MockComponent = <div>Mock Component</div>;
|
||||
|
||||
describe('inheritActionsFromDefaultConfig', () => {
|
||||
it('should return empty object when no action keys are provided', () => {
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: {},
|
||||
actionKeys: [],
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return only provided config when no default action keys are specified', () => {
|
||||
const customConfig: Record<string, ActionConfig> = {
|
||||
'custom-action': {
|
||||
type: ActionType.Standard,
|
||||
scope: ActionScope.Object,
|
||||
key: 'custom-action',
|
||||
label: 'Custom Action',
|
||||
position: 100,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: () => true,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
actionKeys: [],
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual(customConfig);
|
||||
});
|
||||
|
||||
it('should inherit actions from default config', () => {
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordActionKeys.ADD_TO_FAVORITES,
|
||||
];
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: {},
|
||||
actionKeys,
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]:
|
||||
DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
[SingleRecordActionKeys.ADD_TO_FAVORITES]:
|
||||
DEFAULT_RECORD_ACTIONS_CONFIG[SingleRecordActionKeys.ADD_TO_FAVORITES],
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite specific properties of inherited actions', () => {
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]: {
|
||||
label: 'Custom Create Label',
|
||||
position: 999,
|
||||
isPinned: false,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: {},
|
||||
actionKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
const expectedAction = {
|
||||
...DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
label: 'Custom Create Label',
|
||||
position: 999,
|
||||
isPinned: false,
|
||||
};
|
||||
|
||||
expect(result).toEqual({
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]: expectedAction,
|
||||
});
|
||||
});
|
||||
|
||||
it('should overwrite properties for multiple actions', () => {
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordActionKeys.ADD_TO_FAVORITES,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]: {
|
||||
position: 10,
|
||||
},
|
||||
[SingleRecordActionKeys.ADD_TO_FAVORITES]: {
|
||||
label: 'Custom Favorite Label',
|
||||
Icon: IconHeart,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: {},
|
||||
actionKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]).toEqual({
|
||||
...DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
position: 10,
|
||||
});
|
||||
|
||||
expect(result[SingleRecordActionKeys.ADD_TO_FAVORITES]).toEqual({
|
||||
...DEFAULT_RECORD_ACTIONS_CONFIG[SingleRecordActionKeys.ADD_TO_FAVORITES],
|
||||
label: 'Custom Favorite Label',
|
||||
Icon: IconHeart,
|
||||
});
|
||||
});
|
||||
|
||||
it('should only overwrite properties for specified actions', () => {
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordActionKeys.ADD_TO_FAVORITES,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]: {
|
||||
position: 10,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: {},
|
||||
actionKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]).toEqual({
|
||||
...DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
position: 10,
|
||||
});
|
||||
|
||||
expect(result[SingleRecordActionKeys.ADD_TO_FAVORITES]).toEqual(
|
||||
DEFAULT_RECORD_ACTIONS_CONFIG[SingleRecordActionKeys.ADD_TO_FAVORITES],
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge inherited actions with provided config', () => {
|
||||
const customConfig: Record<string, ActionConfig> = {
|
||||
'custom-action': {
|
||||
type: ActionType.Standard,
|
||||
scope: ActionScope.Object,
|
||||
key: 'custom-action',
|
||||
label: 'Custom Action',
|
||||
position: 100,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: () => true,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
actionKeys,
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]:
|
||||
DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
'custom-action': customConfig['custom-action'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should prioritize provided config over inherited actions when keys conflict', () => {
|
||||
const customConfig: Record<string, ActionConfig> = {
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]: {
|
||||
type: ActionType.Standard,
|
||||
scope: ActionScope.Object,
|
||||
key: NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
label: 'Overridden Create Action',
|
||||
position: 999,
|
||||
Icon: IconHeart,
|
||||
shouldBeRegistered: () => false,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
actionKeys,
|
||||
propertiesToOverwrite: {},
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]).toEqual(
|
||||
customConfig[NoSelectionRecordActionKeys.CREATE_NEW_RECORD],
|
||||
);
|
||||
expect(result[NoSelectionRecordActionKeys.CREATE_NEW_RECORD].label).toBe(
|
||||
'Overridden Create Action',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle complex scenario with inheritance, overrides, and custom config', () => {
|
||||
const customConfig: Record<string, ActionConfig> = {
|
||||
'custom-action-1': {
|
||||
type: ActionType.Standard,
|
||||
scope: ActionScope.Object,
|
||||
key: 'custom-action-1',
|
||||
label: 'Custom Action 1',
|
||||
position: 50,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: () => true,
|
||||
component: MockComponent,
|
||||
},
|
||||
[SingleRecordActionKeys.ADD_TO_FAVORITES]: {
|
||||
type: ActionType.Standard,
|
||||
scope: ActionScope.RecordSelection,
|
||||
key: SingleRecordActionKeys.ADD_TO_FAVORITES,
|
||||
label: 'Completely Custom Favorites',
|
||||
position: 1000,
|
||||
Icon: IconHeart,
|
||||
shouldBeRegistered: () => false,
|
||||
component: MockComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
SingleRecordActionKeys.ADD_TO_FAVORITES,
|
||||
SingleRecordActionKeys.REMOVE_FROM_FAVORITES,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]: {
|
||||
label: 'Modified Create Label',
|
||||
position: 5,
|
||||
},
|
||||
[SingleRecordActionKeys.REMOVE_FROM_FAVORITES]: {
|
||||
isPinned: false,
|
||||
},
|
||||
};
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: customConfig,
|
||||
actionKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(Object.keys(result)).toHaveLength(4);
|
||||
|
||||
expect(result['custom-action-1']).toEqual(customConfig['custom-action-1']);
|
||||
|
||||
expect(result[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]).toEqual({
|
||||
...DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
label: 'Modified Create Label',
|
||||
position: 5,
|
||||
});
|
||||
|
||||
expect(result[SingleRecordActionKeys.ADD_TO_FAVORITES]).toEqual(
|
||||
customConfig[SingleRecordActionKeys.ADD_TO_FAVORITES],
|
||||
);
|
||||
|
||||
expect(result[SingleRecordActionKeys.REMOVE_FROM_FAVORITES]).toEqual({
|
||||
...DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
SingleRecordActionKeys.REMOVE_FROM_FAVORITES
|
||||
],
|
||||
isPinned: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty overrides gracefully', () => {
|
||||
const actionKeys: DefaultRecordActionConfigKeys[] = [
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD,
|
||||
];
|
||||
|
||||
const propertiesToOverwrite = {
|
||||
[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]: {},
|
||||
};
|
||||
|
||||
const result = inheritActionsFromDefaultConfig({
|
||||
config: {},
|
||||
actionKeys,
|
||||
propertiesToOverwrite,
|
||||
});
|
||||
|
||||
expect(result[NoSelectionRecordActionKeys.CREATE_NEW_RECORD]).toEqual(
|
||||
DEFAULT_RECORD_ACTIONS_CONFIG[
|
||||
NoSelectionRecordActionKeys.CREATE_NEW_RECORD
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,5 @@
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { getOsControlSymbol } from 'twenty-ui/utilities';
|
||||
@ -18,7 +17,6 @@ export const CmdEnterActionButton = ({
|
||||
keys: [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
|
||||
callback: () => onClick(),
|
||||
focusId: SIDE_PANEL_FOCUS_ID,
|
||||
scope: AppHotkeyScope.CommandMenuOpen,
|
||||
dependencies: [onClick],
|
||||
});
|
||||
|
||||
|
||||
@ -7,12 +7,10 @@ import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
|
||||
import { useToggleDropdown } from '@/ui/layout/dropdown/hooks/useToggleDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useContext } from 'react';
|
||||
@ -39,7 +37,6 @@ export const CommandMenuActionMenuDropdown = () => {
|
||||
dropdownComponentInstanceIdFromProps: dropdownId,
|
||||
});
|
||||
},
|
||||
scope: AppHotkeyScope.CommandMenuOpen,
|
||||
dependencies: [toggleDropdown],
|
||||
};
|
||||
|
||||
@ -86,7 +83,6 @@ export const CommandMenuActionMenuDropdown = () => {
|
||||
selectableListInstanceId={actionMenuId}
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
hotkeyScope={DropdownHotkeyScope.Dropdown}
|
||||
>
|
||||
{recordSelectionActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
|
||||
@ -10,7 +10,6 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
@ -89,7 +88,6 @@ export const RecordIndexActionMenuDropdown = () => {
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={selectedItemIdArray}
|
||||
selectableListInstanceId={dropdownId}
|
||||
hotkeyScope={DropdownHotkeyScope.Dropdown}
|
||||
>
|
||||
{recordIndexActions.map((action) => (
|
||||
<ActionComponent action={action} key={action.key} />
|
||||
|
||||
@ -13,7 +13,6 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
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 { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
@ -123,7 +122,6 @@ export const RecordShowRightDrawerOpenRecordButton = ({
|
||||
keys: ['ctrl+Enter,meta+Enter'],
|
||||
callback: handleOpenRecord,
|
||||
focusId: SIDE_PANEL_FOCUS_ID,
|
||||
scope: AppHotkeyScope.CommandMenuOpen,
|
||||
dependencies: [handleOpenRecord],
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user