fix(ui): layout-shift-on-tasks (#15775)

Fixes #12090 

### Summary
Fixes a UI layout shift issue in table rows in Tasks page, where bottom
borders appeared inconsistently across rows.
The problem occurred because `shouldDisplayBorderBottom` conditionally
applied the bottom border, which caused slight height differences
between rows and visual "jumping" when rendering focused styling.

### Changes Made
- Ensured consistent `border-bottom` rendering
- Removed conditional rendering prop to always display bottom border

### Steps to Reproduce (Before Fix)
1. Navigate to - /objects/tasks?viewId=xxxx
2. Interact with row elements
3. Notice a slight layout shift when first row is active/focused


### Before 

https://github.com/user-attachments/assets/ab95db5d-0922-4460-a086-a03f58375824

### After


https://github.com/user-attachments/assets/a6b4d50c-b6f0-456b-90d4-fc3f0cde42bc

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Aryan Bhati
2025-11-13 16:26:18 +05:30
committed by GitHub
parent ec7e49c8bb
commit b82d38bb13
@@ -8,8 +8,6 @@ import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useC
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnDragAndDropWidth';
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
@@ -22,10 +20,7 @@ import { RECORD_TABLE_COLUMN_MIN_WIDTH } from '@/object-record/record-table/cons
import { RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE } from '@/object-record/record-table/constants/RecordTableLabelIdentifierColumnWidthOnMobile';
import { useAggregateRecordsForRecordTableSection } from '@/object-record/record-table/record-table-section/hooks/useAggregateRecordsForRecordTableSection';
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState';
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilValue } from 'recoil';
import {
@@ -39,16 +34,13 @@ import { IconChevronDown } from 'twenty-ui/display';
import { AnimatedLightIconButton } from 'twenty-ui/input';
import { useIsMobile } from 'twenty-ui/utilities';
const StyledTrContainer = styled.div<{ shouldDisplayBorderBottom: boolean }>`
const StyledTrContainer = styled.div`
cursor: pointer;
display: flex;
flex-direction: row;
div:not(:first-of-type) {
border-bottom: ${({ theme, shouldDisplayBorderBottom }) =>
shouldDisplayBorderBottom
? `1px solid ${theme.border.color.light}`
: 'none'};
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
}
`;
@@ -103,19 +95,14 @@ const StyledFieldPlaceholderCell = styled.div<{ widthOfFields: number }>`
z-index: ${TABLE_Z_INDEX.groupSection.normalCell};
`;
const StyledRecordTableDragAndDropPlaceholderCell = styled.div<{
shouldDisplayBorderBottom: boolean;
}>`
const StyledRecordTableDragAndDropPlaceholderCell = styled.div`
height: ${RECORD_TABLE_ROW_HEIGHT}px;
width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
min-width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px;
background-color: ${({ theme }) => theme.background.primary};
border-bottom: ${({ theme, shouldDisplayBorderBottom }) =>
shouldDisplayBorderBottom
? `1px solid ${theme.background.primary}`
: 'none'};
border-bottom: 1px solid ${({ theme }) => theme.background.primary};
position: sticky;
left: 0;
@@ -181,46 +168,13 @@ export const RecordTableRecordGroupSection = () => {
sumOfWidthOfVisibleRecordFieldsAfterLabelIdentifierField +
sumOfBorderWidthForFields;
const allRecordIds = useRecoilComponentValue(
recordIndexAllRecordIdsComponentSelector,
);
const recordIdsOfThisGroup = useRecoilComponentFamilyValue(
recordIndexRecordIdsByGroupComponentFamilyState,
recordGroup?.id ?? '',
);
const indexOfFirstRowOfThisGroup = allRecordIds.findIndex(
(value) => value === recordIdsOfThisGroup[0],
);
const isFirstRowActive = useRecoilComponentFamilyValue(
isRecordTableRowActiveComponentFamilyState,
indexOfFirstRowOfThisGroup,
);
const isFirstRowFocused = useRecoilComponentFamilyValue(
isRecordTableRowFocusedComponentFamilyState,
indexOfFirstRowOfThisGroup,
);
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
const shouldDisplayBorderBottom =
!isFirstRowActiveOrFocused || !isRecordGroupTableSectionToggled;
if (!isDefined(recordGroup)) {
return null;
}
return (
<StyledTrContainer
onClick={handleDropdownToggle}
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
>
<StyledRecordTableDragAndDropPlaceholderCell
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
/>
<StyledTrContainer onClick={handleDropdownToggle}>
<StyledRecordTableDragAndDropPlaceholderCell />
<StyledChevronContainer>
<StyledAnimatedLightIconButton
Icon={IconChevronDown}