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
This commit is contained in:
@ -33,9 +33,7 @@ export const RecordTableBodyEffectsWrapper = ({
|
|||||||
)}
|
)}
|
||||||
{isAtLeastOneRecordSelected && <RecordTableBodyEscapeHotkeyEffect />}
|
{isAtLeastOneRecordSelected && <RecordTableBodyEscapeHotkeyEffect />}
|
||||||
{isRecordTableFocusActive && <RecordTableBodyFocusKeyboardEffect />}
|
{isRecordTableFocusActive && <RecordTableBodyFocusKeyboardEffect />}
|
||||||
{isRecordTableFocusActive && (
|
<RecordTableBodyFocusClickOutsideEffect tableBodyRef={tableBodyRef} />
|
||||||
<RecordTableBodyFocusClickOutsideEffect tableBodyRef={tableBodyRef} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||||
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
|
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
|
||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
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 { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
|
||||||
export const useLeaveTableFocus = (recordTableId?: string) => {
|
export const useLeaveTableFocus = (recordTableId?: string) => {
|
||||||
const recordTableIdFromContext = useAvailableComponentInstanceIdOrThrow(
|
const recordTableIdFromContext = useAvailableComponentInstanceIdOrThrow(
|
||||||
@ -17,9 +19,16 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
|
|||||||
recordTableIdFromContext,
|
recordTableIdFromContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
|
||||||
|
recordTableHoverPositionComponentState,
|
||||||
|
recordTableIdFromContext,
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resetTableRowSelection();
|
resetTableRowSelection();
|
||||||
|
|
||||||
setIsFocusActiveForCurrentPosition(false);
|
setIsFocusActiveForCurrentPosition(false);
|
||||||
|
|
||||||
|
setRecordTableHoverPosition(null);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,13 +3,16 @@ import { useRecoilCallback } from 'recoil';
|
|||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
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 { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
||||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
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 { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
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 { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
type useSetRecordTableDataProps = {
|
type useSetRecordTableDataProps = {
|
||||||
recordTableId?: string;
|
recordTableId?: string;
|
||||||
@ -44,6 +47,14 @@ export const useSetRecordTableData = ({
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { setIsFocusActiveForCurrentPosition } =
|
||||||
|
useSetIsRecordTableFocusActive(recordTableId);
|
||||||
|
|
||||||
|
const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
|
||||||
|
recordTableHoverPositionComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
return useRecoilCallback(
|
return useRecoilCallback(
|
||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
<T extends ObjectRecord>({
|
<T extends ObjectRecord>({
|
||||||
@ -84,6 +95,9 @@ export const useSetRecordTableData = ({
|
|||||||
const recordIds = records.map((record) => record.id);
|
const recordIds = records.map((record) => record.id);
|
||||||
|
|
||||||
if (!isDeeplyEqual(currentRowIds, recordIds)) {
|
if (!isDeeplyEqual(currentRowIds, recordIds)) {
|
||||||
|
setIsFocusActiveForCurrentPosition(false);
|
||||||
|
setRecordTableHoverPosition(null);
|
||||||
|
|
||||||
if (hasUserSelectedAllRows) {
|
if (hasUserSelectedAllRows) {
|
||||||
for (const rowId of recordIds) {
|
for (const rowId of recordIds) {
|
||||||
set(isRowSelectedFamilyState(rowId), true);
|
set(isRowSelectedFamilyState(rowId), true);
|
||||||
@ -106,6 +120,8 @@ export const useSetRecordTableData = ({
|
|||||||
recordIndexRecordIdsByGroupFamilyState,
|
recordIndexRecordIdsByGroupFamilyState,
|
||||||
recordIndexAllRecordIdsSelector,
|
recordIndexAllRecordIdsSelector,
|
||||||
hasUserSelectedAllRowsState,
|
hasUserSelectedAllRowsState,
|
||||||
|
setIsFocusActiveForCurrentPosition,
|
||||||
|
setRecordTableHoverPosition,
|
||||||
onEntityCountChange,
|
onEntityCountChange,
|
||||||
isRowSelectedFamilyState,
|
isRowSelectedFamilyState,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody';
|
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 { Droppable } from '@hello-pangea/dnd';
|
||||||
import { ReactNode, useState } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
@ -17,6 +19,10 @@ export const RecordTableBodyDroppable = ({
|
|||||||
const [v4Persistable] = useState(v4());
|
const [v4Persistable] = useState(v4());
|
||||||
const recordTableBodyId = `record-table-body${recordGroupId ? '-' + recordGroupId : ''}`;
|
const recordTableBodyId = `record-table-body${recordGroupId ? '-' + recordGroupId : ''}`;
|
||||||
|
|
||||||
|
const setRecordTableHoverPosition = useSetRecoilComponentStateV2(
|
||||||
|
recordTableHoverPositionComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Droppable
|
<Droppable
|
||||||
droppableId={recordGroupId ?? v4Persistable}
|
droppableId={recordGroupId ?? v4Persistable}
|
||||||
@ -28,6 +34,7 @@ export const RecordTableBodyDroppable = ({
|
|||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...provided.droppableProps}
|
{...provided.droppableProps}
|
||||||
|
onMouseLeave={() => setRecordTableHoverPosition(null)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
|
|||||||
Reference in New Issue
Block a user