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
@@ -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 { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { Button } from 'twenty-ui/input'; import { Button } from 'twenty-ui/input';
@@ -13,12 +14,13 @@ export const CmdEnterActionButton = ({
onClick: () => void; onClick: () => void;
disabled?: boolean; disabled?: boolean;
}) => { }) => {
useScopedHotkeys( useHotkeysOnFocusedElement({
[`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`], keys: [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
() => onClick(), callback: () => onClick(),
AppHotkeyScope.CommandMenuOpen, focusId: SIDE_PANEL_FOCUS_ID,
[onClick], scope: AppHotkeyScope.CommandMenuOpen,
); dependencies: [onClick],
});
return ( return (
<Button <Button
@@ -2,15 +2,16 @@ import { ActionComponent } from '@/action-menu/actions/display/components/Action
import { ActionScope } from '@/action-menu/actions/types/ActionScope'; import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId'; 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 { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; 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 { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
@@ -25,24 +26,30 @@ export const CommandMenuActionMenuDropdown = () => {
ActionMenuComponentInstanceContext, ActionMenuComponentInstanceContext,
); );
const { toggleDropdown } = useDropdownV2();
const theme = useTheme(); const theme = useTheme();
useScopedHotkeys( const dropdownId =
['ctrl+o,meta+o'], getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId);
() => { const { toggleDropdown } = useDropdownV2();
toggleDropdown(
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId), const hotkeysConfig = {
{ keys: ['ctrl+o', 'meta+o'],
scope: callback: () => {
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown, toggleDropdown(dropdownId);
},
);
}, },
AppHotkeyScope.CommandMenuOpen, scope: AppHotkeyScope.CommandMenuOpen,
[toggleDropdown], dependencies: [toggleDropdown],
); };
useHotkeysOnFocusedElement({
...hotkeysConfig,
focusId: SIDE_PANEL_FOCUS_ID,
});
useHotkeysOnFocusedElement({
...hotkeysConfig,
focusId: dropdownId,
});
const recordSelectionActions = actions.filter( const recordSelectionActions = actions.filter(
(action) => action.scope === ActionScope.RecordSelection, (action) => action.scope === ActionScope.RecordSelection,
@@ -56,19 +63,17 @@ export const CommandMenuActionMenuDropdown = () => {
return ( return (
<Dropdown <Dropdown
dropdownId={getRightDrawerActionMenuDropdownIdFromActionMenuId( dropdownId={dropdownId}
actionMenuId,
)}
dropdownHotkeyScope={{
scope:
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown,
}}
data-select-disable data-select-disable
clickableComponent={ clickableComponent={
<Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} /> <Button title="Options" hotkeys={[getOsControlSymbol(), 'O']} />
} }
dropdownPlacement="top-end" dropdownPlacement="top-end"
dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }} dropdownOffset={{ y: parseInt(theme.spacing(2), 10) }}
globalHotkeysConfig={{
enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
}}
onOpen={() => { onOpen={() => {
setSelectedItemId(selectableItemIdArray[0]); setSelectedItemId(selectableItemIdArray[0]);
}} }}
@@ -77,10 +82,9 @@ export const CommandMenuActionMenuDropdown = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={actionMenuId} selectableListInstanceId={actionMenuId}
hotkeyScope={ focusId={dropdownId}
CommandMenuActionMenuDropdownHotkeyScope.CommandMenuActionMenuDropdown
}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{recordSelectionActions.map((action) => ( {recordSelectionActions.map((action) => (
<ActionComponent action={action} key={action.key} /> <ActionComponent action={action} key={action.key} />
@@ -5,12 +5,12 @@ import { ACTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/action-menu/constants/A
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState'; import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -72,9 +72,6 @@ export const RecordIndexActionMenuDropdown = () => {
return ( return (
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
}}
data-select-disable data-select-disable
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownStrategy="absolute" dropdownStrategy="absolute"
@@ -89,9 +86,10 @@ export const RecordIndexActionMenuDropdown = () => {
> >
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
hotkeyScope={ActionMenuDropdownHotkeyScope.ActionMenuDropdown} focusId={dropdownId}
selectableItemIdArray={selectedItemIdArray} selectableItemIdArray={selectedItemIdArray}
selectableListInstanceId={dropdownId} selectableListInstanceId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{recordIndexActions.map((action) => ( {recordIndexActions.map((action) => (
<ActionComponent action={action} key={action.key} /> <ActionComponent action={action} key={action.key} />
@@ -1,5 +1,6 @@
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getRightDrawerActionMenuDropdownIdFromActionMenuId'; 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 { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; 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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; 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 { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext'; import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
@@ -118,12 +119,13 @@ export const RecordShowRightDrawerOpenRecordButton = ({
], ],
); );
useScopedHotkeys( useHotkeysOnFocusedElement({
['ctrl+Enter,meta+Enter'], keys: ['ctrl+Enter,meta+Enter'],
handleOpenRecord, callback: handleOpenRecord,
AppHotkeyScope.CommandMenuOpen, focusId: SIDE_PANEL_FOCUS_ID,
[handleOpenRecord], scope: AppHotkeyScope.CommandMenuOpen,
); dependencies: [handleOpenRecord],
});
if (!isDefined(record)) { if (!isDefined(record)) {
return null; return null;
@@ -1,3 +0,0 @@
export enum ActionMenuDropdownHotkeyScope {
ActionMenuDropdown = 'action-menu-dropdown',
}
@@ -1,3 +0,0 @@
export enum CommandMenuActionMenuDropdownHotkeyScope {
CommandMenuActionMenuDropdown = 'command-menu-action-menu-dropdown',
}
@@ -72,7 +72,6 @@ export const AttachmentDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };
@@ -70,6 +70,7 @@ export const ActivityTargetsInlineCell = ({
labelWidth: fieldDefinition?.labelWidth, labelWidth: fieldDefinition?.labelWidth,
editModeContent: ( editModeContent: (
<MultipleRecordPicker <MultipleRecordPicker
focusId={componentInstanceId}
componentInstanceId={componentInstanceId} componentInstanceId={componentInstanceId}
onClickOutside={() => { onClickOutside={() => {
closeInlineCell(); closeInlineCell();
@@ -5,8 +5,9 @@ import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-pic
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; 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 { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; 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 { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
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 { useRecoilCallback } from 'recoil';
type OpenActivityTargetCellEditModeProps = { type OpenActivityTargetCellEditModeProps = {
@@ -19,7 +20,7 @@ export const useOpenActivityTargetCellEditMode = () => {
const { performSearch: multipleRecordPickerPerformSearch } = const { performSearch: multipleRecordPickerPerformSearch } =
useMultipleRecordPickerPerformSearch(); useMultipleRecordPickerPerformSearch();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openActivityTargetCellEditMode = useRecoilCallback( const openActivityTargetCellEditMode = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@@ -84,11 +85,19 @@ export const useOpenActivityTargetCellEditMode = () => {
), ),
}); });
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: MultipleRecordPickerHotkeyScope.MultipleRecordPicker, focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: recordPickerInstanceId,
}); });
}, },
[multipleRecordPickerPerformSearch, setHotkeyScopeAndMemorizePreviousScope], [multipleRecordPickerPerformSearch, pushFocusItemToFocusStack],
); );
return { openActivityTargetCellEditMode }; return { openActivityTargetCellEditMode };
@@ -3,7 +3,6 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { MenuItem } from 'twenty-ui/navigation'; import { MenuItem } from 'twenty-ui/navigation';
import { import {
@@ -72,9 +71,6 @@ export const CommandMenuContextChipGroups = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{
scope: AppHotkeyScope.CommandMenu,
}}
dropdownId={COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID} dropdownId={COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
></Dropdown> ></Dropdown>
@@ -4,6 +4,7 @@ import { ActionGroupConfig } from '@/command-menu/components/CommandMenu';
import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect'; import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight'; import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding'; 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 { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
@@ -75,8 +76,9 @@ export const CommandMenuList = ({
<StyledInnerList> <StyledInnerList>
<SelectableList <SelectableList
selectableListInstanceId="command-menu-list" selectableListInstanceId="command-menu-list"
hotkeyScope={AppHotkeyScope.CommandMenuOpen} focusId={SIDE_PANEL_FOCUS_ID}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={AppHotkeyScope.CommandMenuOpen}
onSelect={() => { onSelect={() => {
setHasUserSelectedCommand(true); setHasUserSelectedCommand(true);
}} }}
@@ -0,0 +1 @@
export const SIDE_PANEL_FOCUS_ID = 'command-menu';
@@ -3,12 +3,13 @@ import { useRecoilCallback } from 'recoil';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId'; 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 { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState'; import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown'; import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState'; import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useRemoveFocusItemFromFocusStack } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStack';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { IconDotsVertical } from 'twenty-ui/display'; import { IconDotsVertical } from 'twenty-ui/display';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
@@ -16,7 +17,8 @@ import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
export const useCommandMenu = () => { export const useCommandMenu = () => {
const { navigateCommandMenu } = useNavigateCommandMenu(); const { navigateCommandMenu } = useNavigateCommandMenu();
const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown(); const { closeAnyOpenDropdown } = useCloseAnyOpenDropdown();
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
const { removeFocusItemFromFocusStack } = useRemoveFocusItemFromFocusStack();
const closeCommandMenu = useRecoilCallback( const closeCommandMenu = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@@ -30,10 +32,13 @@ export const useCommandMenu = () => {
set(isCommandMenuClosingState, true); set(isCommandMenuClosingState, true);
set(isDragSelectionStartEnabledState, true); set(isDragSelectionStartEnabledState, true);
closeAnyOpenDropdown(); 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(() => { const openCommandMenu = useCallback(() => {
@@ -1,4 +1,5 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId'; 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 { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates'; import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { commandMenuNavigationMorphItemByPageState } from '@/command-menu/states/commandMenuNavigationMorphItemsState'; 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 { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState'; import { isDragSelectionStartEnabledState } from '@/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { IconComponent } from 'twenty-ui/display'; import { IconComponent } from 'twenty-ui/display';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@@ -27,13 +29,13 @@ export type CommandMenuNavigationStackItem = {
}; };
export const useNavigateCommandMenu = () => { export const useNavigateCommandMenu = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { copyContextStoreStates } = useCopyContextStoreStates(); const { copyContextStoreStates } = useCopyContextStoreStates();
const { commandMenuCloseAnimationCompleteCleanup } = const { commandMenuCloseAnimationCompleteCleanup } =
useCommandMenuCloseAnimationCompleteCleanup(); useCommandMenuCloseAnimationCompleteCleanup();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openCommandMenu = useRecoilCallback( const openCommandMenu = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
() => { () => {
@@ -53,10 +55,17 @@ export const useNavigateCommandMenu = () => {
return; return;
} }
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: CommandMenuHotkeyScope.CommandMenuFocused, focusId: SIDE_PANEL_FOCUS_ID,
customScopes: { component: {
commandMenuOpen: true, type: FocusComponentType.SIDE_PANEL,
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
},
hotkeyScope: {
scope: CommandMenuHotkeyScope.CommandMenuFocused,
customScopes: {
commandMenuOpen: true,
},
}, },
memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID, memoizeKey: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}); });
@@ -73,7 +82,7 @@ export const useNavigateCommandMenu = () => {
[ [
copyContextStoreStates, copyContextStoreStates,
commandMenuCloseAnimationCompleteCleanup, commandMenuCloseAnimationCompleteCleanup,
setHotkeyScopeAndMemorizePreviousScope, pushFocusItemToFocusStack,
], ],
); );
@@ -1,4 +1,3 @@
import { FavoriteFolderHotkeyScope } from '@/favorites/constants/FavoriteFolderRightIconDropdownHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -33,9 +32,6 @@ export const FavoriteFolderNavigationDrawerItemDropdown = ({
return ( return (
<Dropdown <Dropdown
dropdownId={`favorite-folder-edit-${folderId}`} dropdownId={`favorite-folder-edit-${folderId}`}
dropdownHotkeyScope={{
scope: FavoriteFolderHotkeyScope.FavoriteFolderRightIconDropdown,
}}
data-select-disable data-select-disable
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
@@ -9,7 +9,8 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; 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 { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
@@ -57,22 +58,23 @@ export const FavoriteFolderPicker = ({
!favoriteFoldersSearchFilter || !favoriteFoldersSearchFilter ||
'no folder'.includes(favoriteFoldersSearchFilter.toLowerCase()); 'no folder'.includes(favoriteFoldersSearchFilter.toLowerCase());
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Escape, keys: [Key.Escape],
() => { callback: () => {
if (isFavoriteFolderCreating) { if (isFavoriteFolderCreating) {
setIsFavoriteFolderCreating(false); setIsFavoriteFolderCreating(false);
return; return;
} }
onSubmit?.(); onSubmit?.();
}, },
instanceId, focusId: dropdownId,
[onSubmit, isFavoriteFolderCreating], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [onSubmit, isFavoriteFolderCreating],
});
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Enter, keys: [Key.Enter],
() => { callback: () => {
if (filteredFolders.length === 1 && !showNoFolderOption) { if (filteredFolders.length === 1 && !showNoFolderOption) {
toggleFolderSelection(filteredFolders[0].id); toggleFolderSelection(filteredFolders[0].id);
onSubmit?.(); onSubmit?.();
@@ -85,9 +87,15 @@ export const FavoriteFolderPicker = ({
return; return;
} }
}, },
instanceId, focusId: instanceId,
[filteredFolders, showNoFolderOption, toggleFolderSelection, onSubmit], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [
filteredFolders,
showNoFolderOption,
toggleFolderSelection,
onSubmit,
],
});
return ( return (
<DropdownContent> <DropdownContent>
@@ -180,7 +180,6 @@ export const AdvancedFilterAddFilterRuleSelect = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 8, x: 0 }} dropdownOffset={{ y: 8, x: 0 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
@@ -22,7 +22,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
type AdvancedFilterDropdownFilterInputProps = { type AdvancedFilterDropdownFilterInputProps = {
filterDropdownId?: string; filterDropdownId: string;
recordFilter: RecordFilter; recordFilter: RecordFilter;
}; };
@@ -57,12 +57,15 @@ export const AdvancedFilterDropdownFilterInput = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilter.id} /> <ObjectFilterDropdownRecordSelect
recordFilterId={recordFilter.id}
dropdownId={filterDropdownId}
/>
</DropdownContent> </DropdownContent>
)} )}
{filterType === 'ACTOR' && {filterType === 'ACTOR' &&
(isActorSourceCompositeFilter ? ( (isActorSourceCompositeFilter ? (
<ObjectFilterDropdownSourceSelect /> <ObjectFilterDropdownSourceSelect dropdownId={filterDropdownId} />
) : ( ) : (
<ObjectFilterDropdownTextInput /> <ObjectFilterDropdownTextInput />
))} ))}
@@ -70,7 +73,7 @@ export const AdvancedFilterDropdownFilterInput = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect /> <ObjectFilterDropdownOptionSelect focusId={filterDropdownId} />
</DropdownContent> </DropdownContent>
)} )}
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />} {filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
@@ -34,7 +34,6 @@ export const AdvancedFilterFieldSelectDropdownButton = ({
recordFilterId={recordFilterId} recordFilterId={recordFilterId}
/> />
} }
dropdownHotkeyScope={{ scope: advancedFilterFieldSelectDropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
@@ -23,6 +23,7 @@ import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/uti
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext'; import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@@ -144,9 +145,10 @@ export const AdvancedFilterFieldSelectMenu = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<AdvancedFilterFieldSelectSearchInput /> <AdvancedFilterFieldSelectSearchInput />
<SelectableList <SelectableList
hotkeyScope={advancedFilterFieldSelectDropdownId} focusId={advancedFilterFieldSelectDropdownId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId} selectableListInstanceId={advancedFilterFieldSelectDropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{shouldShowVisibleFields && ( {shouldShowVisibleFields && (
<> <>
@@ -65,7 +65,6 @@ export const AdvancedFilterRecordFilterGroupOptionsDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={{ y: 2, x: 0 }} dropdownOffset={{ y: 2, x: 0 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
@@ -8,6 +8,7 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@@ -63,11 +64,12 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}> <DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
hotkeyScope={dropdownId} focusId={dropdownId}
selectableItemIdArray={operandsForFilterType.map( selectableItemIdArray={operandsForFilterType.map(
(operand) => operand, (operand) => operand,
)} )}
selectableListInstanceId={dropdownId} selectableListInstanceId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{operandsForFilterType.map((filterOperand, index) => ( {operandsForFilterType.map((filterOperand, index) => (
<SelectableListItem <SelectableListItem
@@ -90,7 +92,6 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
@@ -85,7 +85,6 @@ export const AdvancedFilterRecordFilterOptionsDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
/> />
@@ -18,6 +18,7 @@ import { CompositeFieldSubFieldName } from '@/settings/data-model/types/Composit
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -124,9 +125,10 @@ export const AdvancedFilterSubFieldSelectMenu = ({
</DropdownMenuHeader> </DropdownMenuHeader>
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
hotkeyScope={advancedFilterFieldSelectDropdownId} focusId={advancedFilterFieldSelectDropdownId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
selectableListInstanceId={advancedFilterFieldSelectDropdownId} selectableListInstanceId={advancedFilterFieldSelectDropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{compositeFieldTypeIsFilterableByAnySubField && ( {compositeFieldTypeIsFilterableByAnySubField && (
<SelectableListItem <SelectableListItem
@@ -104,9 +104,11 @@ export const AdvancedFilterValueInput = ({
/> />
} }
dropdownComponents={ dropdownComponents={
<AdvancedFilterDropdownFilterInput recordFilter={recordFilter} /> <AdvancedFilterDropdownFilterInput
recordFilter={recordFilter}
filterDropdownId={dropdownId}
/>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownOffset={dropdownContentOffset} dropdownOffset={dropdownContentOffset}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
onClose={handleFilterValueDropdownClose} onClose={handleFilterValueDropdownClose}
@@ -13,10 +13,10 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType'; import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object-record/record-filter/utils/getDefaultSubFieldNameForCompositeFilterableFieldType';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { isCompositeTypeNonFilterableByAnySubField } from '@/object-record/record-filter/utils/isCompositeTypeNonFilterableByAnySubField'; 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 { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@@ -44,7 +44,7 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
currentRecordFiltersComponentState, currentRecordFiltersComponentState,
); );
const setHotkeyScope = useSetHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { getFieldMetadataItemById } = useGetFieldMetadataItemById(); const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
@@ -76,7 +76,17 @@ export const useSelectFieldUsedInAdvancedFilterDropdown = () => {
fieldMetadataItem.type === 'RELATION' || fieldMetadataItem.type === 'RELATION' ||
fieldMetadataItem.type === 'SELECT' 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); const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);
@@ -3,10 +3,10 @@ import styled from '@emotion/styled';
import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue'; import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue';
import { useObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useObjectFilterDropdownFilterValue'; 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 { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@@ -57,7 +57,8 @@ export const ObjectFilterDropdownBooleanSelect = () => {
<SelectableList <SelectableList
selectableListInstanceId="boolean-select" selectableListInstanceId="boolean-select"
selectableItemIdArray={options.map((option) => option.toString())} selectableItemIdArray={options.map((option) => option.toString())}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId="boolean-select"
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{options.map((option) => ( {options.map((option) => (
@@ -25,7 +25,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
type ObjectFilterDropdownFilterInputProps = { type ObjectFilterDropdownFilterInputProps = {
filterDropdownId?: string; filterDropdownId: string;
recordFilterId?: string; recordFilterId?: string;
}; };
@@ -113,7 +113,10 @@ export const ObjectFilterDropdownFilterInput = ({
<> <>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownRecordSelect recordFilterId={recordFilterId} /> <ObjectFilterDropdownRecordSelect
recordFilterId={recordFilterId}
dropdownId={filterDropdownId}
/>
</> </>
)} )}
{filterType === 'ACTOR' && <ObjectFilterDropdownTextInput />} {filterType === 'ACTOR' && <ObjectFilterDropdownTextInput />}
@@ -123,7 +126,7 @@ export const ObjectFilterDropdownFilterInput = ({
<> <>
<ObjectFilterDropdownSearchInput /> <ObjectFilterDropdownSearchInput />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<ObjectFilterDropdownOptionSelect /> <ObjectFilterDropdownOptionSelect focusId={filterDropdownId} />
</> </>
)} )}
{filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />} {filterType === 'BOOLEAN' && <ObjectFilterDropdownBooleanSelect />}
@@ -3,7 +3,6 @@ import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenu
import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect'; import { ObjectFilterDropdownOperandSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; 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 { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext'; import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@@ -47,9 +46,6 @@ export const ObjectFilterDropdownOperandDropdown = ({
</StyledDropdownMenuHeader> </StyledDropdownMenuHeader>
} }
dropdownComponents={<ObjectFilterDropdownOperandSelect />} dropdownComponents={<ObjectFilterDropdownOperandSelect />}
dropdownHotkeyScope={{
scope: FiltersHotkeyScope.ObjectFilterDropdownOperandDropdown,
}}
dropdownOffset={{ x: parseInt(theme.spacing(2), 10) }} dropdownOffset={{ x: parseInt(theme.spacing(2), 10) }}
/> />
</ClickOutsideListenerContext.Provider> </ClickOutsideListenerContext.Provider>
@@ -14,9 +14,9 @@ import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/ob
import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; 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 { 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 { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
@@ -30,7 +30,11 @@ type SelectOptionForFilter = FieldMetadataItemOption & {
isSelected: boolean; isSelected: boolean;
}; };
export const ObjectFilterDropdownOptionSelect = () => { export const ObjectFilterDropdownOptionSelect = ({
focusId,
}: {
focusId: string;
}) => {
const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2( const fieldMetadataItemUsedInDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector, fieldMetadataItemUsedInDropdownComponentSelector,
); );
@@ -92,15 +96,16 @@ export const ObjectFilterDropdownOptionSelect = () => {
} }
}, [selectedOptions, selectOptions]); }, [selectedOptions, selectOptions]);
useScopedHotkeys( useHotkeysOnFocusedElement({
[Key.Escape], keys: [Key.Escape],
() => { callback: () => {
closeDropdown(); closeDropdown();
resetSelectedItem(); resetSelectedItem();
}, },
SingleRecordPickerHotkeyScope.SingleRecordPicker, focusId,
[closeDropdown, resetSelectedItem], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [closeDropdown, resetSelectedItem],
});
const handleMultipleOptionSelectChange = ( const handleMultipleOptionSelectChange = (
optionChanged: SelectOptionForFilter, optionChanged: SelectOptionForFilter,
@@ -148,7 +153,8 @@ export const ObjectFilterDropdownOptionSelect = () => {
<SelectableList <SelectableList
selectableListInstanceId={componentInstanceId} selectableListInstanceId={componentInstanceId}
selectableItemIdArray={objectRecordsIds} selectableItemIdArray={objectRecordsIds}
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{showNoResult ? ( {showNoResult ? (
@@ -8,7 +8,6 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; 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 { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect'; import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
@@ -29,10 +28,12 @@ export const MAX_RECORDS_TO_DISPLAY = 3;
type ObjectFilterDropdownRecordSelectProps = { type ObjectFilterDropdownRecordSelectProps = {
recordFilterId?: string; recordFilterId?: string;
dropdownId: string;
}; };
export const ObjectFilterDropdownRecordSelect = ({ export const ObjectFilterDropdownRecordSelect = ({
recordFilterId, recordFilterId,
dropdownId,
}: ObjectFilterDropdownRecordSelectProps) => { }: ObjectFilterDropdownRecordSelectProps) => {
const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2( const fieldMetadataItemUsedInFilterDropdown = useRecoilComponentValueV2(
fieldMetadataItemUsedInDropdownComponentSelector, fieldMetadataItemUsedInDropdownComponentSelector,
@@ -220,7 +221,7 @@ export const ObjectFilterDropdownRecordSelect = ({
)} )}
<MultipleSelectDropdown <MultipleSelectDropdown
selectableListId="object-filter-record-select-id" selectableListId="object-filter-record-select-id"
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId={dropdownId}
itemsToSelect={recordsToSelect} itemsToSelect={recordsToSelect}
filteredSelectedItems={filteredSelectedRecords} filteredSelectedItems={filteredSelectedRecords}
selectedItems={selectedRecords} selectedItems={selectedRecords}
@@ -3,7 +3,6 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { getActorSourceMultiSelectOptions } from '@/object-record/object-filter-dropdown/utils/getActorSourceMultiSelectOptions'; 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 { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; 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 EMPTY_FILTER_VALUE = '[]';
export const MAX_ITEMS_TO_DISPLAY = 3; export const MAX_ITEMS_TO_DISPLAY = 3;
export const ObjectFilterDropdownSourceSelect = () => { export const ObjectFilterDropdownSourceSelect = ({
dropdownId,
}: {
dropdownId: string;
}) => {
const objectFilterDropdownSearchInput = useRecoilComponentValueV2( const objectFilterDropdownSearchInput = useRecoilComponentValueV2(
objectFilterDropdownSearchInputComponentState, objectFilterDropdownSearchInputComponentState,
); );
@@ -78,7 +81,7 @@ export const ObjectFilterDropdownSourceSelect = () => {
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<MultipleSelectDropdown <MultipleSelectDropdown
selectableListId="object-filter-source-select-id" selectableListId="object-filter-source-select-id"
hotkeyScope={SingleRecordPickerHotkeyScope.SingleRecordPicker} focusId={dropdownId}
itemsToSelect={sourceTypes.filter( itemsToSelect={sourceTypes.filter(
(item) => (item) =>
!filteredSelectedItems.some((selected) => selected.id === item.id), !filteredSelectedItems.some((selected) => selected.id === item.id),
@@ -1,5 +0,0 @@
export enum FiltersHotkeyScope {
ObjectFilterDropdownButton = 'filter-dropdown-button',
ObjectSortDropdownButton = 'sort-dropdown-button',
ObjectFilterDropdownOperandDropdown = 'filter-dropdown-operand-dropdown',
}
@@ -7,7 +7,6 @@ import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dro
import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId'; import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId';
import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal'; import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal';
import { useRecordGroupReorderConfirmationModal } from '@/object-record/record-group/hooks/useRecordGroupReorderConfirmationModal'; 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 { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@@ -40,7 +39,6 @@ export const ObjectOptionsDropdown = ({
<> <>
<Dropdown <Dropdown
dropdownId={OBJECT_OPTIONS_DROPDOWN_ID} dropdownId={OBJECT_OPTIONS_DROPDOWN_ID}
dropdownHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }} dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
clickableComponent={ clickableComponent={
<StyledHeaderDropdownButton isUnfolded={isDropdownOpen}> <StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>
@@ -4,12 +4,12 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu'; import { useSetViewTypeFromLayoutOptionsMenu } from '@/object-record/object-options-dropdown/hooks/useSetViewTypeFromLayoutOptionsMenu';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; 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 { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; 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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -108,8 +108,9 @@ export const ObjectOptionsDropdownLayoutContent = () => {
{!!currentView && ( {!!currentView && (
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
<SelectableListItem <SelectableListItem
@@ -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 { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions'; import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; 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 { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@@ -53,8 +53,9 @@ export const ObjectOptionsDropdownLayoutOpenInContent = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<SelectableListItem <SelectableListItem
itemId={ViewOpenRecordInType.SIDE_PANEL} itemId={ViewOpenRecordInType.SIDE_PANEL}
@@ -1,20 +1,17 @@
import { Key } from 'ts-key-enum';
import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName'; import { ObjectOptionsDropdownMenuViewName } from '@/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuViewName';
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId'; import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard'; import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown'; import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; 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 { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; 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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
@@ -48,14 +45,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
(isDefined(currentView?.viewGroups) && currentView.viewGroups.length > 0) || (isDefined(currentView?.viewGroups) && currentView.viewGroups.length > 0) ||
currentView?.key !== 'INDEX'; currentView?.key !== 'INDEX';
useScopedHotkeys(
[Key.Escape],
() => {
closeDropdown();
},
TableOptionsHotkeyScope.Dropdown,
);
const { visibleBoardFields } = useObjectOptionsForBoard({ const { visibleBoardFields } = useObjectOptionsForBoard({
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
recordBoardId: recordIndexId, recordBoardId: recordIndexId,
@@ -101,8 +90,9 @@ export const ObjectOptionsDropdownMenuContent = () => {
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
<SelectableListItem <SelectableListItem
@@ -4,12 +4,13 @@ import { useUpdateObjectViewOptions } from '@/object-record/object-options-dropd
import { IconPicker } from '@/ui/input/components/IconPicker'; import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextInputV2 } from '@/ui/input/components/TextInputV2'; import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { View } from '@/views/types/View'; 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 { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState';
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState'; import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState'; import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
@@ -75,17 +76,19 @@ export const ObjectOptionsDropdownMenuViewName = ({
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState(); const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
const [viewName, setViewName] = useState(currentView?.name); const [viewName, setViewName] = useState(currentView?.name);
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Enter, keys: [Key.Enter],
async () => { callback: async () => {
if (viewPickerIsPersisting) { if (viewPickerIsPersisting) {
return; return;
} }
await updateViewFromCurrentState(); await updateViewFromCurrentState();
}, },
ViewsHotkeyScope.ListDropdown, focusId: VIEW_PICKER_DROPDOWN_ID,
); scope: DropdownHotkeyScope.Dropdown,
dependencies: [viewPickerIsPersisting, updateViewFromCurrentState],
});
const handleIconChange = ({ iconKey }: { iconKey: string }) => { const handleIconChange = ({ iconKey }: { iconKey: string }) => {
setViewPickerIsDirty(true); setViewPickerIsDirty(true);
@@ -5,11 +5,11 @@ import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hook
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector'; import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort'; import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState'; 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 { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@@ -73,8 +73,9 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<SelectableListItem <SelectableListItem
itemId={RecordGroupSort.Manual} itemId={RecordGroupSort.Manual}
@@ -9,12 +9,12 @@ import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-gr
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState'; import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState'; 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 { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; 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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
@@ -94,6 +94,8 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
'HideEmptyGroups', 'HideEmptyGroups',
]; ];
const hiddenGroupsSelectableListId = `${OBJECT_OPTIONS_DROPDOWN_ID}-hidden-groups`;
return ( return (
<DropdownContent> <DropdownContent>
<DropdownMenuHeader <DropdownMenuHeader
@@ -109,8 +111,9 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID} selectableListInstanceId={OBJECT_OPTIONS_DROPDOWN_ID}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={OBJECT_OPTIONS_DROPDOWN_ID}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{currentView?.key !== 'INDEX' && ( {currentView?.key !== 'INDEX' && (
<> <>
@@ -175,9 +178,10 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
<SelectableList <SelectableList
selectableListInstanceId={`${OBJECT_OPTIONS_DROPDOWN_ID}-hidden-groups`} selectableListInstanceId={hiddenGroupsSelectableListId}
hotkeyScope={TableOptionsHotkeyScope.Dropdown} focusId={hiddenGroupsSelectableListId}
selectableItemIdArray={['HiddenGroups']} selectableItemIdArray={['HiddenGroups']}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<SelectableListItem <SelectableListItem
itemId="HiddenGroups" itemId="HiddenGroups"
@@ -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 { useCloseSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useCloseSortDropdown';
import { useResetRecordSortDropdownSearchInput } from '@/object-record/object-sort-dropdown/hooks/useResetRecordSortDropdownSearchInput'; import { useResetRecordSortDropdownSearchInput } from '@/object-record/object-sort-dropdown/hooks/useResetRecordSortDropdownSearchInput';
import { useResetSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useResetSortDropdown'; 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 { isRecordSortDirectionDropdownMenuUnfoldedComponentState } from '@/object-record/object-sort-dropdown/states/isRecordSortDirectionDropdownMenuUnfoldedComponentState';
import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState'; import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState';
import { selectedRecordSortDirectionComponentState } from '@/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState'; 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 { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
@@ -86,11 +86,7 @@ export type ObjectSortDropdownButtonProps = {
hotkeyScope: HotkeyScope; hotkeyScope: HotkeyScope;
}; };
export const ObjectSortDropdownButton = ({ export const ObjectSortDropdownButton = () => {
hotkeyScope,
}: ObjectSortDropdownButtonProps) => {
const { toggleSortDropdown } = useToggleSortDropdown();
const { resetRecordSortDropdownSearchInput } = const { resetRecordSortDropdownSearchInput } =
useResetRecordSortDropdownSearchInput(); useResetRecordSortDropdownSearchInput();
@@ -162,15 +158,16 @@ export const ObjectSortDropdownButton = ({
const shouldShowSeparator = const shouldShowSeparator =
visibleFieldMetadataItems.length > 0 && hiddenFieldMetadataItems.length > 0; visibleFieldMetadataItems.length > 0 && hiddenFieldMetadataItems.length > 0;
const handleButtonClick = () => {
toggleSortDropdown();
};
const handleDropdownButtonClose = () => { const handleDropdownButtonClose = () => {
resetRecordSortDropdownSearchInput(); resetRecordSortDropdownSearchInput();
resetSortDropdown(); resetSortDropdown();
}; };
const handleDropdownOpen = () => {
resetSortDropdown();
setSelectedItemId(selectableItemIdArray[0]);
};
const { closeSortDropdown } = useCloseSortDropdown(); const { closeSortDropdown } = useCloseSortDropdown();
const { upsertRecordSort } = useUpsertRecordSort(); const { upsertRecordSort } = useUpsertRecordSort();
@@ -224,16 +221,10 @@ export const ObjectSortDropdownButton = ({
return ( return (
<Dropdown <Dropdown
dropdownId={OBJECT_SORT_DROPDOWN_ID} dropdownId={OBJECT_SORT_DROPDOWN_ID}
dropdownHotkeyScope={hotkeyScope}
dropdownOffset={{ y: 8 }} dropdownOffset={{ y: 8 }}
onOpen={handleDropdownOpen}
clickableComponent={ clickableComponent={
<StyledHeaderDropdownButton <StyledHeaderDropdownButton isUnfolded={isDropdownOpen}>
onClick={() => {
handleButtonClick();
setSelectedItemId(selectableItemIdArray[0]);
}}
isUnfolded={isDropdownOpen}
>
<Trans>Sort</Trans> <Trans>Sort</Trans>
</StyledHeaderDropdownButton> </StyledHeaderDropdownButton>
} }
@@ -241,8 +232,9 @@ export const ObjectSortDropdownButton = ({
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}> <DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
<SelectableList <SelectableList
selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID} selectableListInstanceId={OBJECT_SORT_DROPDOWN_ID}
hotkeyScope={hotkeyScope.scope}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
focusId={OBJECT_SORT_DROPDOWN_ID}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{isRecordSortDirectionMenuUnfolded && ( {isRecordSortDirectionMenuUnfolded && (
<StyledSelectedSortDirectionContainer> <StyledSelectedSortDirectionContainer>
@@ -9,7 +9,6 @@ import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/r
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector'; 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 { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody'; import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
@@ -155,7 +154,8 @@ export const RecordBoardCard = () => {
y: event.clientY, y: event.clientY,
}); });
openDropdown(actionMenuDropdownId, { openDropdown(actionMenuDropdownId, {
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown, enableGlobalHotkeysWithModifiers: true,
enableGlobalHotkeysConflictingWithKeyboard: false,
}); });
}; };
@@ -5,7 +5,6 @@ import { RecordBoardColumnHeaderAggregateDropdownComponentInstanceContext } from
import { RecordBoardColumnHeaderAggregateDropdownButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownButton'; 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 { AggregateDropdownContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContent';
import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownContext'; 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 { RecordBoardColumnHeaderAggregateContentId } from '@/object-record/record-board/types/RecordBoardColumnHeaderAggregateContentId';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
@@ -42,9 +41,6 @@ export const RecordBoardColumnHeaderAggregateDropdown = ({
<Dropdown <Dropdown
onClose={handleResetContent} onClose={handleResetContent}
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: RecordBoardColumnHotkeyScope.ColumnHeader,
}}
dropdownOffset={{ y: DROPDOWN_OFFSET_Y }} dropdownOffset={{ y: DROPDOWN_OFFSET_Y }}
clickableComponent={ clickableComponent={
<RecordBoardColumnHeaderAggregateDropdownButton <RecordBoardColumnHeaderAggregateDropdownButton
@@ -11,6 +11,7 @@ import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldM
import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay'; import { MultiSelectDisplay } from '@/ui/field/display/components/MultiSelectDisplay';
import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput'; import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
import { InputLabel } from '@/ui/input/components/InputLabel'; import { InputLabel } from '@/ui/input/components/InputLabel';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
@@ -21,7 +22,6 @@ import { isDefined } from 'twenty-shared/utils';
import { VisibilityHidden } from 'twenty-ui/accessibility'; import { VisibilityHidden } from 'twenty-ui/accessibility';
import { IconChevronDown } from 'twenty-ui/display'; import { IconChevronDown } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
type FormMultiSelectFieldInputProps = { type FormMultiSelectFieldInputProps = {
label?: string; label?: string;
@@ -256,7 +256,7 @@ export const FormMultiSelectFieldInput = ({
selectableListComponentInstanceId={ selectableListComponentInstanceId={
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
} }
hotkeyScope={hotkeyScope} focusId={hotkeyScope}
options={options} options={options}
onCancel={onCancel} onCancel={onCancel}
onOptionSelected={onOptionSelected} onOptionSelected={onOptionSelected}
@@ -10,6 +10,7 @@ import { singleRecordPickerSelectedIdComponentState } from '@/object-record/reco
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { InputLabel } from '@/ui/input/components/InputLabel'; import { InputLabel } from '@/ui/input/components/InputLabel';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; 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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
@@ -18,7 +19,6 @@ import styled from '@emotion/styled';
import { useCallback, useId } from 'react'; import { useCallback, useId } from 'react';
import { isDefined, isValidUuid } from 'twenty-shared/utils'; import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { IconChevronDown, IconForbid } from 'twenty-ui/display'; import { IconChevronDown, IconForbid } from 'twenty-ui/display';
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
const StyledFormSelectContainer = styled(FormFieldInputInnerContainer)<{ const StyledFormSelectContainer = styled(FormFieldInputInnerContainer)<{
readonly?: boolean; readonly?: boolean;
@@ -189,6 +189,7 @@ export const FormSingleRecordPicker = ({
} }
dropdownComponents={ dropdownComponents={
<SingleRecordPicker <SingleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId} componentInstanceId={dropdownId}
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
emptyLabel={'No ' + objectNameSingular} emptyLabel={'No ' + objectNameSingular}
@@ -199,7 +200,6 @@ export const FormSingleRecordPicker = ({
dropdownWidth={GenericDropdownContentWidth.ExtraLarge} dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
/> />
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
)} )}
{isDefined(VariablePicker) && !disabled && ( {isDefined(VariablePicker) && !disabled && (
@@ -7,6 +7,7 @@ import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTa
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useOpenRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput'; 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 { 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 { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { import {
FieldMetadata, FieldMetadata,
@@ -15,11 +16,13 @@ import {
} from '@/object-record/record-field/types/FieldMetadata'; } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects'; import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject'; 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 { INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY } from '@/object-record/record-inline-cell/constants/InlineCellHotkeyScopeMemoizeKey';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; 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 { 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 { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@@ -31,7 +34,7 @@ export const useOpenFieldInputEditMode = () => {
const { openActivityTargetCellEditMode } = const { openActivityTargetCellEditMode } =
useOpenActivityTargetCellEditMode(); useOpenActivityTargetCellEditMode();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openFieldInput = useRecoilCallback( const openFieldInput = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
@@ -72,7 +75,10 @@ export const useOpenFieldInputEditMode = () => {
}); });
openActivityTargetCellEditMode({ openActivityTargetCellEditMode({
recordPickerInstanceId: `relation-from-many-field-input-${recordId}`, recordPickerInstanceId: getRelationFromManyFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
}),
activityTargetObjectRecords, activityTargetObjectRecords,
}); });
return; return;
@@ -103,9 +109,22 @@ export const useOpenFieldInputEditMode = () => {
} }
} }
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: DEFAULT_CELL_SCOPE.scope, focusId: getFieldInputInstanceId(
customScopes: DEFAULT_CELL_SCOPE.customScopes, 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, memoizeKey: INLINE_CELL_HOTKEY_SCOPE_MEMOIZE_KEY,
}); });
}, },
@@ -113,7 +132,7 @@ export const useOpenFieldInputEditMode = () => {
openActivityTargetCellEditMode, openActivityTargetCellEditMode,
openRelationFromManyFieldInput, openRelationFromManyFieldInput,
openRelationToOneFieldInput, openRelationToOneFieldInput,
setHotkeyScopeAndMemorizePreviousScope, pushFocusItemToFocusStack,
], ],
); );
@@ -39,6 +39,7 @@ export const useMultiSelectField = () => {
const draftValue = useRecoilValue(getDraftValueSelector()); const draftValue = useRecoilValue(getDraftValueSelector());
return { return {
recordId,
fieldDefinition, fieldDefinition,
persistField, persistField,
fieldValues: fieldMultiSelectValues, fieldValues: fieldMultiSelectValues,
@@ -34,6 +34,7 @@ export const useSelectField = () => {
const draftValue = useRecoilValue(getDraftValueSelector()); const draftValue = useRecoilValue(getDraftValueSelector());
return { return {
recordId,
fieldDefinition, fieldDefinition,
persistField, persistField,
fieldValue: fieldSelectValue, fieldValue: fieldSelectValue,
@@ -1,6 +1,6 @@
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField'; 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 { 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'; import { MultiSelectInput } from '@/ui/field/input/components/MultiSelectInput';
type MultiSelectFieldInputProps = { type MultiSelectFieldInputProps = {
@@ -10,14 +10,18 @@ type MultiSelectFieldInputProps = {
export const MultiSelectFieldInput = ({ export const MultiSelectFieldInput = ({
onCancel, onCancel,
}: MultiSelectFieldInputProps) => { }: MultiSelectFieldInputProps) => {
const { persistField, fieldDefinition, fieldValues } = useMultiSelectField(); const { persistField, fieldDefinition, fieldValues, recordId } =
useMultiSelectField();
return ( return (
<MultiSelectInput <MultiSelectInput
selectableListComponentInstanceId={ selectableListComponentInstanceId={
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
} }
hotkeyScope={DEFAULT_CELL_SCOPE.scope} focusId={getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
)}
options={fieldDefinition.metadata.options} options={fieldDefinition.metadata.options}
onCancel={onCancel} onCancel={onCancel}
onOptionSelected={persistField} onOptionSelected={persistField}
@@ -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 { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; 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 { 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 { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
@@ -25,7 +26,10 @@ export const RelationFromManyFieldInput = ({
onSubmit, onSubmit,
}: RelationFromManyFieldInputProps) => { }: RelationFromManyFieldInputProps) => {
const { fieldDefinition, recordId } = useContext(FieldContext); 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 { updateRelation } = useUpdateRelationFromManyFieldInput();
const fieldName = fieldDefinition.metadata.fieldName; const fieldName = fieldDefinition.metadata.fieldName;
@@ -84,6 +88,7 @@ export const RelationFromManyFieldInput = ({
return ( return (
<MultipleRecordPicker <MultipleRecordPicker
focusId={recordPickerInstanceId}
componentInstanceId={recordPickerInstanceId} componentInstanceId={recordPickerInstanceId}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onChange={(morphItem) => { onChange={(morphItem) => {
@@ -3,6 +3,7 @@ import { useRelationField } from '../../hooks/useRelationField';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer'; 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 { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState'; import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
@@ -25,7 +26,10 @@ export const RelationToOneFieldInput = ({
const persistField = usePersistField(); const persistField = usePersistField();
const recordPickerInstanceId = `relation-to-one-field-input-${recordId}-${fieldDefinition.metadata.fieldName}`; const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
recordId,
fieldName: fieldDefinition.metadata.fieldName,
});
const handleRecordSelected = ( const handleRecordSelected = (
selectedRecord: SingleRecordPickerRecord | null | undefined, selectedRecord: SingleRecordPickerRecord | null | undefined,
@@ -64,6 +68,7 @@ export const RelationToOneFieldInput = ({
return ( return (
<SingleRecordPicker <SingleRecordPicker
focusId={recordPickerInstanceId}
componentInstanceId={recordPickerInstanceId} componentInstanceId={recordPickerInstanceId}
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
emptyLabel={'No ' + fieldDefinition.label} emptyLabel={'No ' + fieldDefinition.label}
@@ -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 { 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 { 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 { 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 { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { SelectInput } from '@/ui/field/input/components/SelectInput'; import { SelectInput } from '@/ui/field/input/components/SelectInput';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
@@ -20,7 +21,8 @@ export const SelectFieldInput = ({
onSubmit, onSubmit,
onCancel, onCancel,
}: SelectFieldInputProps) => { }: SelectFieldInputProps) => {
const { persistField, fieldDefinition, fieldValue } = useSelectField(); const { persistField, fieldDefinition, fieldValue, recordId } =
useSelectField();
const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]); const [filteredOptions, setFilteredOptions] = useState<SelectOption[]>([]);
@@ -65,7 +67,10 @@ export const SelectFieldInput = ({
SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID SELECT_FIELD_INPUT_SELECTABLE_LIST_COMPONENT_INSTANCE_ID
} }
selectableItemIdArray={optionIds} selectableItemIdArray={optionIds}
hotkeyScope={DEFAULT_CELL_SCOPE.scope} focusId={getFieldInputInstanceId(
recordId,
fieldDefinition.metadata.fieldName,
)}
onEnter={(itemId) => { onEnter={(itemId) => {
const option = filteredOptions.find( const option = filteredOptions.find(
(option) => option.value === itemId, (option) => option.value === itemId,
@@ -18,9 +18,10 @@ import {
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode'; 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 { 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 { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { DropdownHotkeyScope } from '@/ui/layout/dropdown/constants/DropdownHotkeyScope';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from '~/generated-metadata/graphql'; import { RelationType } from '~/generated-metadata/graphql';
@@ -71,7 +72,7 @@ const RelationManyFieldInputWithContext = () => {
useEffect(() => { useEffect(() => {
setRecordStoreFieldValue([]); setRecordStoreFieldValue([]);
setHotKeyScope(MultipleRecordPickerHotkeyScope.MultipleRecordPicker); setHotKeyScope(DropdownHotkeyScope.Dropdown);
openFieldInput({ openFieldInput({
fieldDefinition, fieldDefinition,
recordId: 'recordId', recordId: 'recordId',
@@ -87,7 +88,10 @@ const RelationManyFieldInputWithContext = () => {
<div> <div>
<RecordFieldComponentInstanceContext.Provider <RecordFieldComponentInstanceContext.Provider
value={{ value={{
instanceId: 'relation-from-many-field-record-id-people', instanceId: getRelationFromManyFieldInputInstanceId({
recordId: 'recordId',
fieldName: 'people',
}),
}} }}
> >
<FieldContext.Provider <FieldContext.Provider
@@ -5,7 +5,6 @@ import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@@ -17,9 +16,12 @@ import {
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; 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 { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionLoadingComponentState'; 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 { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { getCanvasElementForDropdownTesting } from 'twenty-ui/testing'; import { getCanvasElementForDropdownTesting } from 'twenty-ui/testing';
@@ -62,11 +64,30 @@ const RelationToOneFieldInputWithContext = ({
onSubmit, onSubmit,
onCancel, onCancel,
}: RelationToOneFieldInputWithContextProps) => { }: RelationToOneFieldInputWithContextProps) => {
const setHotKeyScope = useSetHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
useEffect(() => { useEffect(() => {
setHotKeyScope(SingleRecordPickerHotkeyScope.SingleRecordPicker); pushFocusItemToFocusStack({
}, [setHotKeyScope]); 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 ( return (
<div> <div>
@@ -1,4 +1,5 @@
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getRelationFromManyFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationFromManyFieldInputInstanceId';
import { import {
FieldRelationFromManyValue, FieldRelationFromManyValue,
FieldRelationValue, FieldRelationValue,
@@ -6,17 +7,18 @@ import {
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch'; 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 { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState'; 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 { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; 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 { useRecoilCallback } from 'recoil';
export const useOpenRelationFromManyFieldInput = () => { export const useOpenRelationFromManyFieldInput = () => {
const { performSearch } = useMultipleRecordPickerPerformSearch(); const { performSearch } = useMultipleRecordPickerPerformSearch();
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openRelationFromManyFieldInput = useRecoilCallback( const openRelationFromManyFieldInput = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@@ -29,7 +31,10 @@ export const useOpenRelationFromManyFieldInput = () => {
objectNameSingular: string; objectNameSingular: string;
recordId: string; recordId: string;
}) => { }) => {
const recordPickerInstanceId = `relation-from-many-field-input-${recordId}`; const recordPickerInstanceId = getRelationFromManyFieldInputInstanceId({
recordId,
fieldName,
});
const fieldValue = snapshot const fieldValue = snapshot
.getLoadable<FieldRelationValue<FieldRelationFromManyValue>>( .getLoadable<FieldRelationValue<FieldRelationFromManyValue>>(
@@ -88,11 +93,19 @@ export const useOpenRelationFromManyFieldInput = () => {
forcePickableMorphItems: pickableMorphItems, forcePickableMorphItems: pickableMorphItems,
}); });
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: MultipleRecordPickerHotkeyScope.MultipleRecordPicker, focusId: recordPickerInstanceId,
component: {
type: FocusComponentType.DROPDOWN,
instanceId: recordPickerInstanceId,
},
hotkeyScope: {
scope: DropdownHotkeyScope.Dropdown,
},
memoizeKey: recordPickerInstanceId,
}); });
}, },
[performSearch, setHotkeyScopeAndMemorizePreviousScope], [performSearch, pushFocusItemToFocusStack],
); );
return { openRelationFromManyFieldInput }; return { openRelationFromManyFieldInput };
@@ -1,21 +1,26 @@
import { getRelationToOneFieldInputInstanceId } from '@/object-record/record-field/meta-types/input/utils/getRelationToOneFieldInputInstanceId';
import { import {
FieldRelationToOneValue, FieldRelationToOneValue,
FieldRelationValue, FieldRelationValue,
} from '@/object-record/record-field/types/FieldMetadata'; } from '@/object-record/record-field/types/FieldMetadata';
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState'; 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 { 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 { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
export const useOpenRelationToOneFieldInput = () => { export const useOpenRelationToOneFieldInput = () => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openRelationToOneFieldInput = useRecoilCallback( const openRelationToOneFieldInput = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
({ fieldName, recordId }: { fieldName: string; recordId: string }) => { ({ fieldName, recordId }: { fieldName: string; recordId: string }) => {
const recordPickerInstanceId = `relation-to-one-field-input-${recordId}-${fieldName}`; const recordPickerInstanceId = getRelationToOneFieldInputInstanceId({
recordId,
fieldName,
});
const fieldValue = snapshot const fieldValue = snapshot
.getLoadable<FieldRelationValue<FieldRelationToOneValue>>( .getLoadable<FieldRelationValue<FieldRelationToOneValue>>(
recordStoreFamilySelector({ recordStoreFamilySelector({
@@ -34,11 +39,18 @@ export const useOpenRelationToOneFieldInput = () => {
); );
} }
setHotkeyScopeAndMemorizePreviousScope({ pushFocusItemToFocusStack({
scope: SingleRecordPickerHotkeyScope.SingleRecordPicker, 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 }; return { openRelationToOneFieldInput };
@@ -1,3 +0,0 @@
export enum RelationPickerHotkeyScope {
AddNew = 'add-new',
}
@@ -0,0 +1,9 @@
export const getRelationFromManyFieldInputInstanceId = ({
recordId,
fieldName,
}: {
recordId: string;
fieldName: string;
}): string => {
return `relation-from-many-field-input-${recordId}-${fieldName}`;
};
@@ -0,0 +1,9 @@
export const getRelationToOneFieldInputInstanceId = ({
recordId,
fieldName,
}: {
recordId: string;
fieldName: string;
}): string => {
return `relation-to-one-field-input-${recordId}-${fieldName}`;
};
@@ -0,0 +1,6 @@
export const getFieldInputInstanceId = (
recordId: string,
fieldName: string,
) => {
return `${recordId}-${fieldName}`;
};
@@ -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 { 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 { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState'; 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 { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types/RecordPickerLayoutDirection'; import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types/RecordPickerLayoutDirection';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; 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 { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; 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 { 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 { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRef } from 'react'; import { useRef } from 'react';
@@ -36,6 +36,7 @@ type MultipleRecordPickerProps = {
layoutDirection?: RecordPickerLayoutDirection; layoutDirection?: RecordPickerLayoutDirection;
componentInstanceId: string; componentInstanceId: string;
onClickOutside: () => void; onClickOutside: () => void;
focusId: string;
}; };
export const MultipleRecordPicker = ({ export const MultipleRecordPicker = ({
@@ -45,6 +46,7 @@ export const MultipleRecordPicker = ({
onClickOutside, onClickOutside,
layoutDirection = 'search-bar-on-bottom', layoutDirection = 'search-bar-on-bottom',
componentInstanceId, componentInstanceId,
focusId,
}: MultipleRecordPickerProps) => { }: MultipleRecordPickerProps) => {
const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope(); const { goBackToPreviousHotkeyScope } = usePreviousHotkeyScope();
@@ -94,14 +96,15 @@ export const MultipleRecordPicker = ({
resetState(); resetState();
}; };
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Escape, keys: [Key.Escape],
() => { callback: () => {
handleSubmit(); handleSubmit();
}, },
MultipleRecordPickerHotkeyScope.MultipleRecordPicker, focusId,
[handleSubmit], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [handleSubmit],
});
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -140,13 +143,19 @@ export const MultipleRecordPicker = ({
{layoutDirection === 'search-bar-on-bottom' && ( {layoutDirection === 'search-bar-on-bottom' && (
<> <>
{createNewButtonSection} {createNewButtonSection}
<MultipleRecordPickerItemsDisplay onChange={onChange} /> <MultipleRecordPickerItemsDisplay
onChange={onChange}
focusId={focusId}
/>
</> </>
)} )}
<MultipleRecordPickerSearchInput /> <MultipleRecordPickerSearchInput />
{layoutDirection === 'search-bar-on-top' && ( {layoutDirection === 'search-bar-on-top' && (
<> <>
<MultipleRecordPickerItemsDisplay onChange={onChange} /> <MultipleRecordPickerItemsDisplay
onChange={onChange}
focusId={focusId}
/>
{createNewButtonSection} {createNewButtonSection}
</> </>
)} )}
@@ -10,8 +10,10 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
export const MultipleRecordPickerItemsDisplay = ({ export const MultipleRecordPickerItemsDisplay = ({
onChange, onChange,
focusId,
}: { }: {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void; onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
}) => { }) => {
const componentInstanceId = useAvailableComponentInstanceIdOrThrow( const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
MultipleRecordPickerComponentInstanceContext, MultipleRecordPickerComponentInstanceContext,
@@ -33,7 +35,7 @@ export const MultipleRecordPickerItemsDisplay = ({
{isLoading && itemsLength === 0 ? ( {isLoading && itemsLength === 0 ? (
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />
) : ( ) : (
<MultipleRecordPickerMenuItems onChange={onChange} /> <MultipleRecordPickerMenuItems onChange={onChange} focusId={focusId} />
)} )}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>
@@ -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 { 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 { 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 { 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 { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem'; import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
@@ -25,10 +25,12 @@ export const StyledSelectableItem = styled(SelectableListItem)`
type MultipleRecordPickerMenuItemsProps = { type MultipleRecordPickerMenuItemsProps = {
onChange?: (morphItem: RecordPickerPickableMorphItem) => void; onChange?: (morphItem: RecordPickerPickableMorphItem) => void;
focusId: string;
}; };
export const MultipleRecordPickerMenuItems = ({ export const MultipleRecordPickerMenuItems = ({
onChange, onChange,
focusId,
}: MultipleRecordPickerMenuItemsProps) => { }: MultipleRecordPickerMenuItemsProps) => {
const componentInstanceId = useAvailableComponentInstanceIdOrThrow( const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
MultipleRecordPickerComponentInstanceContext, MultipleRecordPickerComponentInstanceContext,
@@ -85,7 +87,8 @@ export const MultipleRecordPickerMenuItems = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={pickableRecordIds} selectableItemIdArray={pickableRecordIds}
hotkeyScope={MultipleRecordPickerHotkeyScope.MultipleRecordPicker} focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{pickableRecordIds.map((recordId) => { {pickableRecordIds.map((recordId) => {
return ( return (
@@ -1,3 +0,0 @@
export enum MultipleRecordPickerHotkeyScope {
MultipleRecordPicker = 'multiple-record-picker',
}
@@ -28,6 +28,7 @@ export const SingleRecordPicker = ({
componentInstanceId, componentInstanceId,
layoutDirection, layoutDirection,
dropdownWidth, dropdownWidth,
focusId,
}: SingleRecordPickerProps) => { }: SingleRecordPickerProps) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -71,6 +72,7 @@ export const SingleRecordPicker = ({
> >
<DropdownContent ref={containerRef} widthInPixels={dropdownWidth}> <DropdownContent ref={containerRef} widthInPixels={dropdownWidth}>
<SingleRecordPickerMenuItemsWithSearch <SingleRecordPickerMenuItemsWithSearch
focusId={focusId}
{...{ {...{
EmptyIcon, EmptyIcon,
emptyLabel, emptyLabel,
@@ -4,16 +4,16 @@ import { Key } from 'ts-key-enum';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; 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 { 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 { 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 { 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 { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { getSingleRecordPickerSelectableListId } from '@/object-record/record-picker/single-record-picker/utils/getSingleRecordPickerSelectableListId'; 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 { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector'; 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 { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
@@ -29,7 +29,7 @@ export type SingleRecordPickerMenuItemsProps = {
onCancel?: () => void; onCancel?: () => void;
onRecordSelected: (entity?: SingleRecordPickerRecord) => void; onRecordSelected: (entity?: SingleRecordPickerRecord) => void;
selectedRecord?: SingleRecordPickerRecord; selectedRecord?: SingleRecordPickerRecord;
hotkeyScope?: string; focusId: string;
}; };
export const SingleRecordPickerMenuItems = ({ export const SingleRecordPickerMenuItems = ({
@@ -40,7 +40,7 @@ export const SingleRecordPickerMenuItems = ({
onCancel, onCancel,
onRecordSelected, onRecordSelected,
selectedRecord, selectedRecord,
hotkeyScope = SingleRecordPickerHotkeyScope.SingleRecordPicker, focusId,
}: SingleRecordPickerMenuItemsProps) => { }: SingleRecordPickerMenuItemsProps) => {
const selectNone = emptyLabel const selectNone = emptyLabel
? { ? {
@@ -77,15 +77,16 @@ export const SingleRecordPickerMenuItems = ({
'select-none', 'select-none',
); );
useScopedHotkeys( useHotkeysOnFocusedElement({
[Key.Escape], keys: Key.Escape,
() => { callback: () => {
resetSelectedItem(); resetSelectedItem();
onCancel?.(); onCancel?.();
}, },
hotkeyScope, focusId,
[onCancel, resetSelectedItem], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [onCancel, resetSelectedItem],
});
const selectableItemIds = recordsInDropdown.map((entity) => entity.id); const selectableItemIds = recordsInDropdown.map((entity) => entity.id);
const [selectedRecordId, setSelectedRecordId] = useRecoilComponentStateV2( const [selectedRecordId, setSelectedRecordId] = useRecoilComponentStateV2(
@@ -96,7 +97,8 @@ export const SingleRecordPickerMenuItems = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} hotkeyScope={DropdownHotkeyScope.Dropdown}
focusId={focusId}
> >
{loading ? ( {loading ? (
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />
@@ -26,6 +26,7 @@ export type SingleRecordPickerMenuItemsWithSearchProps = {
objectNameSingular: string; objectNameSingular: string;
recordPickerInstanceId?: string; recordPickerInstanceId?: string;
layoutDirection?: RecordPickerLayoutDirection; layoutDirection?: RecordPickerLayoutDirection;
focusId: string;
} & Pick< } & Pick<
SingleRecordPickerMenuItemsProps, SingleRecordPickerMenuItemsProps,
| 'EmptyIcon' | 'EmptyIcon'
@@ -44,6 +45,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
onRecordSelected, onRecordSelected,
objectNameSingular, objectNameSingular,
layoutDirection = 'search-bar-on-top', layoutDirection = 'search-bar-on-top',
focusId,
}: SingleRecordPickerMenuItemsWithSearchProps) => { }: SingleRecordPickerMenuItemsWithSearchProps) => {
const { handleSearchFilterChange } = useSingleRecordPickerSearch(); const { handleSearchFilterChange } = useSingleRecordPickerSearch();
@@ -96,9 +98,11 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</> </>
)} )}
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{searchHasNoResults && <RecordPickerNoRecordFoundMenuItem />} {searchHasNoResults && <RecordPickerNoRecordFoundMenuItem />}
<SingleRecordPickerMenuItems <SingleRecordPickerMenuItems
focusId={focusId}
recordsToSelect={records.recordsToSelect} recordsToSelect={records.recordsToSelect}
loading={records.loading} loading={records.loading}
selectedRecord={records.selectedRecords?.[0]} selectedRecord={records.selectedRecords?.[0]}
@@ -123,6 +127,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
<SingleRecordPickerMenuItems <SingleRecordPickerMenuItems
focusId={focusId}
recordsToSelect={records.recordsToSelect} recordsToSelect={records.recordsToSelect}
loading={records.loading} loading={records.loading}
selectedRecord={records.selectedRecords?.[0]} selectedRecord={records.selectedRecords?.[0]}
@@ -1,3 +0,0 @@
export enum SingleRecordPickerHotkeyScope {
SingleRecordPicker = 'single-record-picker',
}
@@ -285,7 +285,6 @@ export const RecordDetailRelationRecordsListItem = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownScopeId }}
/> />
</DropdownScope> </DropdownScope>
)} )}
@@ -216,10 +216,10 @@ export const RecordDetailRelationSectionDropdown = ({
accent="tertiary" accent="tertiary"
/> />
} }
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownComponents={ dropdownComponents={
isToOneObject ? ( isToOneObject ? (
<SingleRecordPicker <SingleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId} componentInstanceId={dropdownId}
EmptyIcon={IconForbid} EmptyIcon={IconForbid}
onRecordSelected={handleRelationPickerEntitySelected} onRecordSelected={handleRelationPickerEntitySelected}
@@ -235,6 +235,7 @@ export const RecordDetailRelationSectionDropdown = ({
/> />
) : ( ) : (
<MultipleRecordPicker <MultipleRecordPicker
focusId={dropdownId}
componentInstanceId={dropdownId} componentInstanceId={dropdownId}
onCreate={() => { onCreate={() => {
closeDropdown(); closeDropdown();
@@ -8,6 +8,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack';
export const useCloseRecordTableCellInGroup = () => { export const useCloseRecordTableCellInGroup = () => {
const { recordTableId } = useRecordTableContextOrThrow(); const { recordTableId } = useRecordTableContextOrThrow();
@@ -22,11 +23,17 @@ export const useCloseRecordTableCellInGroup = () => {
const closeCurrentTableCellInEditMode = const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId); useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack();
const closeTableCellInGroup = useRecoilCallback( const closeTableCellInGroup = useRecoilCallback(
() => () => { () => () => {
toggleClickOutside(true); toggleClickOutside(true);
setDragSelectionStartEnabled(true); setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode(); closeCurrentTableCellInEditMode();
// TODO: Remove this once we've fully migrated away from hotkey scopes
resetFocusStack();
setHotkeyScope(TableHotkeyScope.TableFocus, { setHotkeyScope(TableHotkeyScope.TableFocus, {
goto: true, goto: true,
keyboardShortcutMenu: true, keyboardShortcutMenu: true,
@@ -35,6 +42,7 @@ export const useCloseRecordTableCellInGroup = () => {
}, },
[ [
closeCurrentTableCellInEditMode, closeCurrentTableCellInEditMode,
resetFocusStack,
setDragSelectionStartEnabled, setDragSelectionStartEnabled,
setHotkeyScope, setHotkeyScope,
toggleClickOutside, toggleClickOutside,
@@ -6,6 +6,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useResetFocusStack } from '@/ui/utilities/focus/hooks/useResetFocusStack';
import { useCallback } from 'react'; import { useCallback } from 'react';
export const useCloseRecordTableCellNoGroup = () => { export const useCloseRecordTableCellNoGroup = () => {
@@ -22,10 +23,16 @@ export const useCloseRecordTableCellNoGroup = () => {
const closeCurrentTableCellInEditMode = const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId); useCloseCurrentTableCellInEditMode(recordTableId);
const { resetFocusStack } = useResetFocusStack();
const closeTableCellNoGroup = useCallback(() => { const closeTableCellNoGroup = useCallback(() => {
toggleClickOutside(true); toggleClickOutside(true);
setDragSelectionStartEnabled(true); setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode(); closeCurrentTableCellInEditMode();
// TODO: Remove this once we've fully migrated away from hotkey scopes
resetFocusStack();
setHotkeyScope(TableHotkeyScope.TableFocus, { setHotkeyScope(TableHotkeyScope.TableFocus, {
goto: true, goto: true,
keyboardShortcutMenu: true, keyboardShortcutMenu: true,
@@ -33,6 +40,7 @@ export const useCloseRecordTableCellNoGroup = () => {
}); });
}, [ }, [
closeCurrentTableCellInEditMode, closeCurrentTableCellInEditMode,
resetFocusStack,
setDragSelectionStartEnabled, setDragSelectionStartEnabled,
setHotkeyScope, setHotkeyScope,
toggleClickOutside, toggleClickOutside,
@@ -2,7 +2,6 @@ import { useRecoilCallback } from 'recoil';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState'; import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
@@ -58,9 +57,7 @@ export const useTriggerActionMenuDropdown = ({
closeCommandMenu(); closeCommandMenu();
openDropdown({ openDropdown();
scope: ActionMenuDropdownHotkeyScope.ActionMenuDropdown,
});
}, },
[ [
recordIndexActionMenuDropdownPositionState, recordIndexActionMenuDropdownPositionState,
@@ -76,7 +76,6 @@ export const RecordTableColumnFooterWithDropdown = ({
} }
dropdownOffset={{ x: -1 }} dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };
@@ -35,7 +35,6 @@ export const RecordTableColumnHeadWithDropdown = ({
dropdownComponents={<RecordTableColumnHeadDropdownMenu column={column} />} dropdownComponents={<RecordTableColumnHeadDropdownMenu column={column} />}
dropdownOffset={{ x: -1 }} dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }}
/> />
); );
}; };
@@ -50,9 +50,6 @@ const StyledDropdownContainer = styled.div`
cursor: pointer; cursor: pointer;
`; `;
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
'hidden-table-columns-dropdown-hotkey-scope-id';
export const RecordTableHeaderLastColumn = () => { export const RecordTableHeaderLastColumn = () => {
const theme = useTheme(); const theme = useTheme();
@@ -89,9 +86,6 @@ export const RecordTableHeaderLastColumn = () => {
} }
dropdownComponents={<RecordTableHeaderPlusButtonContent />} dropdownComponents={<RecordTableHeaderPlusButtonContent />}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
}}
/> />
</StyledDropdownContainer> </StyledDropdownContainer>
</StyledPlusIconHeaderCell> </StyledPlusIconHeaderCell>
@@ -3,19 +3,20 @@ import { Key } from 'ts-key-enum';
import { SelectableItem } from '@/object-record/select/types/SelectableItem'; import { SelectableItem } from '@/object-record/select/types/SelectableItem';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { Avatar } from 'twenty-ui/display'; import { Avatar } from 'twenty-ui/display';
import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation'; import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
export const MultipleSelectDropdown = ({ export const MultipleSelectDropdown = ({
selectableListId, selectableListId,
hotkeyScope, focusId,
itemsToSelect, itemsToSelect,
loadingItems, loadingItems,
filteredSelectedItems, filteredSelectedItems,
@@ -23,7 +24,7 @@ export const MultipleSelectDropdown = ({
searchFilter, searchFilter,
}: { }: {
selectableListId: string; selectableListId: string;
hotkeyScope: string; focusId: string;
itemsToSelect: SelectableItem[]; itemsToSelect: SelectableItem[];
filteredSelectedItems: SelectableItem[]; filteredSelectedItems: SelectableItem[];
selectedItems: SelectableItem[]; selectedItems: SelectableItem[];
@@ -61,15 +62,16 @@ export const MultipleSelectDropdown = ({
...(itemsToSelect ?? []), ...(itemsToSelect ?? []),
]; ];
useScopedHotkeys( useHotkeysOnFocusedElement({
[Key.Escape], keys: [Key.Escape],
() => { callback: () => {
closeDropdown(); closeDropdown();
resetSelectedItem(); resetSelectedItem();
}, },
hotkeyScope, focusId,
[closeDropdown, resetSelectedItem], scope: DropdownHotkeyScope.Dropdown,
); dependencies: [closeDropdown, resetSelectedItem],
});
const showNoResult = const showNoResult =
itemsToSelect?.length === 0 && itemsToSelect?.length === 0 &&
@@ -83,7 +85,8 @@ export const MultipleSelectDropdown = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListId} selectableListInstanceId={selectableListId}
selectableItemIdArray={selectableItemIds} selectableItemIdArray={selectableItemIds}
hotkeyScope={hotkeyScope} focusId={focusId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{itemsInDropdown?.map((item) => { {itemsInDropdown?.map((item) => {
@@ -51,7 +51,6 @@ export const SettingsAccountsRowDropdownMenu = ({
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
@@ -2,7 +2,6 @@ import { Select } from '@/ui/input/components/Select';
import { SelectControl } from '@/ui/input/components/SelectControl'; import { SelectControl } from '@/ui/input/components/SelectControl';
import { TextArea } from '@/ui/input/components/TextArea'; import { TextArea } from '@/ui/input/components/TextArea';
import { TextInputV2 } from '@/ui/input/components/TextInputV2'; import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { SelectHotkeyScope } from '@/ui/input/types/SelectHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -96,7 +95,6 @@ export const ConfigVariableDatabaseInput = ({
{options && Array.isArray(options) ? ( {options && Array.isArray(options) ? (
<Dropdown <Dropdown
dropdownId="config-variable-array-dropdown" dropdownId="config-variable-array-dropdown"
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownOffset={{ dropdownOffset={{
y: 8, y: 8,
@@ -46,7 +46,6 @@ export const ConfigVariableFilterDropdown = ({
/> />
} }
dropdownId="env-var-options-dropdown" dropdownId="env-var-options-dropdown"
dropdownHotkeyScope={{ scope: 'env-var-options' }}
dropdownOffset={{ x: 0, y: 10 }} dropdownOffset={{ x: 0, y: 10 }}
dropdownComponents={ dropdownComponents={
<ConfigVariableOptionsDropdownContent <ConfigVariableOptionsDropdownContent
@@ -130,7 +130,6 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
</StyledContainer> </StyledContainer>
); );
@@ -117,7 +117,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown <Dropdown
dropdownId={SELECT_COLOR_DROPDOWN_ID} dropdownId={SELECT_COLOR_DROPDOWN_ID}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: SELECT_COLOR_DROPDOWN_ID }}
clickableComponent={<StyledColorSample colorName={option.color} />} clickableComponent={<StyledColorSample colorName={option.color} />}
dropdownComponents={ dropdownComponents={
<DropdownContent> <DropdownContent>
@@ -160,7 +159,6 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
<Dropdown <Dropdown
dropdownId={SELECT_ACTIONS_DROPDOWN_ID} dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: SELECT_ACTIONS_DROPDOWN_ID }}
clickableComponent={ clickableComponent={
<StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} /> <StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} />
} }
@@ -82,7 +82,6 @@ export const SettingsObjectFieldActiveActionDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };
@@ -86,9 +86,6 @@ export const SettingsObjectFieldInactiveActionDropdown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{
scope: dropdownId,
}}
/> />
); );
}; };
@@ -63,7 +63,6 @@ export const SettingsObjectInactiveMenuDropDown = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };
@@ -54,7 +54,6 @@ export const SettingsIntegrationDatabaseConnectionSummaryCard = ({
/> />
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
@@ -238,7 +238,6 @@ export const SettingsRoleAssignment = ({
<StyledAssignToMemberContainer> <StyledAssignToMemberContainer>
<Dropdown <Dropdown
dropdownId="role-member-select" dropdownId="role-member-select"
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
dropdownOffset={{ x: 0, y: 4 }} dropdownOffset={{ x: 0, y: 4 }}
clickableComponent={ clickableComponent={
<> <>
@@ -69,7 +69,6 @@ export const SettingsSecuritySSORowDropdownMenu = ({
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
@@ -58,7 +58,6 @@ export const SettingsSecurityApprovedAccessDomainRowDropdownMenu = ({
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="right-start" dropdownPlacement="right-start"
dropdownHotkeyScope={{ scope: dropdownId }}
clickableComponent={ clickableComponent={
<LightIconButton Icon={IconDotsVertical} accent="tertiary" /> <LightIconButton Icon={IconDotsVertical} accent="tertiary" />
} }
@@ -131,7 +131,6 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropDownId }}
/> />
</TableCell> </TableCell>
</StyledTableRow> </StyledTableRow>
@@ -123,9 +123,6 @@ export const MatchColumnToFieldSelect = ({
return ( return (
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{
scope: dropdownId,
}}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
clickableComponent={ clickableComponent={
<StyledMenuItem <StyledMenuItem
@@ -52,7 +52,6 @@ export const SubMatchingSelectRowRightDropdown = <T extends string>({
return ( return (
<StyledDropdownContainer> <StyledDropdownContainer>
<Dropdown <Dropdown
dropdownHotkeyScope={{ scope: dropdownId }}
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownPlacement="bottom-start" dropdownPlacement="bottom-start"
clickableComponent={ clickableComponent={
@@ -44,7 +44,6 @@ export const SupportDropdown = () => {
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: dropdownId }}
/> />
); );
}; };
@@ -2,15 +2,17 @@ import { useRef, useState } from 'react';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata'; 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 { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; 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 { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; 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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@@ -21,7 +23,7 @@ import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmp
type MultiSelectInputProps = { type MultiSelectInputProps = {
selectableListComponentInstanceId: string; selectableListComponentInstanceId: string;
values: FieldMultiSelectValue; values: FieldMultiSelectValue;
hotkeyScope: string; focusId: string;
onCancel?: () => void; onCancel?: () => void;
options: SelectOption[]; options: SelectOption[];
onOptionSelected: (value: FieldMultiSelectValue) => void; onOptionSelected: (value: FieldMultiSelectValue) => void;
@@ -32,7 +34,7 @@ export const MultiSelectInput = ({
selectableListComponentInstanceId, selectableListComponentInstanceId,
values, values,
options, options,
hotkeyScope, focusId,
onCancel, onCancel,
onOptionSelected, onOptionSelected,
dropdownWidth, dropdownWidth,
@@ -69,15 +71,16 @@ export const MultiSelectInput = ({
} }
}; };
useScopedHotkeys( useHotkeysOnFocusedElement({
Key.Escape, keys: Key.Escape,
() => { callback: () => {
onCancel?.(); onCancel?.();
resetSelectedItem(); resetSelectedItem();
}, },
hotkeyScope, focusId,
[onCancel, resetSelectedItem], scope: DEFAULT_CELL_SCOPE.scope,
); dependencies: [onCancel, resetSelectedItem],
});
useListenClickOutside({ useListenClickOutside({
refs: [containerRef], refs: [containerRef],
@@ -102,7 +105,8 @@ export const MultiSelectInput = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={optionIds} selectableItemIdArray={optionIds}
hotkeyScope={hotkeyScope} focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
> >
<DropdownContent <DropdownContent
ref={containerRef} ref={containerRef}
@@ -122,17 +126,25 @@ export const MultiSelectInput = ({
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{filteredOptionsInDropDown.map((option) => { {filteredOptionsInDropDown.map((option) => {
return ( return (
<MenuItemMultiSelectTag <SelectableListItem
key={option.value} key={option.value}
selected={values?.includes(option.value) || false} itemId={option.value}
text={option.label} onEnter={() => {
color={option.color ?? 'transparent'} onOptionSelected(formatNewSelectedOptions(option.value));
Icon={option.Icon ?? undefined} }}
onClick={() => >
onOptionSelected(formatNewSelectedOptions(option.value)) <MenuItemMultiSelectTag
} key={option.value}
isKeySelected={selectedItemId === 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> </DropdownMenuItemsContainer>
@@ -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 { SelectInput as SelectBaseInput } from '@/ui/input/components/SelectInput';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
@@ -5,7 +6,7 @@ import { SelectOption } from 'twenty-ui/input';
type SelectInputProps = { type SelectInputProps = {
selectableListComponentInstanceId: string; selectableListComponentInstanceId: string;
selectableItemIdArray: string[]; selectableItemIdArray: string[];
hotkeyScope: string; focusId: string;
onEnter: (itemId: string) => void; onEnter: (itemId: string) => void;
onOptionSelected: (selectedOption: SelectOption) => void; onOptionSelected: (selectedOption: SelectOption) => void;
options: SelectOption[]; options: SelectOption[];
@@ -19,7 +20,7 @@ type SelectInputProps = {
export const SelectInput = ({ export const SelectInput = ({
selectableListComponentInstanceId, selectableListComponentInstanceId,
selectableItemIdArray, selectableItemIdArray,
hotkeyScope, focusId,
onOptionSelected, onOptionSelected,
options, options,
onCancel, onCancel,
@@ -32,7 +33,8 @@ export const SelectInput = ({
<SelectableList <SelectableList
selectableListInstanceId={selectableListComponentInstanceId} selectableListInstanceId={selectableListComponentInstanceId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={hotkeyScope} focusId={focusId}
hotkeyScope={DEFAULT_CELL_SCOPE.scope}
> >
<SelectBaseInput <SelectBaseInput
onOptionSelected={onOptionSelected} onOptionSelected={onOptionSelected}
@@ -42,7 +44,7 @@ export const SelectInput = ({
onFilterChange={onFilterChange} onFilterChange={onFilterChange}
onClear={onClear} onClear={onClear}
clearLabel={clearLabel} clearLabel={clearLabel}
hotkeyScope={hotkeyScope} focusId={focusId}
/> />
</SelectableList> </SelectableList>
); );
@@ -12,6 +12,7 @@ import { arrayToChunks } from '~/utils/array/arrayToChunks';
import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth'; import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; 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 { useSelectableListListenToEnterHotkeyOnItem } from '@/ui/layout/selectable-list/hooks/useSelectableListListenToEnterHotkeyOnItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@@ -71,9 +72,10 @@ const IconPickerIcon = ({
); );
useSelectableListListenToEnterHotkeyOnItem({ useSelectableListListenToEnterHotkeyOnItem({
hotkeyScope: IconPickerHotkeyScope.IconPicker, focusId: iconKey,
itemId: iconKey, itemId: iconKey,
onEnter: onClick, onEnter: onClick,
hotkeyScope: DropdownHotkeyScope.Dropdown,
}); });
return ( return (
@@ -184,7 +186,6 @@ export const IconPicker = ({
<div className={className}> <div className={className}>
<Dropdown <Dropdown
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownHotkeyScope={{ scope: IconPickerHotkeyScope.IconPicker }}
clickableComponent={ clickableComponent={
<IconButton <IconButton
ariaLabel={`Click to select icon ${ ariaLabel={`Click to select icon ${
@@ -203,7 +204,8 @@ export const IconPicker = ({
<SelectableList <SelectableList
selectableListInstanceId="icon-list" selectableListInstanceId="icon-list"
selectableItemIdMatrix={iconKeys2d} selectableItemIdMatrix={iconKeys2d}
hotkeyScope={IconPickerHotkeyScope.IconPicker} focusId={dropdownId}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
<DropdownMenuSearchInput <DropdownMenuSearchInput
placeholder={t`Search icon`} placeholder={t`Search icon`}
@@ -9,6 +9,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { SelectControl } from '@/ui/input/components/SelectControl'; import { SelectControl } from '@/ui/input/components/SelectControl';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; 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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset'; import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; 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 { IconComponent } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation'; import { MenuItem, MenuItemSelect } from 'twenty-ui/navigation';
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
export type SelectSizeVariant = 'small' | 'default'; export type SelectSizeVariant = 'small' | 'default';
@@ -166,9 +166,10 @@ export const Select = <Value extends SelectValue>({
{!!filteredOptions.length && ( {!!filteredOptions.length && (
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
<SelectableList <SelectableList
hotkeyScope={SelectHotkeyScope.Select}
selectableListInstanceId={dropdownId} selectableListInstanceId={dropdownId}
focusId={dropdownId}
selectableItemIdArray={selectableItemIdArray} selectableItemIdArray={selectableItemIdArray}
hotkeyScope={DropdownHotkeyScope.Dropdown}
> >
{filteredOptions.map((option) => ( {filteredOptions.map((option) => (
<SelectableListItem <SelectableListItem
@@ -211,7 +212,6 @@ export const Select = <Value extends SelectValue>({
)} )}
</DropdownContent> </DropdownContent>
} }
dropdownHotkeyScope={{ scope: SelectHotkeyScope.Select }}
/> />
)} )}
</StyledContainer> </StyledContainer>
@@ -2,10 +2,13 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; 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 { 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 { useEffect, useMemo, useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { TagColor } from 'twenty-ui/components'; import { TagColor } from 'twenty-ui/components';
import { SelectOption } from 'twenty-ui/input'; import { SelectOption } from 'twenty-ui/input';
@@ -19,7 +22,7 @@ interface SelectInputProps {
onFilterChange?: (filteredOptions: SelectOption[]) => void; onFilterChange?: (filteredOptions: SelectOption[]) => void;
onClear?: () => void; onClear?: () => void;
clearLabel?: string; clearLabel?: string;
hotkeyScope: string; focusId: string;
} }
export const SelectInput = ({ export const SelectInput = ({
@@ -30,10 +33,19 @@ export const SelectInput = ({
onCancel, onCancel,
defaultOption, defaultOption,
onFilterChange, onFilterChange,
hotkeyScope,
}: SelectInputProps) => { }: SelectInputProps) => {
const containerRef = useRef<HTMLDivElement>(null); 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 [searchFilter, setSearchFilter] = useState('');
const [selectedOption, setSelectedOption] = useState< const [selectedOption, setSelectedOption] = useState<
SelectOption | undefined SelectOption | undefined
@@ -61,6 +73,11 @@ export const SelectInput = ({
onOptionSelected(option); onOptionSelected(option);
}; };
const handleClearOption = () => {
setSelectedOption(undefined);
onClear?.();
};
useEffect(() => { useEffect(() => {
onFilterChange?.(optionsInDropDown); onFilterChange?.(optionsInDropDown);
}, [onFilterChange, optionsInDropDown]); }, [onFilterChange, optionsInDropDown]);
@@ -81,20 +98,6 @@ export const SelectInput = ({
listenerId: 'select-input', 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 ( return (
<DropdownContent ref={containerRef} selectDisabled> <DropdownContent ref={containerRef} selectDisabled>
<DropdownMenuSearchInput <DropdownMenuSearchInput
@@ -105,27 +108,37 @@ export const SelectInput = ({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight> <DropdownMenuItemsContainer hasMaxHeight>
{onClear && clearLabel && ( {onClear && clearLabel && (
<MenuItemSelectTag <SelectableListItem
key={`No ${clearLabel}`} itemId={`No ${clearLabel}`}
text={`No ${clearLabel}`} onEnter={handleClearOption}
color="transparent" >
variant={'outline'} <MenuItemSelectTag
onClick={() => { key={`No ${clearLabel}`}
setSelectedOption(undefined); text={`No ${clearLabel}`}
onClear(); color="transparent"
}} variant={'outline'}
/> onClick={handleClearOption}
isKeySelected={selectedItemId === `No ${clearLabel}`}
/>
</SelectableListItem>
)} )}
{optionsInDropDown.map((option) => { {optionsInDropDown.map((option) => {
return ( return (
<MenuItemSelectTag <SelectableListItem
key={option.value} key={option.value}
focused={selectedOption?.value === option.value} itemId={option.value}
text={option.label} onEnter={() => handleOptionChange(option)}
color={(option.color as TagColor) ?? 'transparent'} >
onClick={() => handleOptionChange(option)} <MenuItemSelectTag
LeftIcon={option.Icon} 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> </DropdownMenuItemsContainer>
@@ -70,7 +70,6 @@ export const CurrencyPickerDropdownButton = ({
return ( return (
<Dropdown <Dropdown
dropdownId="currency-picker-dropdown-id" dropdownId="currency-picker-dropdown-id"
dropdownHotkeyScope={{ scope: CurrencyPickerHotkeyScope.CurrencyPicker }}
clickableComponent={ clickableComponent={
<StyledDropdownButtonContainer> <StyledDropdownButtonContainer>
<StyledIconContainer> <StyledIconContainer>
@@ -6,8 +6,6 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope';
import { PhoneCountryPickerDropdownSelect } from './PhoneCountryPickerDropdownSelect'; import { PhoneCountryPickerDropdownSelect } from './PhoneCountryPickerDropdownSelect';
import 'react-phone-number-input/style.css'; import 'react-phone-number-input/style.css';
@@ -98,7 +96,6 @@ export const PhoneCountryPickerDropdownButton = ({
return ( return (
<Dropdown <Dropdown
dropdownId="country-picker-dropdown-id" dropdownId="country-picker-dropdown-id"
dropdownHotkeyScope={{ scope: CountryPickerHotkeyScope.CountryPicker }}
clickableComponent={ clickableComponent={
<StyledDropdownButtonContainer isUnfolded={isDropdownOpen}> <StyledDropdownButtonContainer isUnfolded={isDropdownOpen}>
<StyledIconContainer> <StyledIconContainer>

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