From 821ae7c60d3c068179330e2eeb68c075566fd542 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Fri, 5 Sep 2025 18:54:38 +0200 Subject: [PATCH] Refactor table header with divs (#14319) Fixes https://github.com/twentyhq/core-team-issues/issues/1432 We still have some follow-up issues here, I only took care of making the essential of the table work, but the follow-up issues will be better tackled after everything has been switched to div, otherwise we might fix them multiple times. A first round of refactoring of z-index has been made in a common constant, where it is easier to understand, for which case, what should be the order of the different layers. Though we still need to have another round of refactor because of the stacking contexts, mainly because of the header row that is creating a stacking context that makes it hard to have all scrolling cases work. --- .../display/components/ChipFieldDisplay.tsx | 9 +- .../components/RecordTableContent.tsx | 4 +- .../components/RecordTableEmpty.tsx | 6 +- .../components/RecordTableStickyEffect.tsx | 20 +- .../components/RecordTableStyles.tsx | 3 +- .../components/RecordTableWithWrappers.tsx | 2 +- .../record-table/constants/TableZIndex.ts | 36 +++- .../components/RecordTableBody.tsx | 52 +++--- .../components/RecordTableBodyDroppable.tsx | 35 ++-- .../components/RecordTableCellGrip.tsx | 3 +- .../RecordTableCellHoveredPortal.tsx | 24 +++ .../components/RecordTableTd.tsx | 36 +--- .../components/RecordTableAggregateFooter.tsx | 57 +++--- .../components/RecordTableColumnHead.tsx | 10 +- .../components/RecordTableHeader.tsx | 175 +++++++++++------- .../RecordTableHeaderAddColumnButton.tsx | 90 +++++++++ .../components/RecordTableHeaderCell.tsx | 42 ++--- .../RecordTableHeaderCheckboxColumn.tsx | 14 +- .../RecordTableHeaderDragDropColumn.tsx | 17 +- .../RecordTableHeaderLastColumn.tsx | 85 +-------- .../components/RecordTableCellsEmpty.tsx | 1 + .../components/RecordTableTr.tsx | 3 +- .../RecordTableRecordGroupStickyEffect.tsx | 34 +--- ...TableScrolledHorizontallyComponentState.ts | 9 + ...dTableScrolledVerticallyComponentState.ts} | 4 +- 25 files changed, 426 insertions(+), 345 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderAddColumnButton.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState.ts rename packages/twenty-front/src/modules/object-record/record-table/states/{isRecordTableScrolledLeftComponentState.ts => isRecordTableScrolledVerticallyComponentState.ts} (75%) diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ChipFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ChipFieldDisplay.tsx index 6f3639143c..4b921623d1 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ChipFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/ChipFieldDisplay.tsx @@ -1,7 +1,5 @@ import { RecordChip } from '@/object-record/components/RecordChip'; import { useChipFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay'; -import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { isDefined } from 'twenty-shared/utils'; import { ChipSize } from 'twenty-ui/components'; import { useIsMobile } from 'twenty-ui/utilities'; @@ -18,12 +16,9 @@ export const ChipFieldDisplay = () => { } = useChipFieldDisplay(); const isMobile = useIsMobile(); - const isRecordTableScrolledLeftComponent = useRecoilComponentValue( - isRecordTableScrolledLeftComponentState, - ); - const isLabelIdentifierCompact = - isMobile && !isRecordTableScrolledLeftComponent; + // TODO: reimplement scrolled horizontally here. + const isLabelIdentifierCompact = isMobile; if (!isDefined(recordValue)) { return null; 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 803c142a56..55d2dfdc89 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 @@ -1,6 +1,6 @@ import { RecordTableStickyBottomEffect } from '@/object-record/record-table/components/RecordTableStickyBottomEffect'; import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect'; -import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles'; +import { StyledTableDiv } from '@/object-record/record-table/components/RecordTableStyles'; import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody'; import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody'; import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; @@ -12,7 +12,7 @@ import styled from '@emotion/styled'; import { useRef, useState } from 'react'; import { useRecoilCallback } from 'recoil'; -const StyledTableWithPointerEvents = styled(StyledTable)<{ +const StyledTableWithPointerEvents = styled(StyledTableDiv)<{ isDragging: boolean; }>` & > * { diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmpty.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmpty.tsx index 8209cbb2ad..a92d21dac5 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmpty.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableEmpty.tsx @@ -1,4 +1,4 @@ -import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles'; +import { StyledTableDiv } from '@/object-record/record-table/components/RecordTableStyles'; import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState'; import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; import styled from '@emotion/styled'; @@ -14,9 +14,9 @@ export interface RecordTableEmptyProps { export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => ( - + - + ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStickyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStickyEffect.tsx index 4b9ba847a3..5f51863498 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStickyEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStickyEffect.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react'; -import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState'; +import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; +import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState'; import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -11,7 +12,13 @@ export const RecordTableStickyEffect = () => { scrollWrapperScrollTopComponentState, ); + const setIsRecordTableScrolledVertically = useSetRecoilComponentState( + isRecordTableScrolledVerticallyComponentState, + ); + useEffect(() => { + setIsRecordTableScrolledVertically(scrollTop > 0); + if (scrollTop > 0) { document .getElementById('record-table-header') @@ -21,18 +28,19 @@ export const RecordTableStickyEffect = () => { .getElementById('record-table-header') ?.classList.remove('header-sticky'); } - }, [scrollTop]); + }, [scrollTop, setIsRecordTableScrolledVertically]); const scrollLeft = useRecoilComponentValue( scrollWrapperScrollLeftComponentState, ); - const setIsRecordTableScrolledLeft = useSetRecoilComponentState( - isRecordTableScrolledLeftComponentState, + const setIsRecordTableScrolledHorizontally = useSetRecoilComponentState( + isRecordTableScrolledHorizontallyComponentState, ); useEffect(() => { - setIsRecordTableScrolledLeft(scrollLeft === 0); + setIsRecordTableScrolledHorizontally(scrollLeft > 0); + if (scrollLeft > 0) { document .getElementById('record-table-body') @@ -54,7 +62,7 @@ export const RecordTableStickyEffect = () => { .getElementById('record-table-footer') ?.classList.remove('first-columns-sticky'); } - }, [scrollLeft, setIsRecordTableScrolledLeft]); + }, [scrollLeft, setIsRecordTableScrolledHorizontally]); return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyles.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyles.tsx index bcf481903b..4d67f8eb86 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyles.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyles.tsx @@ -1,9 +1,8 @@ import styled from '@emotion/styled'; -export const StyledTable = styled.table` +export const StyledTableDiv = styled.div` border-radius: ${({ theme }) => theme.border.radius.sm}; border-spacing: 0; - table-layout: fixed; width: 100%; .footer-sticky tr:nth-last-of-type(2) td { diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx index 6cd4f31456..9d4967aef6 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx @@ -17,7 +17,7 @@ const StyledTableContainer = styled.div` display: flex; flex-direction: column; position: relative; - width: 100%; + width: fit-content; height: 100%; `; diff --git a/packages/twenty-front/src/modules/object-record/record-table/constants/TableZIndex.ts b/packages/twenty-front/src/modules/object-record/record-table/constants/TableZIndex.ts index 662a488b95..c717959611 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/constants/TableZIndex.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/constants/TableZIndex.ts @@ -1,17 +1,37 @@ export const TABLE_Z_INDEX = { base: 1, cell: { - default: 1, - sticky: 4, - editMode: 6, + default: 3, + sticky: 10, + editMode: 20, }, footer: { - default: 4, - stickyColumn: 5, + default: 12, + stickyColumn: 20, }, - header: { - default: 6, - stickyColumn: 10, + noScrollAtAll: { + hoverPortalCell: 17, + headerRow: 13, + headerColumnsSticky: 16, + headerColumnsNormal: 15, + }, + scrolledBothVerticallyAndHorizontally: { + headerColumnsSticky: 13, + headerColumnsNormal: 12, + headerRow: 11, + hoverPortalCell: 2, + }, + scrolledHorizontallyOnly: { + headerColumnsSticky: 13, + headerColumnsNormal: 12, + headerRow: 11, + hoverPortalCell: 9, + }, + scrolledVerticallyOnly: { + headerColumnsSticky: 14, + headerColumnsNormal: 13, + headerRow: 12, + hoverPortalCell: 11, }, columnGrip: 30, }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx index e9bc21fde5..e7d8ecc7a7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx @@ -1,38 +1,32 @@ import styled from '@emotion/styled'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; +import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; const StyledTbody = styled.tbody` - &.first-columns-sticky { - td:nth-of-type(1) { - position: sticky; - left: 0; - z-index: ${TABLE_Z_INDEX.cell.sticky}; - transition: transform 0.3s ease; - } - td:nth-of-type(2) { - position: sticky; - left: 11px; - z-index: ${TABLE_Z_INDEX.cell.sticky}; - transition: transform 0.3s ease; - } - tr:not(:last-child) td:nth-of-type(3) { - // Last row is aggregate footer - position: sticky; - left: 39px; - z-index: ${TABLE_Z_INDEX.cell.sticky}; - transition: transform 0.3s ease; + // TODO: re-implement horizontal scroll here after table have been refactored to divs + td:nth-of-type(1) { + position: sticky; + left: 0px; + z-index: ${TABLE_Z_INDEX.cell.sticky}; + } - &:not(.disable-shadow)::after { - content: ''; - position: absolute; - top: -1px; - height: calc(100% + 2px); - width: 4px; - right: 0px; - box-shadow: ${({ theme }) => theme.boxShadow.light}; - clip-path: inset(0px -4px 0px 0px); - } + td:nth-of-type(2) { + position: sticky; + left: 16px; + z-index: ${TABLE_Z_INDEX.cell.sticky}; + } + + tr:not(:last-child) td:nth-of-type(3) { + position: sticky; + left: 49px; + z-index: ${TABLE_Z_INDEX.cell.sticky}; + } + + td:nth-of-type(3) { + @media (max-width: ${MOBILE_VIEWPORT}px) { + width: ${38}px; + max-width: ${38}px; } } `; 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 0593f5e6f3..bbadbab21a 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 @@ -2,10 +2,19 @@ import { RecordTableBody } from '@/object-record/record-table/record-table-body/ import { RecordTableBodyDroppableContextProvider } from '@/object-record/record-table/record-table-body/contexts/RecordTableBodyDroppableContext'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import styled from '@emotion/styled'; import { Droppable } from '@hello-pangea/dnd'; import { type ReactNode, useState } from 'react'; import { v4 } from 'uuid'; +const StyledTable = styled.table` + table-layout: fixed; + + border-radius: ${({ theme }) => theme.border.radius.sm}; + border-spacing: 0; + width: 100%; +`; + type RecordTableBodyDroppableProps = { children: ReactNode; recordGroupId?: string; @@ -30,19 +39,21 @@ export const RecordTableBodyDroppable = ({ isDropDisabled={isDropDisabled} > {(provided) => ( - setRecordTableHoverPosition(null)} - > - + setRecordTableHoverPosition(null)} > - {children} - - + + {children} + + + )} ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellGrip.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellGrip.tsx index a7da7822b6..28e12c2884 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellGrip.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellGrip.tsx @@ -5,7 +5,7 @@ import { useRecordTableRowDraggableContextOrThrow } from '@/object-record/record import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd'; import { IconListViewGrip } from 'twenty-ui/input'; -export const TABLE_CELL_GRIP_WIDTH = '16px'; +export const TABLE_CELL_GRIP_WIDTH = 16; const StyledContainer = styled.div` height: 32px; @@ -40,6 +40,7 @@ export const RecordTableCellGrip = () => { data-select-disable hasRightBorder={false} hasBottomBorder={false} + width={TABLE_CELL_GRIP_WIDTH} > 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 c968180845..75db4572cb 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 @@ -6,12 +6,15 @@ import styled from '@emotion/styled'; import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { useIsFieldInputOnly } from '@/object-record/record-field/ui/hooks/useIsFieldInputOnly'; +import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableCellDisplayMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode'; import { RecordTableCellEditButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditButton'; import { RecordTableCellEditMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditMode'; import { RecordTableCellFieldInput } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput'; import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState'; +import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; +import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; import { useContext } from 'react'; import { BORDER_COMMON } from 'twenty-ui/theme'; @@ -20,6 +23,7 @@ import { useIsMobile } from 'twenty-ui/utilities'; const StyledRecordTableCellHoveredPortalContent = styled.div<{ isReadOnly: boolean; isRowActive: boolean; + zIndex: number; }>` align-items: center; background: ${({ theme }) => theme.background.transparent.secondary}; @@ -42,6 +46,8 @@ const StyledRecordTableCellHoveredPortalContent = styled.div<{ position: relative; user-select: none; + + z-index: ${({ zIndex }) => zIndex}; `; const RecordTableCellHoveredPortalContent = () => { @@ -67,10 +73,28 @@ const RecordTableCellHoveredPortalContent = () => { rowIndex, ); + const isRecordTableScrolledHorizontally = useRecoilComponentValue( + isRecordTableScrolledHorizontallyComponentState, + ); + + const isRecordTableScrolledVertically = useRecoilComponentValue( + isRecordTableScrolledVerticallyComponentState, + ); + + const computedZIndex = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.hoverPortalCell + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.scrolledHorizontallyOnly.hoverPortalCell + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledVerticallyOnly.hoverPortalCell + : TABLE_Z_INDEX.noScrollAtAll.hoverPortalCell; + return ( {isFieldInputOnly ? ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx index 376f946174..b20e29c745 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx @@ -1,22 +1,23 @@ import { type DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; import { styled } from '@linaria/react'; import { type ReactNode, useContext } from 'react'; -import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui/theme'; +import { ThemeContext } from 'twenty-ui/theme'; -export const RECORD_TABLE_TD_WIDTH = '32px'; +export const DEFAULT_RECORD_TABLE_TD_WIDTH = 32; const StyledTd = styled.td<{ backgroundColor: string; borderColor: string; isDragging?: boolean; fontColor: string; - sticky?: boolean; - freezeFirstColumns?: boolean; - left?: number; hasRightBorder?: boolean; hasBottomBorder?: boolean; - width?: number; + width: number; }>` + min-width: ${({ width }) => width}px; + width: ${({ width }) => width}px; + max-width: ${({ width }) => width}px; + border-bottom: 1px solid ${({ borderColor, hasBottomBorder, isDragging }) => hasBottomBorder && !isDragging ? borderColor : 'transparent'}; @@ -26,46 +27,31 @@ const StyledTd = styled.td<{ hasRightBorder && !isDragging ? `1px solid ${borderColor}` : 'none'}; padding: 0; - transition: transform 0.3s ease; text-align: left; background: ${({ backgroundColor, isDragging }) => isDragging ? 'transparent' : backgroundColor}; - ${({ freezeFirstColumns }) => - freezeFirstColumns - ? `@media (max-width: ${MOBILE_VIEWPORT}px) { - width: ${RECORD_TABLE_TD_WIDTH}; - max-width: ${RECORD_TABLE_TD_WIDTH}; - }` - : ''} + // TODO: reimplement horizontal scroll here once we have refactored body with divs `; export const RecordTableTd = ({ children, isSelected, isDragging, - sticky, - freezeFirstColumns, - left, hasRightBorder = true, hasBottomBorder = true, - width, - colSpan, + width = DEFAULT_RECORD_TABLE_TD_WIDTH, ...dragHandleProps }: { className?: string; children?: ReactNode; isSelected?: boolean; isDragging?: boolean; - sticky?: boolean; - freezeFirstColumns?: boolean; hasRightBorder?: boolean; hasBottomBorder?: boolean; - left?: number; width?: number; - colSpan?: number; } & (Partial | null)) => { const { theme } = useContext(ThemeContext); @@ -83,13 +69,9 @@ export const RecordTableTd = ({ backgroundColor={tdBackgroundColor} borderColor={borderColor} fontColor={fontColor} - sticky={sticky} - freezeFirstColumns={freezeFirstColumns} - left={left} hasRightBorder={hasRightBorder} hasBottomBorder={hasBottomBorder} width={width} - colSpan={colSpan} // eslint-disable-next-line react/jsx-props-no-spreading {...dragHandleProps} > diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx index 5338d6adc3..36159d6042 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx @@ -20,44 +20,45 @@ const StyledTableRow = styled.tr<{ position: sticky; border: none; - &.footer-sticky { - td { - border-top: ${({ theme }) => `1px solid ${theme.border.color.light}`}; - z-index: ${TABLE_Z_INDEX.footer.default}; - position: sticky; - bottom: 0; - } + // TODO: see how we reimplement horizontal scrolling after all table has been refactored + td { + border-top: ${({ theme }) => `1px solid ${theme.border.color.light}`}; + z-index: ${TABLE_Z_INDEX.footer.default}; + position: sticky; + bottom: 0; } + cursor: pointer; td:nth-of-type(1) { width: ${FIRST_TH_WIDTH}; left: 0; border-top: none; } - &.first-columns-sticky { - td:nth-of-type(1) { - z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; + + td:nth-of-type(1) { + position: sticky; + z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; + } + td:nth-of-type(2) { + position: sticky; + z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; + transition: 0.3s ease; + &::after { + content: ''; + position: absolute; + top: -1px; + height: calc(100% + 2px); + width: 4px; + right: 0px; + box-shadow: ${({ theme }) => theme.boxShadow.light}; + clip-path: inset(0px -4px 0px 0px); } - td:nth-of-type(2) { - position: sticky; - z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; - transition: 0.3s ease; - &::after { - content: ''; - position: absolute; - top: -1px; - height: calc(100% + 2px); - width: 4px; - right: 0px; - box-shadow: ${({ theme }) => theme.boxShadow.light}; - clip-path: inset(0px -4px 0px 0px); - } - @media (max-width: ${MOBILE_VIEWPORT}px) { - width: 34px; - max-width: 34px; - } + @media (max-width: ${MOBILE_VIEWPORT}px) { + width: 38px; + max-width: 38px; } } + background: ${({ theme }) => theme.background.primary}; ${({ hasHorizontalOverflow }) => `.footer-sticky { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx index 05d2b3b0b9..dbe7bac4bd 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx @@ -1,7 +1,7 @@ import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState'; +import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; import { fieldMetadataItemByIdSelector } from '@/object-metadata/states/fieldMetadataItemByIdSelector'; import { isFieldMetadataItemLabelIdentifierSelector } from '@/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector'; @@ -61,8 +61,8 @@ export const RecordTableColumnHead = ({ }), ); - const isRecordTableScrolledLeft = useRecoilComponentValue( - isRecordTableScrolledLeftComponentState, + const isRecordTableScrolledHorizontally = useRecoilComponentValue( + isRecordTableScrolledHorizontallyComponentState, ); const { getIcon } = useIcons(); @@ -77,7 +77,9 @@ export const RecordTableColumnHead = ({ ); return ( - + diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx index 563be9bbd8..853d7c1f05 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx @@ -2,99 +2,140 @@ import styled from '@emotion/styled'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { RecordTableHeaderAddColumnButton } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderAddColumnButton'; import { RecordTableHeaderCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCell'; import { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn'; import { RecordTableHeaderDragDropColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn'; import { RecordTableHeaderLastColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn'; +import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; +import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; export const FIRST_TH_WIDTH = '10px'; -const StyledTableHead = styled.thead` +const StyledTableHead = styled.div<{ + stickyColumnZIndex: number; + normalColumnZIndex: number; + entireRowZIndex: number; +}>` cursor: pointer; - th:nth-of-type(1) { - width: ${FIRST_TH_WIDTH}; - left: 0; - border-right-color: ${({ theme }) => theme.background.primary}; + display: flex; + flex-direction: row; + align-items: center; + + height: 32px; + background-color: ${({ theme }) => theme.background.primary}; + + div.header-cell:nth-of-type(n + 3) { + z-index: ${({ normalColumnZIndex }) => normalColumnZIndex}; } - th:nth-of-type(2) { - border-right-color: ${({ theme }) => theme.background.primary}; + div.header-cell:nth-of-type(1) { + position: sticky; + left: 0px; + z-index: ${({ stickyColumnZIndex }) => stickyColumnZIndex}; + transition: 0.3s ease; + background-color: ${({ theme }) => theme.background.primary}; } - &.first-columns-sticky { - th:nth-of-type(1) { - position: sticky; - left: 0; - z-index: ${TABLE_Z_INDEX.header.default}; - transition: 0.3s ease; - } + div.header-cell:nth-of-type(2) { + position: sticky; + left: 17px; + top: 0; + z-index: ${({ stickyColumnZIndex }) => stickyColumnZIndex}; + transition: 0.3s ease; + background-color: ${({ theme }) => theme.background.primary}; + } - th:nth-of-type(2) { - position: sticky; - left: 11px; - z-index: ${TABLE_Z_INDEX.header.default}; - transition: 0.3s ease; - } + div.header-cell:nth-of-type(3) { + position: sticky; + left: 49px; + right: 0; + z-index: ${({ stickyColumnZIndex }) => stickyColumnZIndex}; + transition: 0.3s ease; + background-color: ${({ theme }) => theme.background.primary}; - th:nth-of-type(3) { - position: sticky; - left: 39px; - z-index: ${TABLE_Z_INDEX.header.default}; - transition: 0.3s ease; + // &::after { + // content: ''; + // position: absolute; + // top: -1px; + // height: calc(100% + 2px); + // width: 4px; + // right: 0px; + // box-shadow: ${({ theme }) => theme.boxShadow.light}; + // clip-path: inset(0px -4px 0px 0px); + // } - &::after { - content: ''; - position: absolute; - top: -1px; - height: calc(100% + 2px); - width: 4px; - right: 0px; - box-shadow: ${({ theme }) => theme.boxShadow.light}; - clip-path: inset(0px -4px 0px 0px); - } - - @media (max-width: ${MOBILE_VIEWPORT}px) { - width: 34px; - max-width: 34px; - } + @media (max-width: ${MOBILE_VIEWPORT}px) { + width: 38px; + max-width: 38px; + min-width: 38px; } } - &.header-sticky { - th { - position: sticky; - top: 0; - z-index: ${TABLE_Z_INDEX.header.default}; - } - } - - &.header-sticky.first-columns-sticky { - th:nth-of-type(1), - th:nth-of-type(2), - th:nth-of-type(3) { - z-index: ${TABLE_Z_INDEX.header.stickyColumn}; - } - } + position: sticky; + top: 0px; + z-index: ${({ entireRowZIndex }) => entireRowZIndex}; `; export const RecordTableHeader = () => { const { visibleRecordFields } = useRecordTableContextOrThrow(); + const isRecordTableScrolledHorizontally = useRecoilComponentValue( + isRecordTableScrolledHorizontallyComponentState, + ); + + const isRecordTableScrolledVertically = useRecoilComponentValue( + isRecordTableScrolledVerticallyComponentState, + ); + + const computedStickyColumnZIndex = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.headerColumnsSticky + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.scrolledHorizontallyOnly.headerColumnsSticky + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledVerticallyOnly.headerColumnsSticky + : TABLE_Z_INDEX.noScrollAtAll.headerColumnsSticky; + + const computedNormalColumnZIndex = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.headerColumnsNormal + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.scrolledHorizontallyOnly.headerColumnsNormal + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledVerticallyOnly.headerColumnsNormal + : TABLE_Z_INDEX.noScrollAtAll.headerColumnsNormal; + + const computedHeaderRowZIndex = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.headerRow + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.scrolledHorizontallyOnly.headerRow + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.scrolledVerticallyOnly.headerRow + : TABLE_Z_INDEX.noScrollAtAll.headerRow; + return ( - - - - - {visibleRecordFields.map((recordField) => ( - - ))} - - + + + + {visibleRecordFields.map((recordField) => ( + + ))} + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderAddColumnButton.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderAddColumnButton.tsx new file mode 100644 index 0000000000..6e0dc4242b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderAddColumnButton.tsx @@ -0,0 +1,90 @@ +import styled from '@emotion/styled'; + +import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId'; +import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent'; +import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState'; +import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement'; +import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; +import { useTheme } from '@emotion/react'; +import { IconPlus } from 'twenty-ui/display'; + +const StyledPlusIconHeaderCell = styled.div<{ + isTableWiderThanScreen: boolean; + isFirstRowActiveOrFocused: boolean; +}>` + border-bottom: ${({ isFirstRowActiveOrFocused, theme }) => + isFirstRowActiveOrFocused + ? 'none' + : `1px solid ${theme.border.color.light}`}; + background-color: ${({ theme }) => theme.background.primary}; + + color: ${({ theme }) => theme.font.color.tertiary}; + border-right: ${({ theme }) => theme.border.color.light} !important; + + cursor: default; + + width: 32px; + + z-index: 1; + + &:hover { + background: ${({ theme }) => theme.background.transparent.secondary}; + } +`; + +const StyledPlusIconContainer = styled.div` + align-items: center; + display: flex; + height: 32px; + width: 100%; + justify-content: center; +`; + +const StyledDropdownContainer = styled.div` + cursor: pointer; + width: 100%; +`; + +export const RecordTableHeaderAddColumnButton = () => { + const theme = useTheme(); + + const { scrollWrapperHTMLElement } = useScrollWrapperElement(); + + const isTableWiderThanScreen = + (scrollWrapperHTMLElement?.clientWidth ?? 0) < + (scrollWrapperHTMLElement?.scrollWidth ?? 0); + + const isFirstRowActive = useRecoilComponentFamilyValue( + isRecordTableRowActiveComponentFamilyState, + 0, + ); + + const isFirstRowFocused = useRecoilComponentFamilyValue( + isRecordTableRowFocusedComponentFamilyState, + 0, + ); + + const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused; + + return ( + + + + + + } + dropdownComponents={} + dropdownPlacement="bottom-start" + /> + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx index c7e2af5511..8d4feb0fc8 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx @@ -12,7 +12,6 @@ import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useC import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown'; import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState'; import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; -import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState'; import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState'; import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer'; import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener'; @@ -20,7 +19,6 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { useSaveRecordFields } from '@/views/hooks/useSaveRecordFields'; import { throwIfNotDefined } from 'twenty-shared/utils'; @@ -29,26 +27,24 @@ import { LightIconButton } from 'twenty-ui/input'; const COLUMN_MIN_WIDTH = 104; -const StyledColumnHeaderCell = styled.th<{ +const StyledColumnHeaderCell = styled.div<{ columnWidth: number; isResizing?: boolean; isFirstRowActiveOrFocused: boolean; }>` - border-bottom: ${({ isFirstRowActiveOrFocused, theme }) => - isFirstRowActiveOrFocused - ? 'none' - : `1px solid ${theme.border.color.light}`}; color: ${({ theme }) => theme.font.color.tertiary}; padding: 0; text-align: left; background-color: ${({ theme }) => theme.background.primary}; border-right: 1px solid ${({ theme }) => theme.border.color.light}; + + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + ${({ columnWidth }) => ` min-width: ${columnWidth}px; width: ${columnWidth}px; `} - position: relative; user-select: none; ${({ theme }) => { return ` @@ -96,8 +92,6 @@ const StyledColumnHeadContainer = styled.div` display: flex; flex-direction: row; justify-content: space-between; - position: relative; - z-index: 1; & > :first-of-type { flex: 1; @@ -202,10 +196,6 @@ export const RecordTableHeaderCell = ({ onMouseUp: handleResizeHandlerEnd, }); - const isRecordTableScrolledLeft = useRecoilComponentValue( - isRecordTableScrolledLeftComponentState, - ); - const isMobile = useIsMobile(); const { labelIdentifierFieldMetadataItem } = useRecordIndexContextOrThrow(); @@ -213,8 +203,7 @@ export const RecordTableHeaderCell = ({ const isLabelIdentifier = recordField.fieldMetadataItemId === labelIdentifierFieldMetadataItem?.id; - const disableColumnResize = - isLabelIdentifier && isMobile && !isRecordTableScrolledLeft; + const disableColumnResize = isLabelIdentifier || isMobile; const { createNewIndexRecord } = useCreateNewIndexRecord({ objectMetadataItem, @@ -241,22 +230,27 @@ export const RecordTableHeaderCell = ({ 0, ); + const widthOffsetWhileResizing = + resizedFieldMetadataItemId === recordField.fieldMetadataItemId + ? resizeFieldOffset + : 0; + + const baseWidth = recordField?.size ?? 0; + + const computedDynamicWidth = baseWidth + widthOffsetWhileResizing; + + const columnWidth = Math.max(computedDynamicWidth, COLUMN_MIN_WIDTH); + const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused; return ( setIconVisibility(true)} onMouseLeave={() => setIconVisibility(false)} isFirstRowActiveOrFocused={isFirstRowActiveOrFocused} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn.tsx index 0558d140ba..c9385bd8af 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn.tsx @@ -17,21 +17,20 @@ const StyledContainer = styled.div` display: flex; height: 32px; justify-content: center; - width: 24px; + min-width: 24px; padding-right: ${({ theme }) => theme.spacing(1)}; background-color: ${({ theme }) => theme.background.primary}; `; -const StyledColumnHeaderCell = styled.th<{ +const StyledColumnHeaderCell = styled.div<{ isFirstRowActiveOrFocused: boolean; }>` background-color: ${({ theme }) => theme.background.primary}; - border-bottom: ${({ isFirstRowActiveOrFocused, theme }) => - isFirstRowActiveOrFocused - ? 'none' - : `1px solid ${theme.border.color.light}`}; - width: 28px; + + min-width: 32px; box-sizing: border-box; + + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; `; export const RecordTableHeaderCheckboxColumn = () => { @@ -85,6 +84,7 @@ export const RecordTableHeaderCheckboxColumn = () => { return ( ` - background: ${({ backgroundColor }) => backgroundColor}; - border-bottom: none; - border-top: none; +const StyledTh = styled.div<{ backgroundColor: string }>` + background-color: ${({ backgroundColor }) => backgroundColor}; + min-width: 17px; + min-height: 100%; + + border-bottom: 1px solid ${({ backgroundColor }) => backgroundColor}; `; export const RecordTableHeaderDragDropColumn = () => { const { theme } = useContext(ThemeContext); - return ; + return ( + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx index fecf14a307..269e8ee11d 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx @@ -1,93 +1,16 @@ import styled from '@emotion/styled'; -import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId'; -import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent'; -import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState'; -import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; -import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement'; -import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; -import { useTheme } from '@emotion/react'; -import { IconPlus } from 'twenty-ui/display'; +const StyledLastColumnHeader = styled.div` + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; -const StyledPlusIconHeaderCell = styled.th<{ - isTableWiderThanScreen: boolean; - isFirstRowActiveOrFocused: boolean; -}>` - border-bottom: ${({ isFirstRowActiveOrFocused, theme }) => - isFirstRowActiveOrFocused - ? 'none' - : `1px solid ${theme.border.color.light}`}; background-color: ${({ theme }) => theme.background.primary}; border-left: none !important; color: ${({ theme }) => theme.font.color.tertiary}; - border-right: none !important; - cursor: default; - ${({ isTableWiderThanScreen, theme }) => - isTableWiderThanScreen - ? ` - background-color: ${theme.background.primary}; - width: 32px; - ` - : 'width: 100%'}; - z-index: 1; - - &:hover { - background: ${({ theme }) => theme.background.transparent.secondary}; - } -`; - -const StyledPlusIconContainer = styled.div` - align-items: center; - display: flex; + width: 100%; height: 32px; - justify-content: flex-start; - margin-left: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledDropdownContainer = styled.div` - cursor: pointer; `; export const RecordTableHeaderLastColumn = () => { - const theme = useTheme(); - - const { scrollWrapperHTMLElement } = useScrollWrapperElement(); - - const isTableWiderThanScreen = - (scrollWrapperHTMLElement?.clientWidth ?? 0) < - (scrollWrapperHTMLElement?.scrollWidth ?? 0); - - const isFirstRowActive = useRecoilComponentFamilyValue( - isRecordTableRowActiveComponentFamilyState, - 0, - ); - - const isFirstRowFocused = useRecoilComponentFamilyValue( - isRecordTableRowFocusedComponentFamilyState, - 0, - ); - - const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused; - - return ( - - - - - - } - dropdownComponents={} - dropdownPlacement="bottom-start" - /> - - - ); + return ; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsEmpty.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsEmpty.tsx index 8cd57a8cb4..47c5151e26 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsEmpty.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsEmpty.tsx @@ -11,6 +11,7 @@ export const RecordTableCellsEmpty = () => { )); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx index 7838880319..7beb14df94 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx @@ -19,8 +19,9 @@ const StyledTr = styled.tr<{ isDragging ? `1px solid ${theme.border.color.medium}` : '1px solid transparent'}; + + border-left: none; position: relative; - transition: border-left-color 0.2s ease-in-out; &[data-next-row-active-or-focused='true'] { td { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect.tsx index afdeb4a746..513b10f049 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; -import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState'; +import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; @@ -11,38 +11,16 @@ export const RecordTableRecordGroupStickyEffect = () => { scrollWrapperScrollLeftComponentState, ); - const setIsRecordTableScrolledLeft = useSetRecoilComponentState( - isRecordTableScrolledLeftComponentState, + const setIsRecordTableScrolledHorizontally = useSetRecoilComponentState( + isRecordTableScrolledHorizontallyComponentState, ); const currentRecordGroupId = useCurrentRecordGroupId(); useEffect(() => { - setIsRecordTableScrolledLeft(scrollLeft === 0); - if (scrollLeft > 0) { - document - .getElementById( - `record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`, - ) - ?.classList.add('first-columns-sticky'); - document - .getElementById( - `record-table-body${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`, - ) - ?.classList.add('first-columns-sticky'); - } else { - document - .getElementById( - `record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`, - ) - ?.classList.remove('first-columns-sticky'); - document - .getElementById( - `record-table-body${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`, - ) - ?.classList.remove('first-columns-sticky'); - } - }, [currentRecordGroupId, scrollLeft, setIsRecordTableScrolledLeft]); + setIsRecordTableScrolledHorizontally(scrollLeft > 0); + // TODO: see if we need to reimplement setting classes here. + }, [currentRecordGroupId, scrollLeft, setIsRecordTableScrolledHorizontally]); return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState.ts new file mode 100644 index 0000000000..663b866979 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState.ts @@ -0,0 +1,9 @@ +import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const isRecordTableScrolledHorizontallyComponentState = + createComponentState({ + key: 'isRecordTableScrolledHorizontallyComponentState', + componentInstanceContext: RecordTableComponentInstanceContext, + defaultValue: true, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledLeftComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState.ts similarity index 75% rename from packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledLeftComponentState.ts rename to packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState.ts index 4dd17e3612..0e4e3ef5ff 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledLeftComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState.ts @@ -1,9 +1,9 @@ import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; -export const isRecordTableScrolledLeftComponentState = +export const isRecordTableScrolledVerticallyComponentState = createComponentState({ - key: 'isRecordTableScrolledLeftComponentState', + key: 'isRecordTableScrolledVerticallyComponentState', componentInstanceContext: RecordTableComponentInstanceContext, defaultValue: true, });