From a6007d43761fc5df2a842fe37782c83f98fd1655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:11:31 +0100 Subject: [PATCH] Fix action menu dropdown (#8368) Fix action menu dropdown not closing when clicking outside the table or board and introduce helper functions to get the action menu component ids. --- .../components/RecordIndexActionMenuBar.tsx | 3 +- .../RecordIndexActionMenuDropdown.tsx | 9 ++-- .../RecordIndexActionMenuEffect.tsx | 6 ++- .../RecordIndexActionMenuBar.stories.tsx | 3 +- .../hooks/__tests__/useActionMenu.test.ts | 23 +++++---- .../action-menu/hooks/useActionMenu.ts | 18 ++++--- .../getActionBarIdFromActionMenuId.test.ts | 9 ++++ ...tionMenuDropdownIdFromActionMenuId.test.ts | 9 ++++ .../getActionMenuIdFromRecordIndexId.test.ts | 9 ++++ .../utils/getActionBarIdFromActionMenuId.ts | 3 ++ ...getActionMenuDropdownIdFromActionMenuId.ts | 5 ++ .../utils/getActionMenuIdFromRecordIndexId.ts | 3 ++ .../hooks/useRecordBoardSelection.ts | 16 ++++--- .../components/RecordBoardCard.tsx | 11 ++++- .../hooks/internal/useLeaveTableFocus.ts | 15 ++++-- .../internal/useResetTableRowSelection.ts | 31 ++++++++---- .../record-table/hooks/useRecordTable.ts | 7 ++- .../RecordTableBodyUnselectEffect.tsx | 2 +- .../hooks/useTriggerActionMenuDropdown.ts | 48 ++++++++++--------- .../layout/dropdown/components/Dropdown.tsx | 5 +- .../pages/object-record/RecordIndexPage.tsx | 5 +- 21 files changed, 164 insertions(+), 76 deletions(-) create mode 100644 packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionBarIdFromActionMenuId.test.ts create mode 100644 packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuDropdownIdFromActionMenuId.test.ts create mode 100644 packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuIdFromRecordIndexId.test.ts create mode 100644 packages/twenty-front/src/modules/action-menu/utils/getActionBarIdFromActionMenuId.ts create mode 100644 packages/twenty-front/src/modules/action-menu/utils/getActionMenuDropdownIdFromActionMenuId.ts create mode 100644 packages/twenty-front/src/modules/action-menu/utils/getActionMenuIdFromRecordIndexId.ts diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBar.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBar.tsx index 861d6bfa8d53..fcc93f5bc50e 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBar.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenuBar.tsx @@ -5,6 +5,7 @@ import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIn import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope'; +import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -39,7 +40,7 @@ export const RecordIndexActionMenuBar = () => { return ( { const actionMenuDropdownPosition = useRecoilValue( extractComponentState( recordIndexActionMenuDropdownPositionComponentState, - `action-menu-dropdown-${actionMenuId}`, + getActionMenuDropdownIdFromActionMenuId(actionMenuId), ), ); - if (actionMenuEntries.length === 0) { - return null; - } - //TODO: remove this const width = actionMenuEntries.some( (actionMenuEntry) => actionMenuEntry.label === 'Remove from favorites', @@ -68,7 +65,7 @@ export const RecordIndexActionMenuDropdown = () => { className="action-menu-dropdown" > { // previous hotkey scope, and we don't want that here. const setIsBottomBarOpened = useSetRecoilComponentStateV2( isBottomBarOpenedComponentState, - `action-bar-${actionMenuId}`, + getActionBarIdFromActionMenuId(actionMenuId), ); const isDropdownOpen = useRecoilValue( extractComponentState( isDropdownOpenComponentState, - `action-menu-dropdown-${actionMenuId}`, + getActionMenuDropdownIdFromActionMenuId(actionMenuId), ), ); const { isRightDrawerOpen } = useRightDrawer(); diff --git a/packages/twenty-front/src/modules/action-menu/components/__stories__/RecordIndexActionMenuBar.stories.tsx b/packages/twenty-front/src/modules/action-menu/components/__stories__/RecordIndexActionMenuBar.stories.tsx index 8aeae25fca66..b7752d42f6f6 100644 --- a/packages/twenty-front/src/modules/action-menu/components/__stories__/RecordIndexActionMenuBar.stories.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/__stories__/RecordIndexActionMenuBar.stories.tsx @@ -6,6 +6,7 @@ import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexAc import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; +import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; @@ -64,7 +65,7 @@ const meta: Meta = { set( isBottomBarOpenedComponentState.atomFamily({ - instanceId: 'action-bar-story-action-menu', + instanceId: getActionBarIdFromActionMenuId('story-action-menu'), }), true, ); diff --git a/packages/twenty-front/src/modules/action-menu/hooks/__tests__/useActionMenu.test.ts b/packages/twenty-front/src/modules/action-menu/hooks/__tests__/useActionMenu.test.ts index 0f37475adedc..aa00785961c0 100644 --- a/packages/twenty-front/src/modules/action-menu/hooks/__tests__/useActionMenu.test.ts +++ b/packages/twenty-front/src/modules/action-menu/hooks/__tests__/useActionMenu.test.ts @@ -1,3 +1,5 @@ +import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId'; +import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { renderHook } from '@testing-library/react'; import { act } from 'react'; import { useActionMenu } from '../useActionMenu'; @@ -23,6 +25,9 @@ jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({ describe('useActionMenu', () => { const actionMenuId = 'test-action-menu'; + const actionBarId = getActionBarIdFromActionMenuId(actionMenuId); + const actionMenuDropdownId = + getActionMenuDropdownIdFromActionMenuId(actionMenuId); it('should return the correct functions', () => { const { result } = renderHook(() => useActionMenu(actionMenuId)); @@ -40,10 +45,8 @@ describe('useActionMenu', () => { result.current.openActionMenuDropdown(); }); - expect(closeBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`); - expect(openDropdown).toHaveBeenCalledWith( - `action-menu-dropdown-${actionMenuId}`, - ); + expect(closeBottomBar).toHaveBeenCalledWith(actionBarId); + expect(openDropdown).toHaveBeenCalledWith(actionMenuDropdownId); }); it('should call the correct functions when opening action bar', () => { @@ -53,10 +56,8 @@ describe('useActionMenu', () => { result.current.openActionBar(); }); - expect(closeDropdown).toHaveBeenCalledWith( - `action-menu-dropdown-${actionMenuId}`, - ); - expect(openBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`); + expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId); + expect(openBottomBar).toHaveBeenCalledWith(actionBarId); }); it('should call the correct function when closing action menu dropdown', () => { @@ -66,9 +67,7 @@ describe('useActionMenu', () => { result.current.closeActionMenuDropdown(); }); - expect(closeDropdown).toHaveBeenCalledWith( - `action-menu-dropdown-${actionMenuId}`, - ); + expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId); }); it('should call the correct function when closing action bar', () => { @@ -78,6 +77,6 @@ describe('useActionMenu', () => { result.current.closeActionBar(); }); - expect(closeBottomBar).toHaveBeenCalledWith(`action-bar-${actionMenuId}`); + expect(closeBottomBar).toHaveBeenCalledWith(actionBarId); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenu.ts b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenu.ts index 881cadd694e1..109bd1ee5932 100644 --- a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenu.ts +++ b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenu.ts @@ -1,3 +1,5 @@ +import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId'; +import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { useBottomBar } from '@/ui/layout/bottom-bar/hooks/useBottomBar'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; @@ -5,22 +7,26 @@ export const useActionMenu = (actionMenuId: string) => { const { openDropdown, closeDropdown } = useDropdownV2(); const { openBottomBar, closeBottomBar } = useBottomBar(); + const actionBarId = getActionBarIdFromActionMenuId(actionMenuId); + const actionMenuDropdownId = + getActionMenuDropdownIdFromActionMenuId(actionMenuId); + const openActionMenuDropdown = () => { - closeBottomBar(`action-bar-${actionMenuId}`); - openDropdown(`action-menu-dropdown-${actionMenuId}`); + closeBottomBar(actionBarId); + openDropdown(actionMenuDropdownId); }; const openActionBar = () => { - closeDropdown(`action-menu-dropdown-${actionMenuId}`); - openBottomBar(`action-bar-${actionMenuId}`); + closeDropdown(actionMenuDropdownId); + openBottomBar(actionBarId); }; const closeActionMenuDropdown = () => { - closeDropdown(`action-menu-dropdown-${actionMenuId}`); + closeDropdown(actionMenuDropdownId); }; const closeActionBar = () => { - closeBottomBar(`action-bar-${actionMenuId}`); + closeBottomBar(actionBarId); }; return { diff --git a/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionBarIdFromActionMenuId.test.ts b/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionBarIdFromActionMenuId.test.ts new file mode 100644 index 000000000000..bf9990909926 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionBarIdFromActionMenuId.test.ts @@ -0,0 +1,9 @@ +import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId'; + +describe('getActionBarIdFromActionMenuId', () => { + it('should return the correct action bar id', () => { + expect(getActionBarIdFromActionMenuId('action-menu-id')).toBe( + 'action-bar-action-menu-id', + ); + }); +}); diff --git a/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuDropdownIdFromActionMenuId.test.ts b/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuDropdownIdFromActionMenuId.test.ts new file mode 100644 index 000000000000..55fdebed4812 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuDropdownIdFromActionMenuId.test.ts @@ -0,0 +1,9 @@ +import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; + +describe('getActionMenuDropdownIdFromActionMenuId', () => { + it('should return the correct action menu dropdown id', () => { + expect(getActionMenuDropdownIdFromActionMenuId('action-menu-id')).toBe( + 'action-menu-dropdown-action-menu-id', + ); + }); +}); diff --git a/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuIdFromRecordIndexId.test.ts b/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuIdFromRecordIndexId.test.ts new file mode 100644 index 000000000000..ff547aa6d6e6 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/utils/__tests__/getActionMenuIdFromRecordIndexId.test.ts @@ -0,0 +1,9 @@ +import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; + +describe('getActionMenuIdFromRecordIndexId', () => { + it('should return the correct action menu id', () => { + expect(getActionMenuIdFromRecordIndexId('record-index-id')).toBe( + 'action-menu-record-index-record-index-id', + ); + }); +}); diff --git a/packages/twenty-front/src/modules/action-menu/utils/getActionBarIdFromActionMenuId.ts b/packages/twenty-front/src/modules/action-menu/utils/getActionBarIdFromActionMenuId.ts new file mode 100644 index 000000000000..2005c48308b5 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/utils/getActionBarIdFromActionMenuId.ts @@ -0,0 +1,3 @@ +export const getActionBarIdFromActionMenuId = (actionMenuId: string) => { + return `action-bar-${actionMenuId}`; +}; diff --git a/packages/twenty-front/src/modules/action-menu/utils/getActionMenuDropdownIdFromActionMenuId.ts b/packages/twenty-front/src/modules/action-menu/utils/getActionMenuDropdownIdFromActionMenuId.ts new file mode 100644 index 000000000000..40b21dbe6c84 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/utils/getActionMenuDropdownIdFromActionMenuId.ts @@ -0,0 +1,5 @@ +export const getActionMenuDropdownIdFromActionMenuId = ( + actionMenuId: string, +) => { + return `action-menu-dropdown-${actionMenuId}`; +}; diff --git a/packages/twenty-front/src/modules/action-menu/utils/getActionMenuIdFromRecordIndexId.ts b/packages/twenty-front/src/modules/action-menu/utils/getActionMenuIdFromRecordIndexId.ts new file mode 100644 index 000000000000..883beab4fd9e --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/utils/getActionMenuIdFromRecordIndexId.ts @@ -0,0 +1,3 @@ +export const getActionMenuIdFromRecordIndexId = (recordIndexId: string) => { + return `action-menu-record-index-${recordIndexId}`; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts index 8b2d11837ae1..baba7e9f9a5a 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts @@ -1,5 +1,7 @@ import { useRecoilCallback } from 'recoil'; +import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; +import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; @@ -8,14 +10,16 @@ export const useRecordBoardSelection = (recordBoardId: string) => { const { selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } = useRecordBoardStates(recordBoardId); + const isActionMenuDropdownOpenState = extractComponentState( + isDropdownOpenComponentState, + getActionMenuDropdownIdFromActionMenuId( + getActionMenuIdFromRecordIndexId(recordBoardId), + ), + ); + const resetRecordSelection = useRecoilCallback( ({ snapshot, set }) => () => { - const isActionMenuDropdownOpenState = extractComponentState( - isDropdownOpenComponentState, - `action-menu-dropdown-${recordBoardId}`, - ); - set(isActionMenuDropdownOpenState, false); const recordIds = snapshot @@ -27,7 +31,7 @@ export const useRecordBoardSelection = (recordBoardId: string) => { } }, [ - recordBoardId, + isActionMenuDropdownOpenState, selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState, ], diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index 2f7898810c4f..66dbd0695553 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -1,5 +1,7 @@ import { useActionMenu } from '@/action-menu/hooks/useActionMenu'; import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState'; +import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; +import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; @@ -183,14 +185,19 @@ export const RecordBoardCard = ({ RecordBoardScopeInternalContext, ); + const actionMenuId = getActionMenuIdFromRecordIndexId(recordBoardId); + + const actionMenuDropdownId = + getActionMenuDropdownIdFromActionMenuId(actionMenuId); + const setActionMenuDropdownPosition = useSetRecoilState( extractComponentState( recordIndexActionMenuDropdownPositionComponentState, - `action-menu-dropdown-${recordBoardId}`, + actionMenuDropdownId, ), ); - const { openActionMenuDropdown } = useActionMenu(recordBoardId); + const { openActionMenuDropdown } = useActionMenu(actionMenuId); const handleActionMenuDropdown = (event: React.MouseEvent) => { event.preventDefault(); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts index 6fc94ed6f910..dad738671f4e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts @@ -3,19 +3,28 @@ import { useRecoilCallback } from 'recoil'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; +import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { isSoftFocusActiveComponentState } from '@/object-record/record-table/states/isSoftFocusActiveComponentState'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useDisableSoftFocus } from './useDisableSoftFocus'; export const useLeaveTableFocus = (recordTableId?: string) => { - const disableSoftFocus = useDisableSoftFocus(recordTableId); + const recordTableIdFromContext = useAvailableComponentInstanceIdOrThrow( + RecordTableComponentInstanceContext, + recordTableId, + ); + + const disableSoftFocus = useDisableSoftFocus(recordTableIdFromContext); const isSoftFocusActiveState = useRecoilComponentCallbackStateV2( isSoftFocusActiveComponentState, - recordTableId, + recordTableIdFromContext, ); - const resetTableRowSelection = useResetTableRowSelection(recordTableId); + const resetTableRowSelection = useResetTableRowSelection( + recordTableIdFromContext, + ); return useRecoilCallback( ({ snapshot }) => diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts index 3dc9e1a1b3a6..8780f623abb3 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useResetTableRowSelection.ts @@ -1,25 +1,43 @@ import { useRecoilCallback } from 'recoil'; +import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; +import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; +import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; export const useResetTableRowSelection = (recordTableId?: string) => { + const recordTableIdFromContext = useAvailableComponentInstanceIdOrThrow( + RecordTableComponentInstanceContext, + recordTableId, + ); + const tableRowIdsState = useRecoilComponentCallbackStateV2( tableRowIdsComponentState, - recordTableId, + recordTableIdFromContext, ); + const isRowSelectedFamilyState = useRecoilComponentCallbackStateV2( isRowSelectedComponentFamilyState, - recordTableId, + recordTableIdFromContext, ); + const hasUserSelectedAllRowsState = useRecoilComponentCallbackStateV2( hasUserSelectedAllRowsComponentState, - recordTableId, + recordTableIdFromContext, + ); + + const isActionMenuDropdownOpenState = extractComponentState( + isDropdownOpenComponentState, + getActionMenuDropdownIdFromActionMenuId( + getActionMenuIdFromRecordIndexId(recordTableIdFromContext), + ), ); return useRecoilCallback( @@ -33,17 +51,12 @@ export const useResetTableRowSelection = (recordTableId?: string) => { set(hasUserSelectedAllRowsState, false); - const isActionMenuDropdownOpenState = extractComponentState( - isDropdownOpenComponentState, - `action-menu-dropdown-${recordTableId}`, - ); - set(isActionMenuDropdownOpenState, false); }, [ tableRowIdsState, hasUserSelectedAllRowsState, - recordTableId, + isActionMenuDropdownOpenState, isRowSelectedFamilyState, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts index 9408ed52e4b3..65036a78e1c2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts @@ -16,6 +16,7 @@ import { ColumnDefinition } from '../types/ColumnDefinition'; import { TableHotkeyScope } from '../types/TableHotkeyScope'; import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState'; +import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState'; import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState'; @@ -27,6 +28,7 @@ import { tableFiltersComponentState } from '@/object-record/record-table/states/ import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState'; import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState'; import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useDisableSoftFocus } from './internal/useDisableSoftFocus'; @@ -42,7 +44,10 @@ type useRecordTableProps = { }; export const useRecordTable = (props?: useRecordTableProps) => { - const recordTableId = props?.recordTableId; + const recordTableId = useAvailableComponentInstanceIdOrThrow( + RecordTableComponentInstanceContext, + props?.recordTableId, + ); const availableTableColumnsState = useRecoilComponentCallbackStateV2( availableTableColumnsComponentState, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect.tsx index 674a37b0fdd7..e8fab2be7714 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect.tsx @@ -16,7 +16,7 @@ export const RecordTableBodyUnselectEffect = ({ tableBodyRef, recordTableId, }: RecordTableBodyUnselectEffectProps) => { - const leaveTableFocus = useLeaveTableFocus(); + const leaveTableFocus = useLeaveTableFocus(recordTableId); const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({ recordTableId, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown.ts index 9a6019866720..5732978ac010 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown.ts @@ -2,6 +2,8 @@ import { useRecoilCallback } from 'recoil'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState'; +import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId'; +import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; @@ -24,21 +26,29 @@ export const useTriggerActionMenuDropdown = ({ recordTableId, ); + const recordIndexActionMenuDropdownPositionState = extractComponentState( + recordIndexActionMenuDropdownPositionComponentState, + getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId), + ); + + const isActionMenuDropdownOpenState = extractComponentState( + isDropdownOpenComponentState, + getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId), + ); + + const isActionBarOpenState = isBottomBarOpenedComponentState.atomFamily({ + instanceId: getActionBarIdFromActionMenuId(actionMenuInstanceId), + }); + const triggerActionMenuDropdown = useRecoilCallback( ({ set, snapshot }) => (event: React.MouseEvent, recordId: string) => { event.preventDefault(); - set( - extractComponentState( - recordIndexActionMenuDropdownPositionComponentState, - `action-menu-dropdown-${actionMenuInstanceId}`, - ), - { - x: event.clientX, - y: event.clientY, - }, - ); + set(recordIndexActionMenuDropdownPositionState, { + x: event.clientX, + y: event.clientY, + }); const isRowSelected = getSnapshotValue( snapshot, @@ -49,21 +59,15 @@ export const useTriggerActionMenuDropdown = ({ set(isRowSelectedFamilyState(recordId), true); } - const isActionMenuDropdownOpenState = extractComponentState( - isDropdownOpenComponentState, - `action-menu-dropdown-${actionMenuInstanceId}`, - ); - - const isActionBarOpenState = isBottomBarOpenedComponentState.atomFamily( - { - instanceId: `action-bar-${actionMenuInstanceId}`, - }, - ); - set(isActionBarOpenState, false); set(isActionMenuDropdownOpenState, true); }, - [actionMenuInstanceId, isRowSelectedFamilyState], + [ + isActionBarOpenState, + isActionMenuDropdownOpenState, + isRowSelectedFamilyState, + recordIndexActionMenuDropdownPositionState, + ], ); return { triggerActionMenuDropdown }; 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 017da77f40cb..679d0fc27b35 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 @@ -15,13 +15,13 @@ import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { isDefined } from '~/utils/isDefined'; import { useDropdown } from '../hooks/useDropdown'; import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement'; +import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; import { DropdownMenu } from './DropdownMenu'; import { DropdownOnToggleEffect } from './DropdownOnToggleEffect'; @@ -112,8 +112,9 @@ export const Dropdown = ({ onClickOutside?.(); }; - useListenClickOutside({ + useListenClickOutsideV2({ refs: [refs.floating], + listenerId: dropdownId, callback: () => { onClickOutside?.(); diff --git a/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx b/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx index f5241933593f..01f3d3191646 100644 --- a/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordIndexPage.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled'; import { useParams } from 'react-router-dom'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; import { MainContextStoreComponentInstanceIdSetterEffect } from '@/context-store/components/MainContextStoreComponentInstanceIdSetterEffect'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; @@ -82,12 +83,12 @@ export const RecordIndexPage = () => {