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.
This commit is contained in:
Lucas Bordeau
2025-09-15 17:28:01 +02:00
committed by GitHub
parent 24b7075f60
commit 13c1e63892
11 changed files with 39 additions and 46 deletions
@@ -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 (
<StyledTableContainer ref={containerRef}>
<RecordTableStyleWrapper
@@ -76,6 +94,7 @@ export const RecordTableContent = ({
visibleRecordFields={visibleRecordFields}
lastColumnWidth={lastColumnWidth}
id={RECORD_TABLE_HTML_ID}
onMouseLeave={handleMouseLeave}
>
<RecordTableHeader />
{hasRecordGroups ? (
@@ -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 (
<RecordTableBodyContextProvider
value={{
@@ -69,7 +64,6 @@ export const RecordTableNoRecordGroupBodyContextProvider = ({
onCloseTableCell: handleCloseTableCell,
onMoveHoverToCurrentCell: handleMoveHoverToCurrentCell,
onActionMenuDropdownOpened: handleActionMenuDropdown,
onCellMouseEnter: handleContainerMouseEnter,
}}
>
{children}
@@ -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 (
<RecordTableBodyContextProvider
value={{
@@ -70,7 +65,6 @@ export const RecordTableRecordGroupBodyContextProvider = ({
onCloseTableCell: handlecloseTableCellInGroup,
onMoveHoverToCurrentCell: handleMoveHoverToCurrentCell,
onActionMenuDropdownOpened: handleActionMenuDropdown,
onCellMouseEnter: handleContainerMouseEnter,
}}
>
{children}
@@ -174,7 +174,6 @@ const meta: Meta = {
onCloseTableCell: () => {},
onMoveHoverToCurrentCell: () => {},
onActionMenuDropdownOpened: () => {},
onCellMouseEnter: () => {},
}}
>
<RecordTableRowContextProvider
@@ -1,6 +1,5 @@
import type React from 'react';
import { type HandleContainerMouseEnterArgs } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { type OpenTableCellArgs } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
import { type MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { type TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
@@ -16,7 +15,6 @@ export type RecordTableBodyContextProps = {
event: React.MouseEvent,
recordId: string,
) => void;
onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void;
};
export const [
@@ -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 (
<Droppable
droppableId={recordGroupId ?? v4Persistable}
@@ -46,7 +28,6 @@ export const RecordTableBodyDroppable = ({
ref={provided.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...provided.droppableProps}
onMouseLeave={handleMouseLeave}
>
<RecordTableBodyDroppableContextProvider
value={{ droppablePlaceholder: provided.placeholder }}
@@ -75,14 +75,11 @@ export const RecordTableCellBaseContainer = ({
fieldDefinition,
isLabelIdentifier,
);
const { onMoveHoverToCurrentCell, onCellMouseEnter } =
useRecordTableBodyContextOrThrow();
const { onMoveHoverToCurrentCell } = useRecordTableBodyContextOrThrow();
const handleContainerMouseMove = () => {
setIsFocused(true);
onCellMouseEnter({
cellPosition,
});
onMoveHoverToCurrentCell(cellPosition);
};
const handleContainerMouseLeave = () => {
@@ -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;
}
@@ -42,6 +42,12 @@ export const RecordTableCellPortalContexts = ({
return null;
}
const recordField = visibleRecordFields[hoverPosition.column];
if (!isDefined(recordField)) {
return null;
}
return (
<RecordTableRowContextProvider
value={{
@@ -59,13 +65,11 @@ export const RecordTableCellPortalContexts = ({
>
<RecordTableCellContext.Provider
value={{
recordField: visibleRecordFields[hoverPosition.column],
recordField,
cellPosition: hoverPosition,
}}
>
<RecordTableCellFieldContextWrapper
recordField={visibleRecordFields[hoverPosition.column]}
>
<RecordTableCellFieldContextWrapper recordField={recordField}>
{children}
</RecordTableCellFieldContextWrapper>
</RecordTableCellContext.Provider>
@@ -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 (
<>
<RecordTableCellWrapper
recordField={visibleRecordFields[0]}
recordField={firstRecordField}
recordFieldIndex={0}
>
{isFirstRow ? (
@@ -169,7 +169,6 @@ const InternalTableContextProviders = ({
>
<RecordTableBodyContextProvider
value={{
onCellMouseEnter: () => {},
onCloseTableCell: () => {},
onOpenTableCell: () => {},
onActionMenuDropdownOpened: () => {},