Replace hotkey scopes by focus stack (Part 1 - Dropdowns and Side Panel) (#12673)

This PR is the first part of a refactoring aiming to deprecate the
hotkey scopes api in favor of the new focus stack api which is more
robust.

The refactored components in this PR are the dropdowns and the side
panel/command menu.

- Replaced `useScopedHotkeys` by `useHotkeysOnFocusedElement` for all
dropdown components, selectable lists and the command menu
- Introduced `focusId` for all dropdowns and created a common hotkey
scope `DropdownHotkeyScope` for backward compatibility
- Replaced `setHotkeyScopeAndMemorizePreviousScope` occurrences with
`usePushFocusItemToFocusStack` and `goBackToPreviousHotkeyScope` with
`removeFocusItemFromFocusStack`

Note: Test that the shorcuts and arrow key navigation still work
properly when interacting with dropdowns and the command menu.

Bugs that I have spotted during the QA but which are already present on
main:
- Icon picker select with arrow keys doesn’t work inside dropdowns
- Some dropdowns are not selectable with arrow keys (no selectable list)
- Dropdowns in dropdowns don’t reset the hotkey scope correctly when
closing
- The table click outside is not triggered after closing a table cell
and clicking outside of the table
This commit is contained in:
Raphaël Bosi
2025-06-19 14:53:18 +02:00
committed by GitHub
parent 6dd3a71497
commit cbc0d06a2f
155 changed files with 977 additions and 845 deletions

View File

@ -1,4 +1,5 @@
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
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';
@ -13,12 +14,13 @@ export const CmdEnterActionButton = ({
onClick: () => void;
disabled?: boolean;
}) => {
useScopedHotkeys(
[`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
() => onClick(),
AppHotkeyScope.CommandMenuOpen,
[onClick],
);
useHotkeysOnFocusedElement({
keys: [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
callback: () => onClick(),
focusId: SIDE_PANEL_FOCUS_ID,
scope: AppHotkeyScope.CommandMenuOpen,
dependencies: [onClick],
});
return (
<Button

View File

@ -2,15 +2,16 @@ import { ActionComponent } from '@/action-menu/actions/display/components/Action
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId';
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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
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';
@ -25,24 +26,30 @@ export const CommandMenuActionMenuDropdown = () => {
ActionMenuComponentInstanceContext,
);
const { toggleDropdown } = useDropdownV2();
const theme = useTheme();
useScopedHotkeys(
['ctrl+o,meta+o'],
() => {
toggleDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
{
scope:
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown,
},
);
const dropdownId =
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId);
const { toggleDropdown } = useDropdownV2();
const hotkeysConfig = {
keys: ['ctrl+o', 'meta+o'],
callback: () => {
toggleDropdown(dropdownId);
},
AppHotkeyScope.CommandMenuOpen,
[toggleDropdown],
);
scope: AppHotkeyScope.CommandMenuOpen,
dependencies: [toggleDropdown],
};
useHotkeysOnFocusedElement({
...hotkeysConfig,
focusId: SIDE_PANEL_FOCUS_ID,
});
useHotkeysOnFocusedElement({
...hotkeysConfig,
focusId: dropdownId,
});
const recordSelectionActions = actions.filter(
(action) => action.scope === ActionScope.RecordSelection,
@ -56,19 +63,17 @@ export const CommandMenuActionMenuDropdown = () => {
return (
<Dropdown
dropdownId={getRightDrawerActionMenuDropdownIdFromActionMenuId(
actionMenuId,
)}
dropdownHotkeyScope={{
scope:
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown,
}}
dropdownId={dropdownId}
data-select-disable
clickableComponent={
<Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} />
}
dropdownPlacement="top-end"
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}
globalHotkeysConfig={{
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
}}
onOpen={() => {
setSelectedItemId(selectableItemIdArray[0]);
}}
@ -77,10 +82,9 @@ export const CommandMenuActionMenuDropdown = () => {
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={actionMenuId}
hotkeyScope={
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown
}
focusId={dropdownId}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{recordSelectionActions.map((action) => (
<ActionComponent action={action} key={action.key} />

View File

@ -5,12 +5,12 @@ import { ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/action-menu/constants/A
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@ -72,9 +72,6 @@ export const RecordIndexActionMenuDropdown = () => {
return (
<Dropdown
dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
}}
data-select-disable
dropdownPlacement="bottom-start"
dropdownStrategy="absolute"
@ -89,9 +86,10 @@ export const RecordIndexActionMenuDropdown = () => {
>
<DropdownMenuItemsContainer>
<SelectableList
hotkeyScope={ActionMenuDropdownHotkeyScope.ActionMenuDropdown}
focusId={dropdownId}
selectableItemIdArray={selectedItemIdArray}
selectableListInstanceId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{recordIndexActions.map((action) => (
<ActionComponent action={action} key={action.key} />

View File

@ -1,5 +1,6 @@
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
@ -11,7 +12,7 @@ import { AppPath } from '@/types/AppPath';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
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';
@ -118,12 +119,13 @@ export const RecordShowRightDrawerOpenRecordButton = ({
],
);
useScopedHotkeys(
['ctrl+Enter,meta+Enter'],
handleOpenRecord,
AppHotkeyScope.CommandMenuOpen,
[handleOpenRecord],
);
useHotkeysOnFocusedElement({
keys: ['ctrl+Enter,meta+Enter'],
callback: handleOpenRecord,
focusId: SIDE_PANEL_FOCUS_ID,
scope: AppHotkeyScope.CommandMenuOpen,
dependencies: [handleOpenRecord],
});
if (!isDefined(record)) {
return null;

View File

@ -1,3 +0,0 @@
export enum ActionMenuDropdownHotkeyScope {
ActionMenuDropdown = 'action-menu-dropdown',
}

View File

@ -1,3 +0,0 @@
export enum CommandMenuActionMenuDropdownHotkeyScope {
CommandMenuActionMenuDropdown = 'command-menu-action-menu-dropdown',
}

View File

@ -72,7 +72,6 @@ export const AttachmentDropdown = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -70,6 +70,7 @@ export const ActivityTargetsInlineCell = ({
labelWidth: fieldDefinition?.labelWidth,
editModeContent: (
<MultipleRecordPicker
focusId={componentInstanceId}
componentInstanceId={componentInstanceId}
onClickOutside={() => {
closeInlineCell();

View File

@ -5,8 +5,9 @@ import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-pic
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil';
type OpenActivityTargetCellEditModeProps = {
@ -19,7 +20,7 @@ export const useOpenActivityTargetCellEditMode = () => {
const { performSearch: multipleRecordPickerPerformSearch } =
useMultipleRecordPickerPerformSearch();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openActivityTargetCellEditMode = useRecoilCallback(
({ set, snapshot }) =>
@ -84,11 +85,19 @@ export const useOpenActivityTargetCellEditMode = () => {
),
});
setHotkeyScopeAndMemorizePreviousScope({
scope: MultipleRecordPickerHotkeyScope.MultipleRecordPicker,
pushFocusItemToFocusStack({
focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: recordPickerInstanceId,
});
},
[multipleRecordPickerPerformSearch, setHotkeyScopeAndMemorizePreviousScope],
[multipleRecordPickerPerformSearch, pushFocusItemToFocusStack],
);
return { openActivityTargetCellEditMode };

View File

@ -3,7 +3,6 @@ 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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui/navigation';
import {
@ -72,9 +71,6 @@ export const CommandMenuContextChipGroups = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{
scope: AppHotkeyScope.CommandMenu,
}}
dropdownId={COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID}
dropdownPlacement="bottom-start"
></Dropdown>

View File

@ -4,6 +4,7 @@ import { ActionGroupConfig } from '@/command-menu/components/CommandMenu';
import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
@ -75,8 +76,9 @@ export const CommandMenuList = ({
<StyledInnerList>
<SelectableList
selectableListInstanceId="command-menu-list"
hotkeyScope={AppHotkeyScope.CommandMenuOpen}
focusId={SIDE_PANEL_FOCUS_ID}
selectableItemIdArray={selectableItemIds}
hotkeyScope={AppHotkeyScope.CommandMenuOpen}
onSelect={() => {
setHasUserSelectedCommand(true);
}}

View File

@ -0,0 +1 @@
export const SIDE_PANEL_FOCUS_ID = 'command-menu';

View File

@ -3,12 +3,13 @@ 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 { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack';
import { useCallback } from 'react';
import { IconDotsVertical } from 'twenty-ui/display';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
@ -16,7 +17,8 @@ import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
export const useCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu();
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack();
const closeCommandMenu = useRecoilCallback(
({ set, snapshot }) =>
@ -30,10 +32,13 @@ export const useCommandMenu = () => {
set(isCommandMenuClosingState, true);
set(isDragSelectionStartEnabledState, true);
closeAnyOpenDropdown();
goBackToPreviousHotkeyScope(COMMAND_MENU_COMPONENT_INSTANCE_ID);
removeFocusItemFromFocusStack({
focusId: SIDE_PANEL_FOCUS_ID,
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
}
},
[closeAnyOpenDropdown, goBackToPreviousHotkeyScope],
[closeAnyOpenDropdown, removeFocusItemFromFocusStack],
);
const openCommandMenu = useCallback(() => {

View File

@ -1,4 +1,5 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState';
@ -13,7 +14,8 @@ import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeySc
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil';
import { IconComponent } from 'twenty-ui/display';
import { v4 } from 'uuid';
@ -27,13 +29,13 @@ export type CommandMenuNavigationStackItem = {
};
export const useNavigateCommandMenu = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { copyContextStoreStates } = useCopyContextStoreStates();
const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openCommandMenu = useRecoilCallback(
({ snapshot, set }) =>
() => {
@ -53,10 +55,17 @@ export const useNavigateCommandMenu = () => {
return;
}
setHotkeyScopeAndMemorizePreviousScope({
scope: CommandMenuHotkeyScope.CommandMenuFocused,
customScopes: {
commandMenuOpen: true,
pushFocusItemToFocusStack({
focusId: SIDE_PANEL_FOCUS_ID,
component: {
type: FocusComponentType.SIDE_PANEL,
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
},
hotkeyScope: {
scope: CommandMenuHotkeyScope.CommandMenuFocused,
customScopes: {
commandMenuOpen: true,
},
},
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
@ -73,7 +82,7 @@ export const useNavigateCommandMenu = () => {
[
copyContextStoreStates,
commandMenuCloseAnimationCompleteCleanup,
setHotkeyScopeAndMemorizePreviousScope,
pushFocusItemToFocusStack,
],
);

View File

@ -1,4 +1,3 @@
import { FavoriteFolderHotkeyScope } from '@/favorites/constants/FavoriteFolderRightIconDropdownHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -33,9 +32,6 @@ export const FavoriteFolderNavigationDrawerItemDropdown = ({
return (
<Dropdown
dropdownId={`favorite-folder-edit-${folderId}`}
dropdownHotkeyScope={{
scope: FavoriteFolderHotkeyScope.FavoriteFolderRightIconDropdown,
}}
data-select-disable
clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />

View File

@ -9,7 +9,8 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
@ -57,22 +58,23 @@ export const FavoriteFolderPicker = ({
!favoriteFoldersSearchFilter ||
'no folder'.includes(favoriteFoldersSearchFilter.toLowerCase());
useScopedHotkeys(
Key.Escape,
() => {
useHotkeysOnFocusedElement({
keys: [Key.Escape],
callback: () => {
if (isFavoriteFolderCreating) {
setIsFavoriteFolderCreating(false);
return;
}
onSubmit?.();
},
instanceId,
[onSubmit, isFavoriteFolderCreating],
);
focusId: dropdownId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [onSubmit, isFavoriteFolderCreating],
});
useScopedHotkeys(
Key.Enter,
() => {
useHotkeysOnFocusedElement({
keys: [Key.Enter],
callback: () => {
if (filteredFolders.length === 1 && !showNoFolderOption) {
toggleFolderSelection(filteredFolders[0].id);
onSubmit?.();
@ -85,9 +87,15 @@ export const FavoriteFolderPicker = ({
return;
}
},
instanceId,
[filteredFolders, showNoFolderOption, toggleFolderSelection, onSubmit],
);
focusId: instanceId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [
filteredFolders,
showNoFolderOption,
toggleFolderSelection,
onSubmit,
],
});
return (
<DropdownContent>

View File

@ -180,7 +180,6 @@ export const AdvancedFilterAddFilterRuleSelect = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start"
/>

View File

@ -22,7 +22,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { FieldMetadataType } from 'twenty-shared/types';
type AdvancedFilterDropdownFilterInputProps = {
filterDropdownId?: string;
filterDropdownId: string;
recordFilter: RecordFilter;
};
@ -57,12 +57,15 @@ export const AdvancedFilterDropdownFilterInput = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilter.id} />
<ObjectFilterDropdownRecordSelect
recordFilterId={recordFilter.id}
dropdownId={filterDropdownId}
/>
</DropdownContent>
)}
{filterType === 'ACTOR' &&
(isActorSourceCompositeFilter ? (
<ObjectFilterDropdownSourceSelect />
<ObjectFilterDropdownSourceSelect dropdownId={filterDropdownId} />
) : (
<ObjectFilterDropdownTextInput />
))}
@ -70,7 +73,7 @@ export const AdvancedFilterDropdownFilterInput = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect />
<ObjectFilterDropdownOptionSelect focusId={filterDropdownId} />
</DropdownContent>
)}
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}

View File

@ -34,7 +34,6 @@ export const AdvancedFilterFieldSelectDropdownButton = ({
recordFilterId={recordFilterId}
/>
}
dropdownHotkeyScope={{ scope: advancedFilterFieldSelectDropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start"
/>

View File

@ -23,6 +23,7 @@ import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/uti
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@ -144,9 +145,10 @@ export const AdvancedFilterFieldSelectMenu = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<AdvancedFilterFieldSelectSearchInput />
<SelectableList
hotkeyScope={advancedFilterFieldSelectDropdownId}
focusId={advancedFilterFieldSelectDropdownId}
selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{shouldShowVisibleFields && (
<>

View File

@ -65,7 +65,6 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 2, x: 0 }}
dropdownPlacement="bottom-start"
/>

View File

@ -8,6 +8,7 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -63,11 +64,12 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
<DropdownMenuItemsContainer>
<SelectableList
hotkeyScope={dropdownId}
focusId={dropdownId}
selectableItemIdArray={operandsForFilterType.map(
(operand) => operand,
)}
selectableListInstanceId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{operandsForFilterType.map((filterOperand, index) => (
<SelectableListItem
@ -90,7 +92,6 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start"
/>

View File

@ -85,7 +85,6 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start"
/>

View File

@ -18,6 +18,7 @@ import { CompositeFieldSubFieldName } from '@/settings/data-model/types/Composit
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@ -124,9 +125,10 @@ export const AdvancedFilterSubFieldSelectMenu = ({
</DropdownMenuHeader>
<DropdownMenuItemsContainer>
<SelectableList
hotkeyScope={advancedFilterFieldSelectDropdownId}
focusId={advancedFilterFieldSelectDropdownId}
selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{compositeFieldTypeIsFilterableByAnySubField && (
<SelectableListItem

View File

@ -104,9 +104,11 @@ export const AdvancedFilterValueInput = ({
/>
}
dropdownComponents={
<AdvancedFilterDropdownFilterInput recordFilter={recordFilter} />
<AdvancedFilterDropdownFilterInput
recordFilter={recordFilter}
filterDropdownId={dropdownId}
/>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={dropdownContentOffset}
dropdownPlacement="bottom-start"
onClose={handleFilterValueDropdownClose}

View File

@ -13,10 +13,10 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { isCompositeTypeNonFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeNonFilterableByAnySubField';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isDefined } from 'twenty-shared/utils';
@ -44,7 +44,7 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
currentRecordFiltersComponentState,
);
const setHotkeyScope = useSetHotkeyScope();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
@ -76,7 +76,17 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
fieldMetadataItem.type === 'RELATION' ||
fieldMetadataItem.type === 'SELECT'
) {
setHotkeyScope(SingleRecordPickerHotkeyScope.SingleRecordPicker);
pushFocusItemToFocusStack({
focusId: fieldMetadataItem.id,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: fieldMetadataItem.id,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: fieldMetadataItem.id,
});
}
const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);

View File

@ -3,10 +3,10 @@ import styled from '@emotion/styled';
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { useObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useObjectFilterDropdownFilterValue';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay';
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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -57,7 +57,8 @@ export const ObjectFilterDropdownBooleanSelect = () => {
<SelectableList
selectableListInstanceId="boolean-select"
selectableItemIdArray={options.map((option) => option.toString())}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
focusId="boolean-select"
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<DropdownMenuItemsContainer hasMaxHeight>
{options.map((option) => (

View File

@ -25,7 +25,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { isDefined } from 'twenty-shared/utils';
type ObjectFilterDropdownFilterInputProps = {
filterDropdownId?: string;
filterDropdownId: string;
recordFilterId?: string;
};
@ -113,7 +113,10 @@ export const ObjectFilterDropdownFilterInput = ({
<>
<ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilterId} />
<ObjectFilterDropdownRecordSelect
recordFilterId={recordFilterId}
dropdownId={filterDropdownId}
/>
</>
)}
{filterType === 'ACTOR' && <ObjectFilterDropdownTextInput />}
@ -123,7 +126,7 @@ export const ObjectFilterDropdownFilterInput = ({
<>
<ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect />
<ObjectFilterDropdownOptionSelect focusId={filterDropdownId} />
</>
)}
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}

View File

@ -3,7 +3,6 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -47,9 +46,6 @@ export const ObjectFilterDropdownOperandDropdown = ({
</StyledDropdownMenuHeader>
}
dropdownComponents={<ObjectFilterDropdownOperandSelect />}
dropdownHotkeyScope={{
scope: FiltersHotkeyScope.ObjectFilterDropdownOperandDropdown,
}}
dropdownOffset={{ x: parseInt(theme.spacing(2), 10) }}
/>
</ClickOutsideListenerContext.Provider>

View File

@ -14,9 +14,9 @@ import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/ob
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isNonEmptyString } from '@sniptt/guards';
@ -30,7 +30,11 @@ type SelectOptionForFilter = FieldMetadataItemOption & {
isSelected: boolean;
};
export const ObjectFilterDropdownOptionSelect = () => {
export const ObjectFilterDropdownOptionSelect = ({
focusId,
}: {
focusId: string;
}) => {
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
);
@ -92,15 +96,16 @@ export const ObjectFilterDropdownOptionSelect = () => {
}
}, [selectedOptions, selectOptions]);
useScopedHotkeys(
[Key.Escape],
() => {
useHotkeysOnFocusedElement({
keys: [Key.Escape],
callback: () => {
closeDropdown();
resetSelectedItem();
},
SingleRecordPickerHotkeyScope.SingleRecordPicker,
[closeDropdown, resetSelectedItem],
);
focusId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [closeDropdown, resetSelectedItem],
});
const handleMultipleOptionSelectChange = (
optionChanged: SelectOptionForFilter,
@ -148,7 +153,8 @@ export const ObjectFilterDropdownOptionSelect = () => {
<SelectableList
selectableListInstanceId={componentInstanceId}
selectableItemIdArray={objectRecordsIds}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<DropdownMenuItemsContainer hasMaxHeight>
{showNoResult ? (

View File

@ -8,7 +8,6 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
@ -29,10 +28,12 @@ export const MAX_RECORDS_TO_DISPLAY = 3;
type ObjectFilterDropdownRecordSelectProps = {
recordFilterId?: string;
dropdownId: string;
};
export const ObjectFilterDropdownRecordSelect = ({
recordFilterId,
dropdownId,
}: ObjectFilterDropdownRecordSelectProps) => {
const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector,
@ -220,7 +221,7 @@ export const ObjectFilterDropdownRecordSelect = ({
)}
<MultipleSelectDropdown
selectableListId="object-filter-record-select-id"
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
focusId={dropdownId}
itemsToSelect={recordsToSelect}
filteredSelectedItems={filteredSelectedRecords}
selectedItems={selectedRecords}

View File

@ -3,7 +3,6 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
@ -15,7 +14,11 @@ import { isDefined } from 'twenty-shared/utils';
export const EMPTY_FILTER_VALUE = '[]';
export const MAX_ITEMS_TO_DISPLAY = 3;
export const ObjectFilterDropdownSourceSelect = () => {
export const ObjectFilterDropdownSourceSelect = ({
dropdownId,
}: {
dropdownId: string;
}) => {
const objectFilterDropdownSearchInput = useRecoilComponentValueV2(
objectFilterDropdownSearchInputComponentState,
);
@ -78,7 +81,7 @@ export const ObjectFilterDropdownSourceSelect = () => {
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<MultipleSelectDropdown
selectableListId="object-filter-source-select-id"
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker}
focusId={dropdownId}
itemsToSelect={sourceTypes.filter(
(item) =>
!filteredSelectedItems.some((selected) => selected.id === item.id),

View File

@ -1,5 +0,0 @@
export enum FiltersHotkeyScope {
ObjectFilterDropdownButton = 'filter-dropdown-button',
ObjectSortDropdownButton = 'sort-dropdown-button',
ObjectFilterDropdownOperandDropdown = 'filter-dropdown-operand-dropdown',
}

View File

@ -7,7 +7,6 @@ import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dro
import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId';
import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal';
import { useRecordGroupReorderConfirmationModal } from '@/object-record/record-group/hooks/useRecordGroupReorderConfirmationModal';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@ -40,7 +39,6 @@ export const ObjectOptionsDropdown = ({
<>
<Dropdown
dropdownId={OBJECT_OPTIONS_DROPDOWN_ID}
dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
clickableComponent={
<StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>

View File

@ -4,12 +4,12 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@ -108,8 +108,9 @@ export const ObjectOptionsDropdownLayoutContent = () => {
{!!currentView && (
<SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<DropdownMenuItemsContainer scrollable={false}>
<SelectableListItem

View File

@ -2,11 +2,11 @@ import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropd
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@ -53,8 +53,9 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<SelectableListItem
itemId={ViewOpenRecordInType.SIDE_PANEL}

View File

@ -1,20 +1,17 @@
import { Key } from 'ts-key-enum';
import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName';
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
@ -48,14 +45,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
(isDefined(currentView?.viewGroups) && currentView.viewGroups.length > 0) ||
currentView?.key !== 'INDEX';
useScopedHotkeys(
[Key.Escape],
() => {
closeDropdown();
},
TableOptionsHotkeyScope.Dropdown,
);
const { visibleBoardFields } = useObjectOptionsForBoard({
objectNameSingular: objectMetadataItem.nameSingular,
recordBoardId: recordIndexId,
@ -101,8 +90,9 @@ export const ObjectOptionsDropdownMenuContent = () => {
<DropdownMenuSeparator />
<SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<DropdownMenuItemsContainer scrollable={false}>
<SelectableListItem

View File

@ -4,12 +4,13 @@ import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropd
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { View } from '@/views/types/View';
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
import { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState';
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
@ -75,17 +76,19 @@ export const ObjectOptionsDropdownMenuViewName = ({
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
const [viewName, setViewName] = useState(currentView?.name);
useScopedHotkeys(
Key.Enter,
async () => {
useHotkeysOnFocusedElement({
keys: [Key.Enter],
callback: async () => {
if (viewPickerIsPersisting) {
return;
}
await updateViewFromCurrentState();
},
ViewsHotkeyScope.ListDropdown,
);
focusId: VIEW_PICKER_DROPDOWN_ID,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [viewPickerIsPersisting, updateViewFromCurrentState],
});
const handleIconChange = ({ iconKey }: { iconKey: string }) => {
setViewPickerIsDirty(true);

View File

@ -5,11 +5,11 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@ -73,8 +73,9 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<SelectableListItem
itemId={RecordGroupSort.Manual}

View File

@ -9,12 +9,12 @@ import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-gr
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@ -94,6 +94,8 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
'HideEmptyGroups',
];
const hiddenGroupsSelectableListId = `${OBJECT_OPTIONS_DROPDOWN_ID}-hidden-groups`;
return (
<DropdownContent>
<DropdownMenuHeader
@ -109,8 +111,9 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<DropdownMenuItemsContainer>
<SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{currentView?.key !== 'INDEX' && (
<>
@ -175,9 +178,10 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<DropdownMenuSeparator />
<DropdownMenuItemsContainer scrollable={false}>
<SelectableList
selectableListInstanceId={`${OBJECT_OPTIONS_DROPDOWN_ID}-hidden-groups`}
hotkeyScope={TableOptionsHotkeyScope.Dropdown}
selectableListInstanceId={hiddenGroupsSelectableListId}
focusId={hiddenGroupsSelectableListId}
selectableItemIdArray={['HiddenGroups']}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<SelectableListItem
itemId="HiddenGroups"

View File

@ -6,7 +6,6 @@ import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/co
import { useCloseSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useCloseSortDropdown';
import { useResetRecordSortDropdownSearchInput } from '@/object-record/object-sort-dropdown/hooks/useResetRecordSortDropdownSearchInput';
import { useResetSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useResetSortDropdown';
import { useToggleSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useToggleSortDropdown';
import { isRecordSortDirectionDropdownMenuUnfoldedComponentState } from '@/object-record/object-sort-dropdown/states/isRecordSortDirectionDropdownMenuUnfoldedComponentState';
import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState';
import { selectedRecordSortDirectionComponentState } from '@/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState';
@ -25,6 +24,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -86,11 +86,7 @@ export type ObjectSortDropdownButtonProps = {
hotkeyScope: HotkeyScope;
};
export const ObjectSortDropdownButton = ({
hotkeyScope,
}: ObjectSortDropdownButtonProps) => {
const { toggleSortDropdown } = useToggleSortDropdown();
export const ObjectSortDropdownButton = () => {
const { resetRecordSortDropdownSearchInput } =
useResetRecordSortDropdownSearchInput();
@ -162,15 +158,16 @@ export const ObjectSortDropdownButton = ({
const shouldShowSeparator =
visibleFieldMetadataItems.length > 0 && hiddenFieldMetadataItems.length > 0;
const handleButtonClick = () => {
toggleSortDropdown();
};
const handleDropdownButtonClose = () => {
resetRecordSortDropdownSearchInput();
resetSortDropdown();
};
const handleDropdownOpen = () => {
resetSortDropdown();
setSelectedItemId(selectableItemIdArray[0]);
};
const { closeSortDropdown } = useCloseSortDropdown();
const { upsertRecordSort } = useUpsertRecordSort();
@ -224,16 +221,10 @@ export const ObjectSortDropdownButton = ({
return (
<Dropdown
dropdownId={OBJECT_SORT_DROPDOWN_ID}
dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ y: 8 }}
onOpen={handleDropdownOpen}
clickableComponent={
<StyledHeaderDropdownButton
onClick={() => {
handleButtonClick();
setSelectedItemId(selectableItemIdArray[0]);
}}
isUnfolded={isDropdownOpen}
>
<StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>
<Trans>Sort</Trans>
</StyledHeaderDropdownButton>
}
@ -241,8 +232,9 @@ export const ObjectSortDropdownButton = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<SelectableList
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
hotkeyScope={hotkeyScope.scope}
selectableItemIdArray={selectableItemIdArray}
focusId={OBJECT_SORT_DROPDOWN_ID}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{isRecordSortDirectionMenuUnfolded && (
<StyledSelectedSortDirectionContainer>

View File

@ -9,7 +9,6 @@ import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/r
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
@ -155,7 +154,8 @@ export const RecordBoardCard = () => {
y: event.clientY,
});
openDropdown(actionMenuDropdownId, {
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
});
};

View File

@ -5,7 +5,6 @@ import { RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext } from
import { RecordBoardColumnHeaderAggregateDropdownButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownButton';
import { AggregateDropdownContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent';
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext';
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
import { RecordBoardColumnHeaderAggregateContentId } from '@/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import styled from '@emotion/styled';
@ -42,9 +41,6 @@ export const RecordBoardColumnHeaderAggregateDropdown = ({
<Dropdown
onClose={handleResetContent}
dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
}}
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
clickableComponent={
<RecordBoardColumnHeaderAggregateDropdownButton

View File

@ -11,6 +11,7 @@ import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldM
import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay';
import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
@ -21,7 +22,6 @@ import { isDefined } from 'twenty-shared/utils';
import { VisibilityHidden } from 'twenty-ui/accessibility';
import { IconChevronDown } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
type FormMultiSelectFieldInputProps = {
label?: string;
@ -256,7 +256,7 @@ export const FormMultiSelectFieldInput = ({
selectableListComponentInstanceId={
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
}
hotkeyScope={hotkeyScope}
focusId={hotkeyScope}
options={options}
onCancel={onCancel}
onOptionSelected={onOptionSelected}

View File

@ -10,6 +10,7 @@ import { singleRecordPickerSelectedIdComponentState } from '@/object-record/reco
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
@ -18,7 +19,6 @@ import styled from '@emotion/styled';
import { useCallback, useId } from 'react';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { IconChevronDown, IconForbid } from 'twenty-ui/display';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
const StyledFormSelectContainer = styled(FormFieldInputInnerContainer)<{
readonly?: boolean;
@ -189,6 +189,7 @@ export const FormSingleRecordPicker = ({
}
dropdownComponents={
<SingleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId}
EmptyIcon={IconForbid}
emptyLabel={'No ' + objectNameSingular}
@ -199,7 +200,6 @@ export const FormSingleRecordPicker = ({
dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
/>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
)}
{isDefined(VariablePicker) && !disabled && (

View File

@ -7,6 +7,7 @@ import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTa
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useOpenRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput';
import { useOpenRelationToOneFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput';
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import {
FieldMetadata,
@ -15,11 +16,13 @@ import {
} from '@/object-record/record-field/types/FieldMetadata';
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
import { INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/object-record/record-inline-cell/constants/InlineCellHotkeyScopeMemoizeKey';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@ -31,7 +34,7 @@ export const useOpenFieldInputEditMode = () => {
const { openActivityTargetCellEditMode } =
useOpenActivityTargetCellEditMode();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openFieldInput = useRecoilCallback(
({ snapshot }) =>
@ -72,7 +75,10 @@ export const useOpenFieldInputEditMode = () => {
});
openActivityTargetCellEditMode({
recordPickerInstanceId: `relation-from-many-field-input-${recordId}`,
recordPickerInstanceId: getRelationFromManyFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
}),
activityTargetObjectRecords,
});
return;
@ -103,9 +109,22 @@ export const useOpenFieldInputEditMode = () => {
}
}
setHotkeyScopeAndMemorizePreviousScope({
scope: DEFAULT_CELL_SCOPE.scope,
customScopes: DEFAULT_CELL_SCOPE.customScopes,
pushFocusItemToFocusStack({
focusId: getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
),
component: {
type: FocusComponentType.OPEN_FIELD_INPUT,
instanceId: getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
),
},
hotkeyScope: {
scope: DEFAULT_CELL_SCOPE.scope,
customScopes: DEFAULT_CELL_SCOPE.customScopes,
},
memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
});
},
@ -113,7 +132,7 @@ export const useOpenFieldInputEditMode = () => {
openActivityTargetCellEditMode,
openRelationFromManyFieldInput,
openRelationToOneFieldInput,
setHotkeyScopeAndMemorizePreviousScope,
pushFocusItemToFocusStack,
],
);

View File

@ -39,6 +39,7 @@ export const useMultiSelectField = () => {
const draftValue = useRecoilValue(getDraftValueSelector());
return {
recordId,
fieldDefinition,
persistField,
fieldValues: fieldMultiSelectValues,

View File

@ -34,6 +34,7 @@ export const useSelectField = () => {
const draftValue = useRecoilValue(getDraftValueSelector());
return {
recordId,
fieldDefinition,
persistField,
fieldValue: fieldSelectValue,

View File

@ -1,6 +1,6 @@
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField';
import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
type MultiSelectFieldInputProps = {
@ -10,14 +10,18 @@ type MultiSelectFieldInputProps = {
export const MultiSelectFieldInput = ({
onCancel,
}: MultiSelectFieldInputProps) => {
const { persistField, fieldDefinition, fieldValues } = useMultiSelectField();
const { persistField, fieldDefinition, fieldValues, recordId } =
useMultiSelectField();
return (
<MultiSelectInput
selectableListComponentInstanceId={
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
focusId={getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
)}
options={fieldDefinition.metadata.options}
onCancel={onCancel}
onOptionSelected={persistField}

View File

@ -10,6 +10,7 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
@ -25,7 +26,10 @@ export const RelationFromManyFieldInput = ({
onSubmit,
}: RelationFromManyFieldInputProps) => {
const { fieldDefinition, recordId } = useContext(FieldContext);
const recordPickerInstanceId = `relation-from-many-field-input-${recordId}`;
const recordPickerInstanceId = getRelationFromManyFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
});
const { updateRelation } = useUpdateRelationFromManyFieldInput();
const fieldName = fieldDefinition.metadata.fieldName;
@ -84,6 +88,7 @@ export const RelationFromManyFieldInput = ({
return (
<MultipleRecordPicker
focusId={recordPickerInstanceId}
componentInstanceId={recordPickerInstanceId}
onSubmit={handleSubmit}
onChange={(morphItem) => {

View File

@ -3,6 +3,7 @@ import { useRelationField } from '../../hooks/useRelationField';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
@ -25,7 +26,10 @@ export const RelationToOneFieldInput = ({
const persistField = usePersistField();
const recordPickerInstanceId = `relation-to-one-field-input-${recordId}-${fieldDefinition.metadata.fieldName}`;
const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
});
const handleRecordSelected = (
selectedRecord: SingleRecordPickerRecord | null | undefined,
@ -64,6 +68,7 @@ export const RelationToOneFieldInput = ({
return (
<SingleRecordPicker
focusId={recordPickerInstanceId}
componentInstanceId={recordPickerInstanceId}
EmptyIcon={IconForbid}
emptyLabel={'No ' + fieldDefinition.label}

View File

@ -2,6 +2,7 @@ import { useClearField } from '@/object-record/record-field/hooks/useClearField'
import { useSelectField } from '@/object-record/record-field/meta-types/hooks/useSelectField';
import { SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID } from '@/object-record/record-field/meta-types/input/constants/SelectFieldInputSelectableListComponentInstanceId';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { getFieldInputInstanceId } from '@/object-record/record-field/utils/getFieldInputInstanceId';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { SelectInput } from '@/ui/field/input/components/SelectInput';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
@ -20,7 +21,8 @@ export const SelectFieldInput = ({
onSubmit,
onCancel,
}: SelectFieldInputProps) => {
const { persistField, fieldDefinition, fieldValue } = useSelectField();
const { persistField, fieldDefinition, fieldValue, recordId } =
useSelectField();
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]);
@ -65,7 +67,10 @@ export const SelectFieldInput = ({
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
}
selectableItemIdArray={optionIds}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
focusId={getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
)}
onEnter={(itemId) => {
const option = filteredOptions.find(
(option) => option.value === itemId,

View File

@ -18,9 +18,10 @@ import {
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from '~/generated-metadata/graphql';
@ -71,7 +72,7 @@ const RelationManyFieldInputWithContext = () => {
useEffect(() => {
setRecordStoreFieldValue([]);
setHotKeyScope(MultipleRecordPickerHotkeyScope.MultipleRecordPicker);
setHotKeyScope(DropdownHotkeyScope.Dropdown);
openFieldInput({
fieldDefinition,
recordId: 'recordId',
@ -87,7 +88,10 @@ const RelationManyFieldInputWithContext = () => {
<div>
<RecordFieldComponentInstanceContext.Provider
value={{
instanceId: 'relation-from-many-field-record-id-people',
instanceId: getRelationFromManyFieldInputInstanceId({
recordId: 'recordId',
fieldName: 'people',
}),
}}
>
<FieldContext.Provider

View File

@ -5,7 +5,6 @@ import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@ -17,9 +16,12 @@ import {
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { FieldMetadataType } from 'twenty-shared/types';
import { getCanvasElementForDropdownTesting } from 'twenty-ui/testing';
@ -62,11 +64,30 @@ const RelationToOneFieldInputWithContext = ({
onSubmit,
onCancel,
}: RelationToOneFieldInputWithContextProps) => {
const setHotKeyScope = useSetHotkeyScope();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
useEffect(() => {
setHotKeyScope(SingleRecordPickerHotkeyScope.SingleRecordPicker);
}, [setHotKeyScope]);
pushFocusItemToFocusStack({
focusId: getRelationToOneFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
component: {
type: FocusComponentType.DROPDOWN,
instanceId: getRelationToOneFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: getRelationToOneFieldInputInstanceId({
recordId: '123',
fieldName: 'Relation',
}),
});
}, [pushFocusItemToFocusStack]);
return (
<div>

View File

@ -1,4 +1,5 @@
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
import {
FieldRelationFromManyValue,
FieldRelationValue,
@ -6,17 +7,18 @@ import {
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil';
export const useOpenRelationFromManyFieldInput = () => {
const { performSearch } = useMultipleRecordPickerPerformSearch();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openRelationFromManyFieldInput = useRecoilCallback(
({ set, snapshot }) =>
@ -29,7 +31,10 @@ export const useOpenRelationFromManyFieldInput = () => {
objectNameSingular: string;
recordId: string;
}) => {
const recordPickerInstanceId = `relation-from-many-field-input-${recordId}`;
const recordPickerInstanceId = getRelationFromManyFieldInputInstanceId({
recordId,
fieldName,
});
const fieldValue = snapshot
.getLoadable<FieldRelationValue<FieldRelationFromManyValue>>(
@ -88,11 +93,19 @@ export const useOpenRelationFromManyFieldInput = () => {
forcePickableMorphItems: pickableMorphItems,
});
setHotkeyScopeAndMemorizePreviousScope({
scope: MultipleRecordPickerHotkeyScope.MultipleRecordPicker,
pushFocusItemToFocusStack({
focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: recordPickerInstanceId,
});
},
[performSearch, setHotkeyScopeAndMemorizePreviousScope],
[performSearch, pushFocusItemToFocusStack],
);
return { openRelationFromManyFieldInput };

View File

@ -1,21 +1,26 @@
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
import {
FieldRelationToOneValue,
FieldRelationValue,
} from '@/object-record/record-field/types/FieldMetadata';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const useOpenRelationToOneFieldInput = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openRelationToOneFieldInput = useRecoilCallback(
({ set, snapshot }) =>
({ fieldName, recordId }: { fieldName: string; recordId: string }) => {
const recordPickerInstanceId = `relation-to-one-field-input-${recordId}-${fieldName}`;
const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
recordId,
fieldName,
});
const fieldValue = snapshot
.getLoadable<FieldRelationValue<FieldRelationToOneValue>>(
recordStoreFamilySelector({
@ -34,11 +39,18 @@ export const useOpenRelationToOneFieldInput = () => {
);
}
setHotkeyScopeAndMemorizePreviousScope({
scope: SingleRecordPickerHotkeyScope.SingleRecordPicker,
pushFocusItemToFocusStack({
focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.OPEN_FIELD_INPUT,
instanceId: recordPickerInstanceId,
},
// TODO: Remove this once we've fully migrated away from hotkey scopes
hotkeyScope: { scope: DropdownHotkeyScope.Dropdown },
memoizeKey: recordPickerInstanceId,
});
},
[setHotkeyScopeAndMemorizePreviousScope],
[pushFocusItemToFocusStack],
);
return { openRelationToOneFieldInput };

View File

@ -1,3 +0,0 @@
export enum RelationPickerHotkeyScope {
AddNew = 'add-new',
}

View File

@ -0,0 +1,9 @@
export const getRelationFromManyFieldInputInstanceId = ({
recordId,
fieldName,
}: {
recordId: string;
fieldName: string;
}): string => {
return `relation-from-many-field-input-${recordId}-${fieldName}`;
};

View File

@ -0,0 +1,9 @@
export const getRelationToOneFieldInputInstanceId = ({
recordId,
fieldName,
}: {
recordId: string;
fieldName: string;
}): string => {
return `relation-to-one-field-input-${recordId}-${fieldName}`;
};

View File

@ -0,0 +1,6 @@
export const getFieldInputInstanceId = (
recordId: string,
fieldName: string,
) => {
return `${recordId}-${fieldName}`;
};

View File

@ -4,7 +4,6 @@ import { MultipleRecordPickerSearchInput } from '@/object-record/record-picker/m
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types/RecordPickerLayoutDirection';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
@ -12,10 +11,11 @@ import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObj
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
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 { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import styled from '@emotion/styled';
import { useRef } from 'react';
@ -36,6 +36,7 @@ type MultipleRecordPickerProps = {
layoutDirection?: RecordPickerLayoutDirection;
componentInstanceId: string;
onClickOutside: () => void;
focusId: string;
};
export const MultipleRecordPicker = ({
@ -45,6 +46,7 @@ export const MultipleRecordPicker = ({
onClickOutside,
layoutDirection = 'search-bar-on-bottom',
componentInstanceId,
focusId,
}: MultipleRecordPickerProps) => {
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
@ -94,14 +96,15 @@ export const MultipleRecordPicker = ({
resetState();
};
useScopedHotkeys(
Key.Escape,
() => {
useHotkeysOnFocusedElement({
keys: [Key.Escape],
callback: () => {
handleSubmit();
},
MultipleRecordPickerHotkeyScope.MultipleRecordPicker,
[handleSubmit],
);
focusId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [handleSubmit],
});
const containerRef = useRef<HTMLDivElement>(null);
@ -140,13 +143,19 @@ export const MultipleRecordPicker = ({
{layoutDirection === 'search-bar-on-bottom' && (
<>
{createNewButtonSection}
<MultipleRecordPickerItemsDisplay onChange={onChange} />
<MultipleRecordPickerItemsDisplay
onChange={onChange}
focusId={focusId}
/>
</>
)}
<MultipleRecordPickerSearchInput />
{layoutDirection === 'search-bar-on-top' && (
<>
<MultipleRecordPickerItemsDisplay onChange={onChange} />
<MultipleRecordPickerItemsDisplay
onChange={onChange}
focusId={focusId}
/>
{createNewButtonSection}
</>
)}

View File

@ -10,8 +10,10 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
export const MultipleRecordPickerItemsDisplay = ({
onChange,
focusId,
}: {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
}) => {
const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
MultipleRecordPickerComponentInstanceContext,
@ -33,7 +35,7 @@ export const MultipleRecordPickerItemsDisplay = ({
{isLoading && itemsLength === 0 ? (
<DropdownMenuSkeletonItem />
) : (
<MultipleRecordPickerMenuItems onChange={onChange} />
<MultipleRecordPickerMenuItems onChange={onChange} focusId={focusId} />
)}
<DropdownMenuSeparator />
</>

View File

@ -6,10 +6,10 @@ import { MultipleRecordPickerMenuItem } from '@/object-record/record-picker/mult
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerPickableRecordIdsMatchingSearchComponentSelector';
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
@ -25,10 +25,12 @@ export const StyledSelectableItem = styled(SelectableListItem)`
type MultipleRecordPickerMenuItemsProps = {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
};
export const MultipleRecordPickerMenuItems = ({
onChange,
focusId,
}: MultipleRecordPickerMenuItemsProps) => {
const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
MultipleRecordPickerComponentInstanceContext,
@ -85,7 +87,8 @@ export const MultipleRecordPickerMenuItems = ({
<SelectableList
selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={pickableRecordIds}
hotkeyScope={MultipleRecordPickerHotkeyScope.MultipleRecordPicker}
focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{pickableRecordIds.map((recordId) => {
return (

View File

@ -1,3 +0,0 @@
export enum MultipleRecordPickerHotkeyScope {
MultipleRecordPicker = 'multiple-record-picker',
}

View File

@ -28,6 +28,7 @@ export const SingleRecordPicker = ({
componentInstanceId,
layoutDirection,
dropdownWidth,
focusId,
}: SingleRecordPickerProps) => {
const containerRef = useRef<HTMLDivElement>(null);
@ -71,6 +72,7 @@ export const SingleRecordPicker = ({
>
<DropdownContent ref={containerRef} widthInPixels={dropdownWidth}>
<SingleRecordPickerMenuItemsWithSearch
focusId={focusId}
{...{
EmptyIcon,
emptyLabel,

View File

@ -4,16 +4,16 @@ import { Key } from 'ts-key-enum';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { SingleRecordPickerMenuItem } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPickerMenuItem';
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
@ -29,7 +29,7 @@ export type SingleRecordPickerMenuItemsProps = {
onCancel?: () => void;
onRecordSelected: (entity?: SingleRecordPickerRecord) => void;
selectedRecord?: SingleRecordPickerRecord;
hotkeyScope?: string;
focusId: string;
};
export const SingleRecordPickerMenuItems = ({
@ -40,7 +40,7 @@ export const SingleRecordPickerMenuItems = ({
onCancel,
onRecordSelected,
selectedRecord,
hotkeyScope = SingleRecordPickerHotkeyScope.SingleRecordPicker,
focusId,
}: SingleRecordPickerMenuItemsProps) => {
const selectNone = emptyLabel
? {
@ -77,15 +77,16 @@ export const SingleRecordPickerMenuItems = ({
'select-none',
);
useScopedHotkeys(
[Key.Escape],
() => {
useHotkeysOnFocusedElement({
keys: Key.Escape,
callback: () => {
resetSelectedItem();
onCancel?.();
},
hotkeyScope,
[onCancel, resetSelectedItem],
);
focusId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [onCancel, resetSelectedItem],
});
const selectableItemIds = recordsInDropdown.map((entity) => entity.id);
const [selectedRecordId, setSelectedRecordId] = useRecoilComponentStateV2(
@ -96,7 +97,8 @@ export const SingleRecordPickerMenuItems = ({
<SelectableList
selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope}
hotkeyScope={DropdownHotkeyScope.Dropdown}
focusId={focusId}
>
{loading ? (
<DropdownMenuSkeletonItem />

View File

@ -26,6 +26,7 @@ export type SingleRecordPickerMenuItemsWithSearchProps = {
objectNameSingular: string;
recordPickerInstanceId?: string;
layoutDirection?: RecordPickerLayoutDirection;
focusId: string;
} & Pick<
SingleRecordPickerMenuItemsProps,
| 'EmptyIcon'
@ -44,6 +45,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
onRecordSelected,
objectNameSingular,
layoutDirection = 'search-bar-on-top',
focusId,
}: SingleRecordPickerMenuItemsWithSearchProps) => {
const { handleSearchFilterChange } = useSingleRecordPickerSearch();
@ -96,9 +98,11 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItemsContainer hasMaxHeight>
{searchHasNoResults && <RecordPickerNoRecordFoundMenuItem />}
<SingleRecordPickerMenuItems
focusId={focusId}
recordsToSelect={records.recordsToSelect}
loading={records.loading}
selectedRecord={records.selectedRecords?.[0]}
@ -123,6 +127,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<SingleRecordPickerMenuItems
focusId={focusId}
recordsToSelect={records.recordsToSelect}
loading={records.loading}
selectedRecord={records.selectedRecords?.[0]}

View File

@ -1,3 +0,0 @@
export enum SingleRecordPickerHotkeyScope {
SingleRecordPicker = 'single-record-picker',
}

View File

@ -285,7 +285,6 @@ export const RecordDetailRelationRecordsListItem = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownScopeId }}
/>
</DropdownScope>
)}

View File

@ -216,10 +216,10 @@ export const RecordDetailRelationSectionDropdown = ({
accent="tertiary"
/>
}
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownComponents={
isToOneObject ? (
<SingleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId}
EmptyIcon={IconForbid}
onRecordSelected={handleRelationPickerEntitySelected}
@ -235,6 +235,7 @@ export const RecordDetailRelationSectionDropdown = ({
/>
) : (
<MultipleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId}
onCreate={() => {
closeDropdown();

View File

@ -8,6 +8,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack';
export const useCloseRecordTableCellInGroup = () => {
const { recordTableId } = useRecordTableContextOrThrow();
@ -22,11 +23,17 @@ export const useCloseRecordTableCellInGroup = () => {
const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack();
const closeTableCellInGroup = useRecoilCallback(
() => () => {
toggleClickOutside(true);
setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode();
// TODO: Remove this once we've fully migrated away from hotkey scopes
resetFocusStack();
setHotkeyScope(TableHotkeyScope.TableFocus, {
goto: true,
keyboardShortcutMenu: true,
@ -35,6 +42,7 @@ export const useCloseRecordTableCellInGroup = () => {
},
[
closeCurrentTableCellInEditMode,
resetFocusStack,
setDragSelectionStartEnabled,
setHotkeyScope,
toggleClickOutside,

View File

@ -6,6 +6,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack';
import { useCallback } from 'react';
export const useCloseRecordTableCellNoGroup = () => {
@ -22,10 +23,16 @@ export const useCloseRecordTableCellNoGroup = () => {
const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack();
const closeTableCellNoGroup = useCallback(() => {
toggleClickOutside(true);
setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode();
// TODO: Remove this once we've fully migrated away from hotkey scopes
resetFocusStack();
setHotkeyScope(TableHotkeyScope.TableFocus, {
goto: true,
keyboardShortcutMenu: true,
@ -33,6 +40,7 @@ export const useCloseRecordTableCellNoGroup = () => {
});
}, [
closeCurrentTableCellInEditMode,
resetFocusStack,
setDragSelectionStartEnabled,
setHotkeyScope,
toggleClickOutside,

View File

@ -2,7 +2,6 @@ import { useRecoilCallback } from 'recoil';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
@ -58,9 +57,7 @@ export const useTriggerActionMenuDropdown = ({
closeCommandMenu();
openDropdown({
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
});
openDropdown();
},
[
recordIndexActionMenuDropdownPositionState,

View File

@ -76,7 +76,6 @@ export const RecordTableColumnFooterWithDropdown = ({
}
dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -35,7 +35,6 @@ export const RecordTableColumnHeadWithDropdown = ({
dropdownComponents={<RecordTableColumnHeadDropdownMenu column={column} />}
dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }}
/>
);
};

View File

@ -50,9 +50,6 @@ const StyledDropdownContainer = styled.div`
cursor: pointer;
`;
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
'hidden-table-columns-dropdown-hotkey-scope-id';
export const RecordTableHeaderLastColumn = () => {
const theme = useTheme();
@ -89,9 +86,6 @@ export const RecordTableHeaderLastColumn = () => {
}
dropdownComponents={<RecordTableHeaderPlusButtonContent />}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
}}
/>
</StyledDropdownContainer>
</StyledPlusIconHeaderCell>

View File

@ -3,19 +3,20 @@ import { Key } from 'ts-key-enum';
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { Avatar } from 'twenty-ui/display';
import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
export const MultipleSelectDropdown = ({
selectableListId,
hotkeyScope,
focusId,
itemsToSelect,
loadingItems,
filteredSelectedItems,
@ -23,7 +24,7 @@ export const MultipleSelectDropdown = ({
searchFilter,
}: {
selectableListId: string;
hotkeyScope: string;
focusId: string;
itemsToSelect: SelectableItem[];
filteredSelectedItems: SelectableItem[];
selectedItems: SelectableItem[];
@ -61,15 +62,16 @@ export const MultipleSelectDropdown = ({
...(itemsToSelect ?? []),
];
useScopedHotkeys(
[Key.Escape],
() => {
useHotkeysOnFocusedElement({
keys: [Key.Escape],
callback: () => {
closeDropdown();
resetSelectedItem();
},
hotkeyScope,
[closeDropdown, resetSelectedItem],
);
focusId,
scope: DropdownHotkeyScope.Dropdown,
dependencies: [closeDropdown, resetSelectedItem],
});
const showNoResult =
itemsToSelect?.length === 0 &&
@ -83,7 +85,8 @@ export const MultipleSelectDropdown = ({
<SelectableList
selectableListInstanceId={selectableListId}
selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope}
focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<DropdownMenuItemsContainer hasMaxHeight>
{itemsInDropdown?.map((item) => {

View File

@ -51,7 +51,6 @@ export const SettingsAccountsRowDropdownMenu = ({
<Dropdown
dropdownId={dropdownId}
dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
}

View File

@ -2,7 +2,6 @@ import { Select } from '@/ui/input/components/Select';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@ -96,7 +95,6 @@ export const ConfigVariableDatabaseInput = ({
{options && Array.isArray(options) ? (
<Dropdown
dropdownId="config-variable-array-dropdown"
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
dropdownPlacement="bottom-start"
dropdownOffset={{
y: 8,

View File

@ -46,7 +46,6 @@ export const ConfigVariableFilterDropdown = ({
/>
}
dropdownId="env-var-options-dropdown"
dropdownHotkeyScope={{ scope: 'env-var-options' }}
dropdownOffset={{ x: 0, y: 10 }}
dropdownComponents={
<ConfigVariableOptionsDropdownContent

View File

@ -130,7 +130,6 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
</StyledContainer>
);

View File

@ -117,7 +117,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown
dropdownId={SELECT_COLOR_DROPDOWN_ID}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: SELECT_COLOR_DROPDOWN_ID }}
clickableComponent={<StyledColorSample colorName={option.color} />}
dropdownComponents={
<DropdownContent>
@ -160,7 +159,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: SELECT_ACTIONS_DROPDOWN_ID }}
clickableComponent={
<StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} />
}

View File

@ -82,7 +82,6 @@ export const SettingsObjectFieldActiveActionDropdown = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -86,9 +86,6 @@ export const SettingsObjectFieldInactiveActionDropdown = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{
scope: dropdownId,
}}
/>
);
};

View File

@ -63,7 +63,6 @@ export const SettingsObjectInactiveMenuDropDown = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -54,7 +54,6 @@ export const SettingsIntegrationDatabaseConnectionSummaryCard = ({
/>
<Dropdown
dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
}

View File

@ -238,7 +238,6 @@ export const SettingsRoleAssignment = ({
<StyledAssignToMemberContainer>
<Dropdown
dropdownId="role-member-select"
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
dropdownOffset={{ x: 0, y: 4 }}
clickableComponent={
<>

View File

@ -69,7 +69,6 @@ export const SettingsSecuritySSORowDropdownMenu = ({
<Dropdown
dropdownId={dropdownId}
dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
}

View File

@ -58,7 +58,6 @@ export const SettingsSecurityApprovedAccessDomainRowDropdownMenu = ({
<Dropdown
dropdownId={dropdownId}
dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
}

View File

@ -131,7 +131,6 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropDownId }}
/>
</TableCell>
</StyledTableRow>

View File

@ -123,9 +123,6 @@ export const MatchColumnToFieldSelect = ({
return (
<Dropdown
dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: dropdownId,
}}
dropdownPlacement="bottom-start"
clickableComponent={
<StyledMenuItem

View File

@ -52,7 +52,6 @@ export const SubMatchingSelectRowRightDropdown = <T extends string>({
return (
<StyledDropdownContainer>
<Dropdown
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownId={dropdownId}
dropdownPlacement="bottom-start"
clickableComponent={

View File

@ -44,7 +44,6 @@ export const SupportDropdown = () => {
</DropdownMenuItemsContainer>
</DropdownContent>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -2,15 +2,17 @@ import { useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils';
@ -21,7 +23,7 @@ import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmp
type MultiSelectInputProps = {
selectableListComponentInstanceId: string;
values: FieldMultiSelectValue;
hotkeyScope: string;
focusId: string;
onCancel?: () => void;
options: SelectOption[];
onOptionSelected: (value: FieldMultiSelectValue) => void;
@ -32,7 +34,7 @@ export const MultiSelectInput = ({
selectableListComponentInstanceId,
values,
options,
hotkeyScope,
focusId,
onCancel,
onOptionSelected,
dropdownWidth,
@ -69,15 +71,16 @@ export const MultiSelectInput = ({
}
};
useScopedHotkeys(
Key.Escape,
() => {
useHotkeysOnFocusedElement({
keys: Key.Escape,
callback: () => {
onCancel?.();
resetSelectedItem();
},
hotkeyScope,
[onCancel, resetSelectedItem],
);
focusId,
scope: DEFAULT_CELL_SCOPE.scope,
dependencies: [onCancel, resetSelectedItem],
});
useListenClickOutside({
refs: [containerRef],
@ -102,7 +105,8 @@ export const MultiSelectInput = ({
<SelectableList
selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={optionIds}
hotkeyScope={hotkeyScope}
focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
>
<DropdownContent
ref={containerRef}
@ -122,17 +126,25 @@ export const MultiSelectInput = ({
<DropdownMenuItemsContainer hasMaxHeight>
{filteredOptionsInDropDown.map((option) => {
return (
<MenuItemMultiSelectTag
<SelectableListItem
key={option.value}
selected={values?.includes(option.value) || false}
text={option.label}
color={option.color ?? 'transparent'}
Icon={option.Icon ?? undefined}
onClick={() =>
onOptionSelected(formatNewSelectedOptions(option.value))
}
isKeySelected={selectedItemId === option.value}
/>
itemId={option.value}
onEnter={() => {
onOptionSelected(formatNewSelectedOptions(option.value));
}}
>
<MenuItemMultiSelectTag
key={option.value}
selected={values?.includes(option.value) || false}
text={option.label}
color={option.color ?? 'transparent'}
Icon={option.Icon ?? undefined}
onClick={() =>
onOptionSelected(formatNewSelectedOptions(option.value))
}
isKeySelected={selectedItemId === option.value}
/>
</SelectableListItem>
);
})}
</DropdownMenuItemsContainer>

View File

@ -1,3 +1,4 @@
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { SelectInput as SelectBaseInput } from '@/ui/input/components/SelectInput';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectOption } from 'twenty-ui/input';
@ -5,7 +6,7 @@ import { SelectOption } from 'twenty-ui/input';
type SelectInputProps = {
selectableListComponentInstanceId: string;
selectableItemIdArray: string[];
hotkeyScope: string;
focusId: string;
onEnter: (itemId: string) => void;
onOptionSelected: (selectedOption: SelectOption) => void;
options: SelectOption[];
@ -19,7 +20,7 @@ type SelectInputProps = {
export const SelectInput = ({
selectableListComponentInstanceId,
selectableItemIdArray,
hotkeyScope,
focusId,
onOptionSelected,
options,
onCancel,
@ -32,7 +33,8 @@ export const SelectInput = ({
<SelectableList
selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={hotkeyScope}
focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
>
<SelectBaseInput
onOptionSelected={onOptionSelected}
@ -42,7 +44,7 @@ export const SelectInput = ({
onFilterChange={onFilterChange}
onClear={onClear}
clearLabel={clearLabel}
hotkeyScope={hotkeyScope}
focusId={focusId}
/>
</SelectableList>
);

View File

@ -12,6 +12,7 @@ import { arrayToChunks } from '~/utils/array/arrayToChunks';
import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -71,9 +72,10 @@ const IconPickerIcon = ({
);
useSelectableListListenToEnterHotkeyOnItem({
hotkeyScope: IconPickerHotkeyScope.IconPicker,
focusId: iconKey,
itemId: iconKey,
onEnter: onClick,
hotkeyScope: DropdownHotkeyScope.Dropdown,
});
return (
@ -184,7 +186,6 @@ export const IconPicker = ({
<div className={className}>
<Dropdown
dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: IconPickerHotkeyScope.IconPicker }}
clickableComponent={
<IconButton
ariaLabel={`Click to select icon ${
@ -203,7 +204,8 @@ export const IconPicker = ({
<SelectableList
selectableListInstanceId="icon-list"
selectableItemIdMatrix={iconKeys2d}
hotkeyScope={IconPickerHotkeyScope.IconPicker}
focusId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
<DropdownMenuSearchInput
placeholder={t`Search icon`}

View File

@ -9,6 +9,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectControl } from '@/ui/input/components/SelectControl';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@ -19,7 +20,6 @@ import { isDefined } from 'twenty-shared/utils';
import { IconComponent } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
export type SelectSizeVariant = 'small' | 'default';
@ -166,9 +166,10 @@ export const Select = <Value extends SelectValue>({
{!!filteredOptions.length && (
<DropdownMenuItemsContainer hasMaxHeight>
<SelectableList
hotkeyScope={SelectHotkeyScope.Select}
selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
>
{filteredOptions.map((option) => (
<SelectableListItem
@ -211,7 +212,6 @@ export const Select = <Value extends SelectValue>({
)}
</DropdownContent>
}
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
/>
)}
</StyledContainer>

View File

@ -2,10 +2,13 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { isDefined } from 'twenty-shared/utils';
import { TagColor } from 'twenty-ui/components';
import { SelectOption } from 'twenty-ui/input';
@ -19,7 +22,7 @@ interface SelectInputProps {
onFilterChange?: (filteredOptions: SelectOption[]) => void;
onClear?: () => void;
clearLabel?: string;
hotkeyScope: string;
focusId: string;
}
export const SelectInput = ({
@ -30,10 +33,19 @@ export const SelectInput = ({
onCancel,
defaultOption,
onFilterChange,
hotkeyScope,
}: SelectInputProps) => {
const containerRef = useRef<HTMLDivElement>(null);
// Get the SelectableList instance id from context
const selectableListInstanceId = useAvailableComponentInstanceIdOrThrow(
SelectableListComponentInstanceContext,
);
const selectedItemId = useRecoilComponentValueV2(
selectedItemIdComponentState,
selectableListInstanceId,
);
const [searchFilter, setSearchFilter] = useState('');
const [selectedOption, setSelectedOption] = useState<
SelectOption | undefined
@ -61,6 +73,11 @@ export const SelectInput = ({
onOptionSelected(option);
};
const handleClearOption = () => {
setSelectedOption(undefined);
onClear?.();
};
useEffect(() => {
onFilterChange?.(optionsInDropDown);
}, [onFilterChange, optionsInDropDown]);
@ -81,20 +98,6 @@ export const SelectInput = ({
listenerId: 'select-input',
});
useScopedHotkeys(
Key.Enter,
() => {
const selectedOption = optionsInDropDown.find((option) =>
option.label.toLowerCase().includes(searchFilter.toLowerCase()),
);
if (isDefined(selectedOption)) {
handleOptionChange(selectedOption);
}
},
hotkeyScope,
[searchFilter, optionsInDropDown],
);
return (
<DropdownContent ref={containerRef} selectDisabled>
<DropdownMenuSearchInput
@ -105,27 +108,37 @@ export const SelectInput = ({
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{onClear && clearLabel && (
<MenuItemSelectTag
key={`No ${clearLabel}`}
text={`No ${clearLabel}`}
color="transparent"
variant={'outline'}
onClick={() => {
setSelectedOption(undefined);
onClear();
}}
/>
<SelectableListItem
itemId={`No ${clearLabel}`}
onEnter={handleClearOption}
>
<MenuItemSelectTag
key={`No ${clearLabel}`}
text={`No ${clearLabel}`}
color="transparent"
variant={'outline'}
onClick={handleClearOption}
isKeySelected={selectedItemId === `No ${clearLabel}`}
/>
</SelectableListItem>
)}
{optionsInDropDown.map((option) => {
return (
<MenuItemSelectTag
<SelectableListItem
key={option.value}
focused={selectedOption?.value === option.value}
text={option.label}
color={(option.color as TagColor) ?? 'transparent'}
onClick={() => handleOptionChange(option)}
LeftIcon={option.Icon}
/>
itemId={option.value}
onEnter={() => handleOptionChange(option)}
>
<MenuItemSelectTag
key={option.value}
selected={selectedOption?.value === option.value}
text={option.label}
color={(option.color as TagColor) ?? 'transparent'}
onClick={() => handleOptionChange(option)}
LeftIcon={option.Icon}
isKeySelected={selectedItemId === option.value}
/>
</SelectableListItem>
);
})}
</DropdownMenuItemsContainer>

View File

@ -70,7 +70,6 @@ export const CurrencyPickerDropdownButton = ({
return (
<Dropdown
dropdownId="currency-picker-dropdown-id"
dropdownHotkeyScope={{ scope: CurrencyPickerHotkeyScope.CurrencyPicker }}
clickableComponent={
<StyledDropdownButtonContainer>
<StyledIconContainer>

View File

@ -6,8 +6,6 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useEffect, useState } from 'react';
import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope';
import { PhoneCountryPickerDropdownSelect } from './PhoneCountryPickerDropdownSelect';
import 'react-phone-number-input/style.css';
@ -98,7 +96,6 @@ export const PhoneCountryPickerDropdownButton = ({
return (
<Dropdown
dropdownId="country-picker-dropdown-id"
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isDropdownOpen}>
<StyledIconContainer>

Some files were not shown because too many files have changed in this diff Show More