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:
@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user