From 13c1e63892559ff282bfec32c21600f583945eef Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Mon, 15 Sep 2025 17:28:01 +0200 Subject: [PATCH] Fix hovered portal on table with groups (#14512) This PR fixes https://github.com/twentyhq/twenty/issues/13022 It was due to a mouse leave event that worked for table without groups but not with table with groups, the fix is to handle this event higher up in the table. This PR also fixes a bug that happened when hiding a column, the page crashed because of an indexed array returning undefined that TS didn't catch, linked to https://github.com/twentyhq/core-team-issues/issues/1205. --- .../components/RecordTableContent.tsx | 19 +++++++++++++++++++ ...dTableNoRecordGroupBodyContextProvider.tsx | 6 ------ ...ordTableRecordGroupBodyContextProvider.tsx | 6 ------ .../perf/RecordTableCell.perf.stories.tsx | 1 - .../contexts/RecordTableBodyContext.ts | 2 -- .../components/RecordTableBodyDroppable.tsx | 19 ------------------- .../RecordTableCellBaseContainer.tsx | 7 ++----- .../RecordTableCellHoveredPortal.tsx | 3 ++- .../RecordTableCellPortalContexts.tsx | 12 ++++++++---- .../components/RecordTableCellsVisible.tsx | 9 ++++++++- .../decorators/RecordTableDecorator.tsx | 1 - 11 files changed, 39 insertions(+), 46 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx index 70f818a2ff..fa06d8682b 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx @@ -9,9 +9,13 @@ import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/recor import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody'; import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; +import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; +import { isSomeCellInEditModeComponentSelector } from '@/object-record/record-table/states/selectors/isSomeCellInEditModeComponentSelector'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass'; import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import styled from '@emotion/styled'; import { useRef, useState } from 'react'; import { useRecoilCallback } from 'recoil'; @@ -68,6 +72,20 @@ export const RecordTableContent = ({ const { lastColumnWidth } = useRecordTableLastColumnWidthToFill(); + const setRecordTableHoverPosition = useSetRecoilComponentState( + recordTableHoverPositionComponentState, + ); + + const isSomeCellInEditMode = useRecoilComponentValue( + isSomeCellInEditModeComponentSelector, + ); + + const handleMouseLeave = () => { + if (!isSomeCellInEditMode) { + setRecordTableHoverPosition(null); + } + }; + return ( {hasRecordGroups ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx index 5b2d4c384f..de087f0177 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx @@ -1,6 +1,5 @@ import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter'; import { useRecordTableMoveFocusedCell } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedCell'; import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup'; import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell'; @@ -57,10 +56,6 @@ export const RecordTableNoRecordGroupBodyContextProvider = ({ triggerActionMenuDropdown(event, recordId); }; - const { handleContainerMouseEnter } = useHandleContainerMouseEnter({ - recordTableId, - }); - return ( {children} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx index 077179801a..83c0c8587e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider.tsx @@ -1,6 +1,5 @@ import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter'; import { useRecordTableMoveFocusedCell } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedCell'; import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup'; import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell'; @@ -58,10 +57,6 @@ export const RecordTableRecordGroupBodyContextProvider = ({ triggerActionMenuDropdown(event, recordId); }; - const { handleContainerMouseEnter } = useHandleContainerMouseEnter({ - recordTableId, - }); - return ( {children} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx index f1cd9cd865..ed80700117 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx @@ -174,7 +174,6 @@ const meta: Meta = { onCloseTableCell: () => {}, onMoveHoverToCurrentCell: () => {}, onActionMenuDropdownOpened: () => {}, - onCellMouseEnter: () => {}, }} > void; - onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void; }; export const [ 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 ab9ea02a04..e9c7a9b526 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,9 +1,5 @@ import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody'; import { RecordTableBodyDroppableContextProvider } from '@/object-record/record-table/record-table-body/contexts/RecordTableBodyDroppableContext'; -import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; -import { isSomeCellInEditModeComponentSelector } from '@/object-record/record-table/states/selectors/isSomeCellInEditModeComponentSelector'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { Droppable } from '@hello-pangea/dnd'; import { type ReactNode, useState } from 'react'; import { v4 } from 'uuid'; @@ -21,20 +17,6 @@ export const RecordTableBodyDroppable = ({ }: RecordTableBodyDroppableProps) => { const [v4Persistable] = useState(v4()); - const setRecordTableHoverPosition = useSetRecoilComponentState( - recordTableHoverPositionComponentState, - ); - - const isSomeCellInEditMode = useRecoilComponentValue( - isSomeCellInEditModeComponentSelector, - ); - - const handleMouseLeave = () => { - if (!isSomeCellInEditMode) { - setRecordTableHoverPosition(null); - } - }; - return ( { setIsFocused(true); - onCellMouseEnter({ - cellPosition, - }); + onMoveHoverToCurrentCell(cellPosition); }; const handleContainerMouseLeave = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal.tsx index fda534369c..d51dfe060f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal.tsx @@ -8,6 +8,7 @@ import { RecordTableCellHoveredPortalContent } from '@/object-record/record-tabl import { RecordTableCellPortalRootContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalRootContainer'; import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; +import { isDefined } from 'twenty-shared/utils'; export const RecordTableCellHoveredPortal = () => { const hoverPosition = useRecoilComponentValue( @@ -26,7 +27,7 @@ export const RecordTableCellHoveredPortal = () => { hasRecordGroupsComponentSelector, ); - if (!hoverPosition) { + if (!isDefined(hoverPosition)) { return null; } diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalContexts.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalContexts.tsx index 81142766ca..dcc869fc64 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalContexts.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalContexts.tsx @@ -42,6 +42,12 @@ export const RecordTableCellPortalContexts = ({ return null; } + const recordField = visibleRecordFields[hoverPosition.column]; + + if (!isDefined(recordField)) { + return null; + } + return ( - + {children} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsVisible.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsVisible.tsx index f069875dab..86142d72c9 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsVisible.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsVisible.tsx @@ -6,6 +6,7 @@ import { RecordTableCellFirstRowFirstColumn } from '@/object-record/record-table import { RecordTableCellStyleWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellStyleWrapper'; import { RecordTableCellWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellWrapper'; import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName'; +import { isDefined } from 'twenty-shared/utils'; import { isNonEmptyArray } from '~/utils/isNonEmptyArray'; export const RecordTableCellsVisible = () => { @@ -23,10 +24,16 @@ export const RecordTableCellsVisible = () => { const isFirstRow = rowIndex === 0; + const firstRecordField = visibleRecordFields[0]; + + if (!isDefined(firstRecordField)) { + return null; + } + return ( <> {isFirstRow ? ( diff --git a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx index 8b0055cd60..e88a6e559e 100644 --- a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx @@ -169,7 +169,6 @@ const InternalTableContextProviders = ({ > {}, onCloseTableCell: () => {}, onOpenTableCell: () => {}, onActionMenuDropdownOpened: () => {},