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

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