From 52078365662893cd1b4d3a408148fa24185ee296 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?=
<71827178+bosiraphael@users.noreply.github.com>
Date: Fri, 25 Apr 2025 16:50:48 +0200
Subject: [PATCH] Table hover and click outside fixes (#11737)
# Table hover and click outside fixes
This PR improves table interaction behavior by refining cell hover
states and click-outside handling in the record table component.
## Changes
### Click Outside Handling
- Removed conditional rendering of
`RecordTableBodyFocusClickOutsideEffect`
### Hover State Management
Implemented hover state cleanup in multiple components:
- Added `recordTableHoverPosition` state reset in `useLeaveTableFocus`
hook
- Integrated mouse leave handler in `RecordTableBodyDroppable` to clear
hover position
### Fixes double focus bug
- Reset the focus and the hover when the table data changes
## Videos
### Before
https://github.com/user-attachments/assets/f815b65c-c545-4841-a0d9-04c58771e69f
### After
https://github.com/user-attachments/assets/046cc7df-18b8-46ca-b2cc-bdfa3125311b
---
.../RecordTableBodyEffectsWrapper.tsx | 4 +---
.../hooks/internal/useLeaveTableFocus.ts | 9 +++++++++
.../hooks/internal/useSetRecordTableData.ts | 18 +++++++++++++++++-
.../components/RecordTableBodyDroppable.tsx | 7 +++++++
4 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx
index 6b616ea47..ce3715550 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffectsWrapper.tsx
@@ -33,9 +33,7 @@ export const RecordTableBodyEffectsWrapper = ({
)}
{isAtLeastOneRecordSelected && }
{isRecordTableFocusActive && }
- {isRecordTableFocusActive && (
-
- )}
+
>
);
};
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 61f627e0b..8f3ad47a8 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
@@ -1,7 +1,9 @@
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
+import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
export const useLeaveTableFocus = (recordTableId?: string) => {
const recordTableIdFromContext = useAvailableComponentInstanceIdOrThrow(
@@ -17,9 +19,16 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
recordTableIdFromContext,
);
+ const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
+ recordTableHoverPositionComponentState,
+ recordTableIdFromContext,
+ );
+
return () => {
resetTableRowSelection();
setIsFocusActiveForCurrentPosition(false);
+
+ setRecordTableHoverPosition(null);
};
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts
index 2e472f4f4..db22c3357 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts
@@ -3,13 +3,16 @@ import { useRecoilCallback } from 'recoil';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
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 { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
-import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { isDefined } from 'twenty-shared/utils';
+import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
type useSetRecordTableDataProps = {
recordTableId?: string;
@@ -44,6 +47,14 @@ export const useSetRecordTableData = ({
recordTableId,
);
+ const { setIsFocusActiveForCurrentPosition } =
+ useSetIsRecordTableFocusActive(recordTableId);
+
+ const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
+ recordTableHoverPositionComponentState,
+ recordTableId,
+ );
+
return useRecoilCallback(
({ set, snapshot }) =>
({
@@ -84,6 +95,9 @@ export const useSetRecordTableData = ({
const recordIds = records.map((record) => record.id);
if (!isDeeplyEqual(currentRowIds, recordIds)) {
+ setIsFocusActiveForCurrentPosition(false);
+ setRecordTableHoverPosition(null);
+
if (hasUserSelectedAllRows) {
for (const rowId of recordIds) {
set(isRowSelectedFamilyState(rowId), true);
@@ -106,6 +120,8 @@ export const useSetRecordTableData = ({
recordIndexRecordIdsByGroupFamilyState,
recordIndexAllRecordIdsSelector,
hasUserSelectedAllRowsState,
+ setIsFocusActiveForCurrentPosition,
+ setRecordTableHoverPosition,
onEntityCountChange,
isRowSelectedFamilyState,
],
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDroppable.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDroppable.tsx
index 8e731aaf5..a113a4c1f 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDroppable.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDroppable.tsx
@@ -1,4 +1,6 @@
import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody';
+import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { Droppable } from '@hello-pangea/dnd';
import { ReactNode, useState } from 'react';
import { v4 } from 'uuid';
@@ -17,6 +19,10 @@ export const RecordTableBodyDroppable = ({
const [v4Persistable] = useState(v4());
const recordTableBodyId = `record-table-body${recordGroupId ? '-' + recordGroupId : ''}`;
+ const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
+ recordTableHoverPositionComponentState,
+ );
+
return (
setRecordTableHoverPosition(null)}
>
{children}
{provided.placeholder}