From 61290528504ba69c9223e33791892268454a37c8 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 7 Jan 2025 17:06:06 +0100 Subject: [PATCH] Fixed single record select hotkeys (#9433) There is a problem of hotkey scope not being passed to the relation picker used for single record select fields. Fixed it where we open a single record select. --- .../ObjectMetadataItemsProvider.tsx | 3 +- .../RecordBoardColumnNewOpportunity.tsx | 4 +- .../hooks/useAddNewCard.ts | 2 +- .../RecordDetailRelationSection.tsx | 3 +- .../components/RelationPicker.tsx | 3 +- ...ingsObjectInactiveMenuDropDown.stories.tsx | 4 +- .../InternalDatePicker.stories.tsx | 16 +++--- .../layout/dropdown/components/Dropdown.tsx | 27 +++++++--- .../ui/layout/dropdown/hooks/useDropdown.ts | 50 +++++++++++++------ .../decorators/RecordPickerDecorator.tsx | 3 +- 10 files changed, 77 insertions(+), 38 deletions(-) diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx index 0a3f21e1e..d516f3ecf 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx @@ -5,6 +5,7 @@ import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/Obje import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader'; export const ObjectMetadataItemsProvider = ({ @@ -20,7 +21,7 @@ export const ObjectMetadataItemsProvider = ({ {shouldDisplayChildren ? ( {children} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx index bfb1866f5..0095e1ded 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx @@ -6,12 +6,14 @@ import { viewableRecordIdState } from '@/object-record/record-right-drawer/state import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { SingleRecordSelect } from '@/object-record/relation-picker/components/SingleRecordSelect'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { v4 } from 'uuid'; import { isDefined } from '~/utils/isDefined'; + export const RecordBoardColumnNewOpportunity = ({ columnId, position, @@ -62,7 +64,7 @@ export const RecordBoardColumnNewOpportunity = ({ {newRecord.isCreating && newRecord.position === position && ( handleCreateSuccess(position, columnId, false)} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts index 7fce3b598..e54da6b03 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts @@ -21,7 +21,7 @@ export const useAddNewCard = () => { const { createOneRecord, selectFieldMetadataItem, objectMetadataItem } = useContext(RecordBoardContext); const { resetSearchFilter } = useRecordSelectSearch({ - recordPickerInstanceId: 'record-picker', + recordPickerInstanceId: RelationPickerHotkeyScope.RelationPicker, }); const { diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx index 47765952c..e3a315a43 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx @@ -78,7 +78,7 @@ export const RecordDetailRelationSection = ({ const relationRecordIds = relationRecords.map(({ id }) => id); - const dropdownId = `record-field-card-relation-picker-${fieldDefinition.label}-${recordId}`; + const dropdownId = `record-field-card-relation-picker-${fieldDefinition.fieldMetadataId}-${recordId}`; const { closeDropdown, isDropdownOpen, dropdownPlacement } = useDropdown(dropdownId); @@ -138,6 +138,7 @@ export const RecordDetailRelationSection = ({ }, view: indexView?.id, }; + const filterLinkHref = `/objects/${ relationObjectMetadataItem.namePlural }?${qs.stringify(filterQueryParams)}`; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx index cdeb25c68..3de101ad1 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx @@ -9,6 +9,7 @@ import { SearchPickerInitialValueEffect } from '@/object-record/relation-picker/ import { SingleRecordSelect } from '@/object-record/relation-picker/components/SingleRecordSelect'; import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer'; import { RecordForSelect } from '@/object-record/relation-picker/types/RecordForSelect'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; export type RelationPickerProps = { selectedRecordId?: string; @@ -29,7 +30,7 @@ export const RelationPicker = ({ initialSearchFilter, fieldDefinition, }: RelationPickerProps) => { - const recordPickerInstanceId = 'relation-picker'; + const recordPickerInstanceId = RelationPickerHotkeyScope.RelationPicker; const handleRecordSelected = ( selectedRecord: RecordForSelect | null | undefined, diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/components/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/components/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx index 8401f576c..9ca74cfbd 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/components/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/components/__stories__/SettingsObjectInactiveMenuDropDown.stories.tsx @@ -54,7 +54,7 @@ export const WithActivate: Story = { await expect(handleActivateMockFunction).toHaveBeenCalledTimes(0); - const activateMenuItem = await canvas.getByText('Activate'); + const activateMenuItem = await canvas.findByText('Activate'); await userEvent.click(activateMenuItem); @@ -75,7 +75,7 @@ export const WithDelete: Story = { await expect(handleDeleteMockFunction).toHaveBeenCalledTimes(0); - const deleteMenuItem = await canvas.getByText('Delete'); + const deleteMenuItem = await canvas.findByText('Delete'); await userEvent.click(deleteMenuItem); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/InternalDatePicker.stories.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/InternalDatePicker.stories.tsx index 9d107b1eb..74d54be0f 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/InternalDatePicker.stories.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/__stories__/InternalDatePicker.stories.tsx @@ -51,13 +51,13 @@ export const WithOpenMonthSelect: Story = { 'October', 'November', 'December', - ].forEach((monthLabel) => - expect(canvas.getByText(monthLabel)).toBeInTheDocument(), + ].forEach(async (monthLabel) => + expect(await canvas.findByText(monthLabel)).toBeInTheDocument(), ); - await userEvent.click(canvas.getByText('February')); + await userEvent.click(await canvas.findByText('February')); - expect(canvas.getByText('February')).toBeInTheDocument(); + expect(await canvas.findByText('February')).toBeInTheDocument(); }, }; @@ -69,12 +69,12 @@ export const WithOpenYearSelect: Story = { await userEvent.click(yearSelect); - ['2024', '2025', '2026'].forEach((yearLabel) => - expect(canvas.getByText(yearLabel)).toBeInTheDocument(), + ['2024', '2025', '2026'].forEach(async (yearLabel) => + expect(await canvas.findByText(yearLabel)).toBeInTheDocument(), ); - await userEvent.click(canvas.getByText('2024')); + await userEvent.click(await canvas.findByText('2024')); - expect(canvas.getByText('2024')).toBeInTheDocument(); + expect(await canvas.findByText('2024')).toBeInTheDocument(); }, }; diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx index 0b0c1185a..f87cc2f2d 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx @@ -17,11 +17,14 @@ import { useDropdown } from '../hooks/useDropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownUnmountEffect } from '@/ui/layout/dropdown/components/DropdownUnmountEffect'; import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext'; +import { dropdownHotkeyComponentState } from '@/ui/layout/dropdown/states/dropdownHotkeyComponentState'; import { dropdownMaxHeightComponentStateV2 } from '@/ui/layout/dropdown/states/dropdownMaxHeightComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import styled from '@emotion/styled'; import { flushSync } from 'react-dom'; +import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-ui'; +import { sleep } from '~/utils/sleep'; import { DropdownOnToggleEffect } from './DropdownOnToggleEffect'; const StyledDropdownFallbackAnchor = styled.div` @@ -104,13 +107,25 @@ export const Dropdown = ({ strategy: dropdownStrategy, }); - const handleClickableComponentClick = (event: MouseEvent) => { - event.stopPropagation(); - event.preventDefault(); + const handleClickableComponentClick = useRecoilCallback( + ({ set }) => + async (event: MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); - toggleDropdown(); - onClickOutside?.(); - }; + // TODO: refactor this when we have finished dropdown refactor with state and V1 + V2 + set( + dropdownHotkeyComponentState({ scopeId: dropdownId }), + dropdownHotkeyScope, + ); + + await sleep(100); + + toggleDropdown(); + onClickOutside?.(); + }, + [dropdownId, dropdownHotkeyScope, onClickOutside, toggleDropdown], + ); return ( { const { scopeId, - dropdownHotkeyScopeState, dropdownWidthState, isDropdownOpenState, dropdownPlacementState, @@ -30,8 +31,6 @@ export const useDropdown = (dropdownId?: string) => { goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); - const [dropdownHotkeyScope] = useRecoilState(dropdownHotkeyScopeState); - const [dropdownWidth, setDropdownWidth] = useRecoilState(dropdownWidthState); const [dropdownPlacement, setDropdownPlacement] = useRecoilState( @@ -54,18 +53,37 @@ export const useDropdown = (dropdownId?: string) => { goBackToPreviousDropdownFocusId, ]); - const openDropdown = () => { - if (!isDropdownOpen) { - setIsDropdownOpen(true); - setActiveDropdownFocusIdAndMemorizePrevious(dropdownId ?? scopeId); - if (isDefined(dropdownHotkeyScope)) { - setHotkeyScopeAndMemorizePreviousScope( - dropdownHotkeyScope.scope, - dropdownHotkeyScope.customScopes, - ); - } - } - }; + const openDropdown = useRecoilCallback( + ({ snapshot }) => + () => { + if (!isDropdownOpen) { + setIsDropdownOpen(true); + setActiveDropdownFocusIdAndMemorizePrevious(dropdownId ?? scopeId); + + const dropdownHotkeyScope = getSnapshotValue( + snapshot, + dropdownHotkeyComponentState({ + scopeId: dropdownId ?? scopeId, + }), + ); + + if (isDefined(dropdownHotkeyScope)) { + setHotkeyScopeAndMemorizePreviousScope( + dropdownHotkeyScope.scope, + dropdownHotkeyScope.customScopes, + ); + } + } + }, + [ + dropdownId, + isDropdownOpen, + scopeId, + setHotkeyScopeAndMemorizePreviousScope, + setActiveDropdownFocusIdAndMemorizePrevious, + setIsDropdownOpen, + ], + ); const toggleDropdown = () => { if (isDropdownOpen) { diff --git a/packages/twenty-front/src/testing/decorators/RecordPickerDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordPickerDecorator.tsx index 46fd5bbc2..6bb82de28 100644 --- a/packages/twenty-front/src/testing/decorators/RecordPickerDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordPickerDecorator.tsx @@ -1,10 +1,11 @@ import { Decorator } from '@storybook/react'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; export const RecordPickerDecorator: Decorator = (Story) => (