Skip to content

Commit b781da9

Browse files
authored
Fixes table z-index, focus, active rows and cells, pixel perfect (#14519)
This PR fixes z-indices on all cases for table with and without groups. It also fixes 1px glitches that appeared previously already and also during this refactor, with active and focused rows. The focus on cells is no a portal similar to hovered portal, which works really easily thanks to the z-index management already done on hovered portal. The urgent issues here twentyhq/core-team-issues#1490 have been fixed.
1 parent 91b7ecc commit b781da9

29 files changed

+1015
-235
lines changed

packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupRows.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const RecordTableNoRecordGroupRows = () => {
1919
recordId={recordId}
2020
rowIndexForFocus={rowIndex}
2121
rowIndexForDrag={rowIndex}
22+
isFirstRowOfGroup={rowIndex === 0}
2223
/>
2324
);
2425
})}

packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRecordGroupRows.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export const RecordTableRecordGroupRows = () => {
5353
recordId={recordId}
5454
rowIndexForFocus={rowIndex}
5555
rowIndexForDrag={rowIndexInGroup}
56+
isFirstRowOfGroup={rowIndexInGroup === 0}
5657
/>
5758
);
5859
})}

packages/twenty-front/src/modules/object-record/record-table/constants/TableZIndex.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,37 @@ export const TABLE_Z_INDEX = {
77
},
88
headerColumnsSticky: 14,
99
headerColumnsNormal: 10,
10-
withGroupsCell0_0: {
11-
cell0_0HoveredWithoutScroll: 15,
12-
cell0_0Normal: 13,
13-
},
1410
withoutGroupsCell0_0: {
1511
cell0_0HoveredWithoutScroll: 15,
1612
cell0_0Normal: 12,
1713
},
1814
groupSection: {
19-
stickyCell: 12,
20-
normalCell: 10,
15+
stickyCell: 10,
16+
normalCell: 8,
2117
},
2218
withGroups: {
2319
noScrollAtAll: {
2420
hoverPortalCellOnFirstScrollableColumn: 17,
2521
hoverPortalCellOnNormalColumn: 17,
2622
hoverPortalCellOnLabelIdentifierColumn: 17,
27-
firstScrollableHeaderCell: 12,
23+
firstScrollableHeaderCell: 10,
2824
},
2925
scrolledBothVerticallyAndHorizontally: {
30-
hoverPortalCellOnNormalColumn: 2,
31-
hoverPortalCellOnFirstScrollableColumn: 11,
32-
hoverPortalCellOnLabelIdentifierColumn: 15,
33-
firstScrollableHeaderCell: 12,
26+
hoverPortalCellOnNormalColumn: 9,
27+
hoverPortalCellOnFirstScrollableColumn: 9,
28+
hoverPortalCellOnLabelIdentifierColumn: 9,
29+
firstScrollableHeaderCell: 10,
3430
},
3531
scrolledHorizontallyOnly: {
36-
hoverPortalCellOnLabelIdentifierColumn: 15,
37-
hoverPortalCellOnNormalColumn: 11,
38-
hoverPortalCellOnFirstScrollableColumn: 11,
32+
hoverPortalCellOnLabelIdentifierColumn: 9,
33+
hoverPortalCellOnNormalColumn: 9,
34+
hoverPortalCellOnFirstScrollableColumn: 9,
3935
firstScrollableHeaderCell: 10,
4036
},
4137
scrolledVerticallyOnly: {
4238
hoverPortalCellOnNormalColumn: 9,
43-
hoverPortalCellOnFirstScrollableColumn: 15,
44-
hoverPortalCellOnLabelIdentifierColumn: 15,
39+
hoverPortalCellOnFirstScrollableColumn: 13,
40+
hoverPortalCellOnLabelIdentifierColumn: 9,
4541
firstScrollableHeaderCell: 14,
4642
},
4743
},
@@ -82,4 +78,26 @@ export const TABLE_Z_INDEX = {
8278
stickyColumn: 20,
8379
},
8480
},
81+
activeRows: {
82+
firstRow: {
83+
sticky: {
84+
scrolledVertically: 10,
85+
noVerticalScroll: 15,
86+
},
87+
normal: {
88+
scrolledVertically: 8,
89+
noVerticalScroll: 11,
90+
},
91+
},
92+
afterFirstRow: {
93+
sticky: {
94+
scrolledVertically: 8,
95+
noVerticalScroll: 8,
96+
},
97+
normal: {
98+
scrolledVertically: 7,
99+
noVerticalScroll: 7,
100+
},
101+
},
102+
},
85103
};

packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const RecordTableBodyLoading = () => {
3838
isDragging={false}
3939
data-testid={`row-id-${rowIndex}`}
4040
data-selectable-id={`row-id-${rowIndex}`}
41+
isFirstRowOfGroup={rowIndex === 0}
4142
>
4243
<RecordTableCellGrip />
4344
<RecordTableCellCheckbox />

packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFirstRowFirstColumn.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
21
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
32
import { StyledCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCellStyleWrapper';
43
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
4+
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
55
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
66
import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName';
77
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
@@ -37,29 +37,26 @@ export const RecordTableCellFirstRowFirstColumn = ({
3737
recordTableHoverPositionComponentState,
3838
);
3939

40+
const focusPosition = useRecoilComponentValue(
41+
recordTableFocusPositionComponentState,
42+
);
43+
44+
const isFocusPortalOnThisCell =
45+
focusPosition.column === 0 && focusPosition.row === 0;
46+
4047
const isHoveredPortalOnThisCell =
4148
hoverPosition?.column === 0 && hoverPosition.row === 0;
4249

4350
const [isRecordTableScrolledVertically] = useRecoilComponentState(
4451
isRecordTableScrolledVerticallyComponentState,
4552
);
4653

47-
const hasRecordGroups = useRecoilComponentValue(
48-
hasRecordGroupsComponentSelector,
49-
);
50-
51-
const zIndexWithoutGroups =
52-
!isRecordTableScrolledVertically && isHoveredPortalOnThisCell
54+
const zIndex =
55+
!isRecordTableScrolledVertically &&
56+
(isHoveredPortalOnThisCell || isFocusPortalOnThisCell)
5357
? TABLE_Z_INDEX.withoutGroupsCell0_0.cell0_0HoveredWithoutScroll
5458
: TABLE_Z_INDEX.withoutGroupsCell0_0.cell0_0Normal;
5559

56-
const zIndexWithGroups =
57-
!isRecordTableScrolledVertically && isHoveredPortalOnThisCell
58-
? TABLE_Z_INDEX.withGroupsCell0_0.cell0_0HoveredWithoutScroll
59-
: TABLE_Z_INDEX.withGroupsCell0_0.cell0_0Normal;
60-
61-
const zIndex = hasRecordGroups ? zIndexWithGroups : zIndexWithoutGroups;
62-
6360
const tdBackgroundColor = isSelected
6461
? theme.accent.quaternary
6562
: theme.background.primary;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { RecordTableCellPortalWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper';
2+
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
3+
4+
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
5+
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
6+
import { RecordTableCellFocusedPortalContent } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFocusedPortalContent';
7+
import { RecordTableCellPortalRootContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalRootContainer';
8+
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
9+
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
10+
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
11+
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
12+
import { isDefined } from 'twenty-shared/utils';
13+
14+
export const RecordTableCellFocusedPortal = () => {
15+
const focusPosition = useRecoilComponentValue(
16+
recordTableFocusPositionComponentState,
17+
);
18+
19+
const isRecordTableScrolledVertically = useRecoilComponentValue(
20+
isRecordTableScrolledVerticallyComponentState,
21+
);
22+
23+
const isRecordTableScrolledHorizontally = useRecoilComponentValue(
24+
isRecordTableScrolledHorizontallyComponentState,
25+
);
26+
27+
const hasRecordGroups = useRecoilComponentValue(
28+
hasRecordGroupsComponentSelector,
29+
);
30+
31+
const hoverPosition = useRecoilComponentValue(
32+
recordTableHoverPositionComponentState,
33+
);
34+
35+
const isUnderHoveredPortal =
36+
isDefined(hoverPosition) &&
37+
isDefined(focusPosition) &&
38+
hoverPosition.column === focusPosition.column &&
39+
hoverPosition.row === focusPosition.row;
40+
41+
if (!isDefined(focusPosition) || isUnderHoveredPortal) {
42+
return null;
43+
}
44+
45+
const isOnFirstScrollableColumn = focusPosition.column === 1;
46+
const isOnLabelIdentifierStickyColumn = focusPosition.column === 0;
47+
48+
const zIndexForHoveredPortalOnFirstScrollableColumnWithoutGroups =
49+
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
50+
? TABLE_Z_INDEX.withoutGroups.scrolledBothVerticallyAndHorizontally
51+
.hoverPortalCellOnFirstScrollableColumn
52+
: isRecordTableScrolledHorizontally
53+
? TABLE_Z_INDEX.withoutGroups.scrolledHorizontallyOnly
54+
.hoverPortalCellOnFirstScrollableColumn
55+
: isRecordTableScrolledVertically
56+
? TABLE_Z_INDEX.withoutGroups.scrolledVerticallyOnly
57+
.hoverPortalCellOnFirstScrollableColumn
58+
: TABLE_Z_INDEX.withoutGroups.noScrollAtAll
59+
.hoverPortalCellOnFirstScrollableColumn;
60+
61+
const zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithoutGroups =
62+
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
63+
? TABLE_Z_INDEX.withoutGroups.scrolledBothVerticallyAndHorizontally
64+
.hoverPortalCellOnLabelIdentifierColumn
65+
: isRecordTableScrolledHorizontally
66+
? TABLE_Z_INDEX.withoutGroups.scrolledHorizontallyOnly
67+
.hoverPortalCellOnLabelIdentifierColumn
68+
: isRecordTableScrolledVertically
69+
? TABLE_Z_INDEX.withoutGroups.scrolledVerticallyOnly
70+
.hoverPortalCellOnLabelIdentifierColumn
71+
: TABLE_Z_INDEX.withoutGroups.noScrollAtAll
72+
.hoverPortalCellOnLabelIdentifierColumn;
73+
74+
const zIndexForHoveredPortalOnNormalColumnWithoutGroups =
75+
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
76+
? TABLE_Z_INDEX.withoutGroups.scrolledBothVerticallyAndHorizontally
77+
.hoverPortalCellOnNormalColumn
78+
: isRecordTableScrolledHorizontally
79+
? TABLE_Z_INDEX.withoutGroups.scrolledHorizontallyOnly
80+
.hoverPortalCellOnNormalColumn
81+
: isRecordTableScrolledVertically
82+
? TABLE_Z_INDEX.withoutGroups.scrolledVerticallyOnly
83+
.hoverPortalCellOnNormalColumn
84+
: TABLE_Z_INDEX.withoutGroups.noScrollAtAll
85+
.hoverPortalCellOnNormalColumn;
86+
87+
const zIndexForHoveredPortalWithoutGroups = isOnFirstScrollableColumn
88+
? zIndexForHoveredPortalOnFirstScrollableColumnWithoutGroups
89+
: isOnLabelIdentifierStickyColumn
90+
? zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithoutGroups
91+
: zIndexForHoveredPortalOnNormalColumnWithoutGroups;
92+
93+
const zIndexForHoveredPortalOnFirstScrollableColumnWithGroups =
94+
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
95+
? TABLE_Z_INDEX.withGroups.scrolledBothVerticallyAndHorizontally
96+
.hoverPortalCellOnFirstScrollableColumn
97+
: isRecordTableScrolledHorizontally
98+
? TABLE_Z_INDEX.withGroups.scrolledHorizontallyOnly
99+
.hoverPortalCellOnFirstScrollableColumn
100+
: isRecordTableScrolledVertically
101+
? TABLE_Z_INDEX.withGroups.scrolledVerticallyOnly
102+
.hoverPortalCellOnFirstScrollableColumn
103+
: TABLE_Z_INDEX.withGroups.noScrollAtAll
104+
.hoverPortalCellOnFirstScrollableColumn;
105+
106+
const zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithGroups =
107+
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
108+
? TABLE_Z_INDEX.withGroups.scrolledBothVerticallyAndHorizontally
109+
.hoverPortalCellOnLabelIdentifierColumn
110+
: isRecordTableScrolledHorizontally
111+
? TABLE_Z_INDEX.withGroups.scrolledHorizontallyOnly
112+
.hoverPortalCellOnLabelIdentifierColumn
113+
: isRecordTableScrolledVertically
114+
? TABLE_Z_INDEX.withGroups.scrolledVerticallyOnly
115+
.hoverPortalCellOnLabelIdentifierColumn
116+
: TABLE_Z_INDEX.withGroups.noScrollAtAll
117+
.hoverPortalCellOnLabelIdentifierColumn;
118+
119+
const zIndexForHoveredPortalOnNormalColumnWithGroups =
120+
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
121+
? TABLE_Z_INDEX.withGroups.scrolledBothVerticallyAndHorizontally
122+
.hoverPortalCellOnNormalColumn
123+
: isRecordTableScrolledHorizontally
124+
? TABLE_Z_INDEX.withGroups.scrolledHorizontallyOnly
125+
.hoverPortalCellOnNormalColumn
126+
: isRecordTableScrolledVertically
127+
? TABLE_Z_INDEX.withGroups.scrolledVerticallyOnly
128+
.hoverPortalCellOnNormalColumn
129+
: TABLE_Z_INDEX.withGroups.noScrollAtAll
130+
.hoverPortalCellOnNormalColumn;
131+
132+
const zIndexForHoveredPortalWithGroups = isOnFirstScrollableColumn
133+
? zIndexForHoveredPortalOnFirstScrollableColumnWithGroups
134+
: isOnLabelIdentifierStickyColumn
135+
? zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithGroups
136+
: zIndexForHoveredPortalOnNormalColumnWithGroups;
137+
138+
const zIndex = hasRecordGroups
139+
? zIndexForHoveredPortalWithGroups
140+
: zIndexForHoveredPortalWithoutGroups;
141+
142+
return (
143+
<RecordTableCellPortalWrapper position={focusPosition}>
144+
<RecordTableCellPortalRootContainer zIndex={zIndex}>
145+
<RecordTableCellFocusedPortalContent />
146+
</RecordTableCellPortalRootContainer>
147+
</RecordTableCellPortalWrapper>
148+
);
149+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay';
2+
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
3+
import { RecordTableCellDisplayMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode';
4+
5+
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
6+
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
7+
import styled from '@emotion/styled';
8+
import { BORDER_COMMON } from 'twenty-ui/theme';
9+
10+
const StyledRecordTableCellFocusPortalContent = styled.div<{
11+
isRowActive: boolean;
12+
}>`
13+
align-items: center;
14+
background: ${({ theme }) => theme.background.transparent.secondary};
15+
background-color: ${({ theme, isRowActive }) =>
16+
isRowActive ? theme.accent.quaternary : theme.background.primary};
17+
border-radius: ${BORDER_COMMON.radius.sm};
18+
box-sizing: border-box;
19+
display: flex;
20+
21+
height: 32px;
22+
23+
outline: ${({ theme }) => `1px solid ${theme.color.blue}`};
24+
25+
user-select: none;
26+
`;
27+
28+
export const RecordTableCellFocusedPortalContent = () => {
29+
const { rowIndex } = useRecordTableRowContextOrThrow();
30+
31+
const isRowActive = useRecoilComponentFamilyValue(
32+
isRecordTableRowActiveComponentFamilyState,
33+
rowIndex,
34+
);
35+
36+
return (
37+
<StyledRecordTableCellFocusPortalContent isRowActive={isRowActive}>
38+
<RecordTableCellDisplayMode>
39+
<FieldDisplay />
40+
</RecordTableCellDisplayMode>
41+
</StyledRecordTableCellFocusPortalContent>
42+
);
43+
};

0 commit comments

Comments
 (0)