From fc8a8dc575f8fcb9687ae7c0992f41ed9a81310d Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Mon, 15 Sep 2025 16:28:28 +0200 Subject: [PATCH] Fixes to new table (#14506) This PR brings fixes to the table after the div refactor. There is still work to do to simplify and clean code, fix some left bugs, but overall this PR brings a way cleaner experience already. The table with groups still needs some work as it is often less tested, thus it has more bugs and code to clean. What this PR fixes : - Started refactoring hard-coded height and widths into constants (still some work to do) - Refactored TABLE_Z_INDEX and its consumer to separate into withGroups and withoutGroups (still some refactoring to simplify this) - Refactored common placeholder cells : add button, dynamic filling cell, checkbox, drag and drop - Fixed UI issues in table with groups action rows (load more and create new) - Started fixing z-index issues on record table with groups (some left to do) --- .../components/RecordTableStyleWrapper.tsx | 21 ---- .../constants/RecordTableRowHeight.ts | 1 + .../record-table/constants/TableZIndex.ts | 94 +++++++++++++----- .../RecordTableCellFirstRowFirstColumn.tsx | 28 +++++- .../RecordTableCellHoveredPortal.tsx | 97 +++++++++++++++---- .../components/RecordTableAggregateFooter.tsx | 60 ++++++++++-- .../RecordTableAggregateFooterCell.tsx | 26 +++-- .../RecordTableHeaderAddColumnButton.tsx | 12 ++- .../RecordTableHeaderCellContainer.tsx | 5 +- .../RecordTableHeaderFirstScrollableCell.tsx | 31 +++++- .../components/RecordTableActionRow.tsx | 60 +++++------- .../RecordTableAddButtonPlaceholderCell.tsx | 17 ++++ .../RecordTableDragAndDropPlaceholderCell.tsx | 25 +++++ .../RecordTableLastDynamicFillingCell.tsx | 23 +++++ .../RecordTableRecordGroupSection.tsx | 47 +++++---- 15 files changed, 391 insertions(+), 156 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/constants/RecordTableRowHeight.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableAddButtonPlaceholderCell.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDragAndDropPlaceholderCell.tsx create mode 100644 packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableLastDynamicFillingCell.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx index 78bf2d379c..445e430161 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableStyleWrapper.tsx @@ -113,27 +113,6 @@ const StyledTable = styled.div<{ } } - div.footer-cell:nth-of-type(n + 3) { - z-index: ${TABLE_Z_INDEX.footer.default}; - - position: sticky; - bottom: 0; - } - - div.footer-cell:nth-of-type(1) { - z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; - left: 0px; - bottom: 0; - position: sticky; - } - - div.footer-cell:nth-of-type(2) { - z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; - left: 48px; - bottom: 0; - position: sticky; - } - div.${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME} { width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px; min-width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH}px; diff --git a/packages/twenty-front/src/modules/object-record/record-table/constants/RecordTableRowHeight.ts b/packages/twenty-front/src/modules/object-record/record-table/constants/RecordTableRowHeight.ts new file mode 100644 index 0000000000..94195b7c41 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/constants/RecordTableRowHeight.ts @@ -0,0 +1 @@ +export const RECORD_TABLE_ROW_HEIGHT = 32; 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 0ef114ba5c..99fdad1f2f 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,41 +1,85 @@ export const TABLE_Z_INDEX = { base: 1, cell: { - default: 8, + default: 3, sticky: 12, editMode: 30, }, headerColumnsSticky: 14, headerColumnsNormal: 10, - firstCellWithoutVerticalScroll: 15, - firstCellWithVerticalScroll: 12, - noScrollAtAll: { - hoverPortalCellOnFirstScrollableColumn: 17, - hoverPortalCellOnNormalColumn: 17, - hoverPortalCellOnLabelIdentifierColumn: 17, - firstScrollableHeaderCell: 12, + withGroupsCell0_0: { + cell0_0HoveredWithoutScroll: 15, + cell0_0Normal: 13, }, - scrolledBothVerticallyAndHorizontally: { - hoverPortalCellOnNormalColumn: 2, - hoverPortalCellOnFirstScrollableColumn: 2, - hoverPortalCellOnLabelIdentifierColumn: 2, - firstScrollableHeaderCell: 12, + withoutGroupsCell0_0: { + cell0_0HoveredWithoutScroll: 15, + cell0_0Normal: 12, }, - scrolledHorizontallyOnly: { - hoverPortalCellOnLabelIdentifierColumn: 17, - hoverPortalCellOnNormalColumn: 11, - hoverPortalCellOnFirstScrollableColumn: 11, - firstScrollableHeaderCell: 10, + groupSection: { + stickyCell: 12, + normalCell: 10, }, - scrolledVerticallyOnly: { - hoverPortalCellOnNormalColumn: 9, - hoverPortalCellOnFirstScrollableColumn: 13, - hoverPortalCellOnLabelIdentifierColumn: 13, - firstScrollableHeaderCell: 14, + withGroups: { + noScrollAtAll: { + hoverPortalCellOnFirstScrollableColumn: 17, + hoverPortalCellOnNormalColumn: 17, + hoverPortalCellOnLabelIdentifierColumn: 17, + firstScrollableHeaderCell: 12, + }, + scrolledBothVerticallyAndHorizontally: { + hoverPortalCellOnNormalColumn: 2, + hoverPortalCellOnFirstScrollableColumn: 11, + hoverPortalCellOnLabelIdentifierColumn: 15, + firstScrollableHeaderCell: 12, + }, + scrolledHorizontallyOnly: { + hoverPortalCellOnLabelIdentifierColumn: 15, + hoverPortalCellOnNormalColumn: 11, + hoverPortalCellOnFirstScrollableColumn: 11, + firstScrollableHeaderCell: 10, + }, + scrolledVerticallyOnly: { + hoverPortalCellOnNormalColumn: 9, + hoverPortalCellOnFirstScrollableColumn: 15, + hoverPortalCellOnLabelIdentifierColumn: 15, + firstScrollableHeaderCell: 14, + }, + }, + withoutGroups: { + noScrollAtAll: { + hoverPortalCellOnFirstScrollableColumn: 17, + hoverPortalCellOnNormalColumn: 17, + hoverPortalCellOnLabelIdentifierColumn: 17, + firstScrollableHeaderCell: 12, + }, + scrolledBothVerticallyAndHorizontally: { + hoverPortalCellOnNormalColumn: 2, + hoverPortalCellOnFirstScrollableColumn: 2, + hoverPortalCellOnLabelIdentifierColumn: 2, + firstScrollableHeaderCell: 12, + }, + scrolledHorizontallyOnly: { + hoverPortalCellOnLabelIdentifierColumn: 17, + hoverPortalCellOnNormalColumn: 11, + hoverPortalCellOnFirstScrollableColumn: 11, + firstScrollableHeaderCell: 10, + }, + scrolledVerticallyOnly: { + hoverPortalCellOnNormalColumn: 9, + hoverPortalCellOnFirstScrollableColumn: 13, + hoverPortalCellOnLabelIdentifierColumn: 13, + firstScrollableHeaderCell: 14, + }, }, columnGrip: 30, footer: { - default: 18, - stickyColumn: 20, + tableWithGroups: { + default: 4, + stickyColumn: 5, + }, + tableWithoutGroups: { + default: 18, + stickyColumn: 20, + }, }, }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFirstRowFirstColumn.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFirstRowFirstColumn.tsx index 1d45c0f6fb..dcf84de571 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFirstRowFirstColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFirstRowFirstColumn.tsx @@ -1,8 +1,11 @@ +import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { StyledCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCellStyleWrapper'; import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; +import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import styled from '@emotion/styled'; import { type DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; import { cx } from '@linaria/core'; @@ -30,13 +33,32 @@ export const RecordTableCellFirstRowFirstColumn = ({ } & (Partial | null)) => { const { theme } = useContext(ThemeContext); + const hoverPosition = useRecoilComponentValue( + recordTableHoverPositionComponentState, + ); + + const isHoveredPortalOnThisCell = + hoverPosition?.column === 0 && hoverPosition.row === 0; + const [isRecordTableScrolledVertically] = useRecoilComponentState( isRecordTableScrolledVerticallyComponentState, ); - const zIndex = isRecordTableScrolledVertically - ? TABLE_Z_INDEX.firstCellWithVerticalScroll - : TABLE_Z_INDEX.firstCellWithoutVerticalScroll; + const hasRecordGroups = useRecoilComponentValue( + hasRecordGroupsComponentSelector, + ); + + const zIndexWithoutGroups = + !isRecordTableScrolledVertically && isHoveredPortalOnThisCell + ? TABLE_Z_INDEX.withoutGroupsCell0_0.cell0_0HoveredWithoutScroll + : TABLE_Z_INDEX.withoutGroupsCell0_0.cell0_0Normal; + + const zIndexWithGroups = + !isRecordTableScrolledVertically && isHoveredPortalOnThisCell + ? TABLE_Z_INDEX.withGroupsCell0_0.cell0_0HoveredWithoutScroll + : TABLE_Z_INDEX.withGroupsCell0_0.cell0_0Normal; + + const zIndex = hasRecordGroups ? zIndexWithGroups : zIndexWithoutGroups; const tdBackgroundColor = isSelected ? theme.accent.quaternary 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 9e8f43d3f0..fda534369c 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 @@ -2,6 +2,7 @@ import { RecordTableCellPortalWrapper } from '@/object-record/record-table/recor import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { RecordTableCellHoveredPortalContent } from '@/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortalContent'; import { RecordTableCellPortalRootContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellPortalRootContainer'; @@ -21,6 +22,10 @@ export const RecordTableCellHoveredPortal = () => { isRecordTableScrolledHorizontallyComponentState, ); + const hasRecordGroups = useRecoilComponentValue( + hasRecordGroupsComponentSelector, + ); + if (!hoverPosition) { return null; } @@ -28,45 +33,99 @@ export const RecordTableCellHoveredPortal = () => { const isOnFirstScrollableColumn = hoverPosition.column === 1; const isOnLabelIdentifierStickyColumn = hoverPosition.column === 0; - const zIndexForFirstScrollableColumn = + const zIndexForHoveredPortalOnFirstScrollableColumnWithoutGroups = isRecordTableScrolledHorizontally && isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally + ? TABLE_Z_INDEX.withoutGroups.scrolledBothVerticallyAndHorizontally .hoverPortalCellOnFirstScrollableColumn : isRecordTableScrolledHorizontally - ? TABLE_Z_INDEX.scrolledHorizontallyOnly + ? TABLE_Z_INDEX.withoutGroups.scrolledHorizontallyOnly .hoverPortalCellOnFirstScrollableColumn : isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledVerticallyOnly + ? TABLE_Z_INDEX.withoutGroups.scrolledVerticallyOnly .hoverPortalCellOnFirstScrollableColumn - : TABLE_Z_INDEX.noScrollAtAll.hoverPortalCellOnFirstScrollableColumn; + : TABLE_Z_INDEX.withoutGroups.noScrollAtAll + .hoverPortalCellOnFirstScrollableColumn; - const zIndexForLabelIdentifierStickyColumn = + const zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithoutGroups = isRecordTableScrolledHorizontally && isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally + ? TABLE_Z_INDEX.withoutGroups.scrolledBothVerticallyAndHorizontally .hoverPortalCellOnLabelIdentifierColumn : isRecordTableScrolledHorizontally - ? TABLE_Z_INDEX.scrolledHorizontallyOnly + ? TABLE_Z_INDEX.withoutGroups.scrolledHorizontallyOnly .hoverPortalCellOnLabelIdentifierColumn : isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledVerticallyOnly + ? TABLE_Z_INDEX.withoutGroups.scrolledVerticallyOnly .hoverPortalCellOnLabelIdentifierColumn - : TABLE_Z_INDEX.noScrollAtAll.hoverPortalCellOnLabelIdentifierColumn; + : TABLE_Z_INDEX.withoutGroups.noScrollAtAll + .hoverPortalCellOnLabelIdentifierColumn; - const zIndexForNormalColumn = + const zIndexForHoveredPortalOnNormalColumnWithoutGroups = isRecordTableScrolledHorizontally && isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally + ? TABLE_Z_INDEX.withoutGroups.scrolledBothVerticallyAndHorizontally .hoverPortalCellOnNormalColumn : isRecordTableScrolledHorizontally - ? TABLE_Z_INDEX.scrolledHorizontallyOnly.hoverPortalCellOnNormalColumn + ? TABLE_Z_INDEX.withoutGroups.scrolledHorizontallyOnly + .hoverPortalCellOnNormalColumn : isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledVerticallyOnly.hoverPortalCellOnNormalColumn - : TABLE_Z_INDEX.noScrollAtAll.hoverPortalCellOnNormalColumn; + ? TABLE_Z_INDEX.withoutGroups.scrolledVerticallyOnly + .hoverPortalCellOnNormalColumn + : TABLE_Z_INDEX.withoutGroups.noScrollAtAll + .hoverPortalCellOnNormalColumn; - const zIndex = isOnFirstScrollableColumn - ? zIndexForFirstScrollableColumn + const zIndexForHoveredPortalWithoutGroups = isOnFirstScrollableColumn + ? zIndexForHoveredPortalOnFirstScrollableColumnWithoutGroups : isOnLabelIdentifierStickyColumn - ? zIndexForLabelIdentifierStickyColumn - : zIndexForNormalColumn; + ? zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithoutGroups + : zIndexForHoveredPortalOnNormalColumnWithoutGroups; + + const zIndexForHoveredPortalOnFirstScrollableColumnWithGroups = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withGroups.scrolledBothVerticallyAndHorizontally + .hoverPortalCellOnFirstScrollableColumn + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.withGroups.scrolledHorizontallyOnly + .hoverPortalCellOnFirstScrollableColumn + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withGroups.scrolledVerticallyOnly + .hoverPortalCellOnFirstScrollableColumn + : TABLE_Z_INDEX.withGroups.noScrollAtAll + .hoverPortalCellOnFirstScrollableColumn; + + const zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithGroups = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withGroups.scrolledBothVerticallyAndHorizontally + .hoverPortalCellOnLabelIdentifierColumn + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.withGroups.scrolledHorizontallyOnly + .hoverPortalCellOnLabelIdentifierColumn + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withGroups.scrolledVerticallyOnly + .hoverPortalCellOnLabelIdentifierColumn + : TABLE_Z_INDEX.withGroups.noScrollAtAll + .hoverPortalCellOnLabelIdentifierColumn; + + const zIndexForHoveredPortalOnNormalColumnWithGroups = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withGroups.scrolledBothVerticallyAndHorizontally + .hoverPortalCellOnNormalColumn + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.withGroups.scrolledHorizontallyOnly + .hoverPortalCellOnNormalColumn + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withGroups.scrolledVerticallyOnly + .hoverPortalCellOnNormalColumn + : TABLE_Z_INDEX.withGroups.noScrollAtAll + .hoverPortalCellOnNormalColumn; + + const zIndexForHoveredPortalWithGroups = isOnFirstScrollableColumn + ? zIndexForHoveredPortalOnFirstScrollableColumnWithGroups + : isOnLabelIdentifierStickyColumn + ? zIndexForHoveredPortalOnLabelIdentifierStickyColumnWithGroups + : zIndexForHoveredPortalOnNormalColumnWithGroups; + + const zIndex = hasRecordGroups + ? zIndexForHoveredPortalWithGroups + : zIndexForHoveredPortalWithoutGroups; return ( 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 737dee31b7..d366c36f95 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 @@ -1,17 +1,54 @@ import styled from '@emotion/styled'; +import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth'; +import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidth'; +import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnDragAndDropWidth'; +import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnLastEmptyColumnWidthClassName'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableAggregateFooterCell } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell'; import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext'; +import { isDefined } from 'twenty-shared/utils'; -const StyledPlaceholderFirstCell = styled.div` +const StyledPlaceholderDragAndDropFooterCell = styled.div<{ + isTableWithGroups: boolean; +}>` background-color: ${({ theme }) => theme.background.primary}; - width: 48px; + width: ${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px; position: sticky; left: 0px; bottom: 0; - z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; + + z-index: ${({ isTableWithGroups }) => + isTableWithGroups + ? TABLE_Z_INDEX.footer.tableWithGroups.stickyColumn + : TABLE_Z_INDEX.footer.tableWithoutGroups.stickyColumn}; +`; + +const StyledPlaceholderAddButtonPlaceholderFooterCell = styled.div<{ + isTableWithGroups: boolean; +}>` + background-color: ${({ theme }) => theme.background.primary}; + width: ${RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH}px; + position: sticky; + bottom: 0; + z-index: ${({ isTableWithGroups }) => + isTableWithGroups + ? TABLE_Z_INDEX.footer.tableWithGroups.default + : TABLE_Z_INDEX.footer.tableWithoutGroups.default}; +`; + +const StyledPlaceholderLastColumnEmptyFooterCell = styled.div<{ + isTableWithGroups: boolean; +}>` + background-color: ${({ theme }) => theme.background.primary}; + position: sticky; + bottom: 0; + z-index: ${({ isTableWithGroups }) => + isTableWithGroups + ? TABLE_Z_INDEX.footer.tableWithGroups.default + : TABLE_Z_INDEX.footer.tableWithoutGroups.default}; `; export const RecordTableAggregateFooter = ({ @@ -21,9 +58,13 @@ export const RecordTableAggregateFooter = ({ }) => { const { visibleRecordFields } = useRecordTableContextOrThrow(); + const isTableWithGroups = isDefined(currentRecordGroupId); + return ( <> - + {visibleRecordFields.map((recordField, index) => { return ( ); })} - {/* TODO: fix span for divs styling here colSpan={visibleRecordFields.length - 1}*/} -
-
-
+ + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx index 127124664f..bd8df8b869 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx @@ -1,6 +1,7 @@ import styled from '@emotion/styled'; import { useContext } from 'react'; +import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext'; @@ -9,7 +10,8 @@ import { findByProperty, isDefined } from 'twenty-shared/utils'; const StyledColumnFooterCell = styled.div<{ columnWidth: number; - isFirstCell?: boolean; + isFirstCell: boolean; + isTableWithGroups: boolean; }>` background-color: ${({ theme }) => theme.background.primary}; color: ${({ theme }) => theme.font.color.tertiary}; @@ -31,12 +33,7 @@ const StyledColumnFooterCell = styled.div<{ }; `; }}; - height: 32px; - - position: sticky; - left: 48px; - bottom: 0; - z-index: ${TABLE_Z_INDEX.footer.stickyColumn}; + height: ${RECORD_TABLE_ROW_HEIGHT}px; user-select: none; overflow: auto; @@ -45,6 +42,14 @@ const StyledColumnFooterCell = styled.div<{ *::-webkit-scrollbar { display: none; } + + position: sticky; + bottom: 0; + + ${({ isFirstCell, isTableWithGroups }) => + isFirstCell + ? `left: 48px; z-index: ${isTableWithGroups ? TABLE_Z_INDEX.footer.tableWithGroups.stickyColumn : TABLE_Z_INDEX.footer.tableWithoutGroups.stickyColumn};` + : `z-index: ${isTableWithGroups ? TABLE_Z_INDEX.footer.tableWithGroups.default : TABLE_Z_INDEX.footer.tableWithoutGroups.default};`} `; const StyledColumnFootContainer = styled.div` @@ -70,6 +75,8 @@ export const RecordTableAggregateFooterCell = ({ findByProperty('fieldMetadataItemId', fieldMetadataId), ); + const isTableWithGroups = isDefined(currentRecordGroupId); + if (!isDefined(recordField)) { return null; } @@ -77,10 +84,9 @@ export const RecordTableAggregateFooterCell = ({ return ( theme.background.transparent.secondary}; + background: ${({ theme }) => theme.background.secondary}; } `; const StyledPlusIconContainer = styled.div` align-items: center; display: flex; - height: 32px; + height: ${RECORD_TABLE_ROW_HEIGHT}px; width: 100%; justify-content: center; `; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer.tsx index 59b3f3a520..ddce89dab7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer.tsx @@ -1,3 +1,4 @@ +import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; import styled from '@emotion/styled'; const StyledHeaderCell = styled.div<{ @@ -10,8 +11,8 @@ const StyledHeaderCell = styled.div<{ position: relative; - height: 32px; - max-height: 32px; + height: ${RECORD_TABLE_ROW_HEIGHT}px; + max-height: ${RECORD_TABLE_ROW_HEIGHT}px; background-color: ${({ theme }) => theme.background.primary}; border-right: 1px solid ${({ theme }) => theme.border.color.light}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderFirstScrollableCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderFirstScrollableCell.tsx index 1f458e3555..507d614648 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderFirstScrollableCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderFirstScrollableCell.tsx @@ -7,6 +7,7 @@ import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/rec import { RecordTableHeaderCellContainer } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer'; +import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector'; import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState'; import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState'; import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; @@ -50,15 +51,35 @@ export const RecordTableHeaderFirstScrollableCell = () => { isRecordTableScrolledHorizontallyComponentState, ); - const zIndex = + const hasRecordGroups = useRecoilComponentValue( + hasRecordGroupsComponentSelector, + ); + + const zIndexWithGroups = isRecordTableScrolledHorizontally && isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally + ? TABLE_Z_INDEX.withGroups.scrolledBothVerticallyAndHorizontally .firstScrollableHeaderCell : isRecordTableScrolledHorizontally - ? TABLE_Z_INDEX.scrolledHorizontallyOnly.firstScrollableHeaderCell + ? TABLE_Z_INDEX.withGroups.scrolledHorizontallyOnly + .firstScrollableHeaderCell : isRecordTableScrolledVertically - ? TABLE_Z_INDEX.scrolledVerticallyOnly.firstScrollableHeaderCell - : TABLE_Z_INDEX.noScrollAtAll.firstScrollableHeaderCell; + ? TABLE_Z_INDEX.withGroups.scrolledVerticallyOnly + .firstScrollableHeaderCell + : TABLE_Z_INDEX.withGroups.noScrollAtAll.firstScrollableHeaderCell; + + const zIndexWithoutGroups = + isRecordTableScrolledHorizontally && isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withoutGroups.scrolledBothVerticallyAndHorizontally + .firstScrollableHeaderCell + : isRecordTableScrolledHorizontally + ? TABLE_Z_INDEX.withoutGroups.scrolledHorizontallyOnly + .firstScrollableHeaderCell + : isRecordTableScrolledVertically + ? TABLE_Z_INDEX.withoutGroups.scrolledVerticallyOnly + .firstScrollableHeaderCell + : TABLE_Z_INDEX.withoutGroups.noScrollAtAll.firstScrollableHeaderCell; + + const zIndex = hasRecordGroups ? zIndexWithGroups : zIndexWithoutGroups; if (!recordField) { return <>; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableActionRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableActionRow.tsx index 895544132e..f2f7bfb8d0 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableActionRow.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableActionRow.tsx @@ -1,7 +1,12 @@ import styled from '@emotion/styled'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidth'; +import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { RecordTableAddButtonPlaceholderCell } from '@/object-record/record-table/record-table-row/components/RecordTableAddButtonPlaceholderCell'; +import { RecordTableDragAndDropPlaceholderCell } from '@/object-record/record-table/record-table-row/components/RecordTableDragAndDropPlaceholderCell'; +import { RecordTableLastDynamicFillingCell } from '@/object-record/record-table/record-table-row/components/RecordTableLastDynamicFillingCell'; import { useTheme } from '@emotion/react'; import { filterOutByProperty, @@ -10,37 +15,22 @@ import { } from 'twenty-shared/utils'; import { type IconComponent } from 'twenty-ui/display'; -const StyledDragDropPlaceholderCell = styled.div` - min-width: 16px; - width: 16px; - - position: sticky; +const StyledDragDropPlaceholderCell = styled( + RecordTableDragAndDropPlaceholderCell, +)` left: 0; -`; - -const StyledPlusButtonPlaceholderCell = styled.div` - height: 32px; - min-width: 32px; - width: 32px; - &:hover { - background-color: ${({ theme }) => theme.background.transparent.light}; - } + position: sticky; `; const StyledFieldPlaceholderCell = styled.div<{ widthOfFields: number }>` - height: 32px; + height: ${RECORD_TABLE_ROW_HEIGHT}px; min-width: ${({ widthOfFields }) => widthOfFields}px; width: ${({ widthOfFields }) => widthOfFields}px; - - &:hover { - background-color: ${({ theme }) => theme.background.transparent.light}; - } `; const StyledRecordTableDraggableTr = styled.div` cursor: pointer; - transition: background-color ${({ theme }) => theme.animation.duration.fast} - ease-in-out; + border: none; background: ${({ theme }) => theme.background.primary}; @@ -50,19 +40,12 @@ const StyledRecordTableDraggableTr = styled.div` &:hover { div:not(:first-of-type) { - background-color: ${({ theme }) => theme.background.transparent.light}; + background-color: ${({ theme }) => theme.background.secondary}; } } - div { + div:not(:first-of-type) { border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - background-color: ${({ theme }) => theme.background.primary}; - transition: background-color ${({ theme }) => theme.animation.duration.fast} - ease-in-out; - - &:first-of-type { - border-bottom: 1px solid ${({ theme }) => theme.background.primary}; - } } width: 100%; @@ -74,21 +57,21 @@ const StyledIconContainer = styled.div` border-right: none; color: ${({ theme }) => theme.font.color.secondary}; display: flex; - height: 32px; + height: ${RECORD_TABLE_ROW_HEIGHT}px; justify-content: center; - width: 32px; + width: ${RECORD_TABLE_COLUMN_CHECKBOX_WIDTH}px; position: sticky; left: 16px; `; -const StyledRecordTableTdTextContainer = styled.div<{ width: number }>` +const StyledActionTextContainer = styled.div<{ width: number }>` align-items: center; - background-color: transparent; + border-right: none; display: flex; - height: 32px; + height: ${RECORD_TABLE_ROW_HEIGHT}px; justify-content: start; left: 48px; @@ -147,18 +130,19 @@ export const RecordTableActionRow = ({ color={theme.font.color.tertiary} /> - {text} - + - + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableAddButtonPlaceholderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableAddButtonPlaceholderCell.tsx new file mode 100644 index 0000000000..a00a7cd532 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableAddButtonPlaceholderCell.tsx @@ -0,0 +1,17 @@ +import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth'; +import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; +import styled from '@emotion/styled'; +import { cx } from '@linaria/core'; + +const StyledPlaceholderAddButtonCell = styled.div` + height: ${RECORD_TABLE_ROW_HEIGHT}px; + width: ${RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH}px; +`; + +export const RecordTableAddButtonPlaceholderCell = ({ + className, +}: { + className?: string; +}) => { + return ; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDragAndDropPlaceholderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDragAndDropPlaceholderCell.tsx new file mode 100644 index 0000000000..e53831a546 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableDragAndDropPlaceholderCell.tsx @@ -0,0 +1,25 @@ +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 styled from '@emotion/styled'; +import { cx } from '@linaria/core'; + +const StyledPlaceholderDragAndDropCell = 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: 1px solid ${({ theme }) => theme.background.primary}; + + position: sticky; + left: 0; +`; + +export const RecordTableDragAndDropPlaceholderCell = ({ + className, +}: { + className?: string; +}) => { + return ; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableLastDynamicFillingCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableLastDynamicFillingCell.tsx new file mode 100644 index 0000000000..d2d65c8fc2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableLastDynamicFillingCell.tsx @@ -0,0 +1,23 @@ +import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnLastEmptyColumnWidthClassName'; +import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; +import styled from '@emotion/styled'; +import { cx } from '@linaria/core'; + +const StyledPlaceholderLastDynamicFillingCell = styled.div` + height: ${RECORD_TABLE_ROW_HEIGHT}px; +`; + +export const RecordTableLastDynamicFillingCell = ({ + className, +}: { + className?: string; +}) => { + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx index ebd96865ab..746530d9ba 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection.tsx @@ -8,7 +8,12 @@ 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 { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; +import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { RecordTableAddButtonPlaceholderCell } from '@/object-record/record-table/record-table-row/components/RecordTableAddButtonPlaceholderCell'; +import { RecordTableDragAndDropPlaceholderCell } from '@/object-record/record-table/record-table-row/components/RecordTableDragAndDropPlaceholderCell'; +import { RecordTableLastDynamicFillingCell } from '@/object-record/record-table/record-table-row/components/RecordTableLastDynamicFillingCell'; import { RecordTableRecordGroupStickyEffect } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect'; import { useAggregateRecordsForRecordTableSection } from '@/object-record/record-table/record-table-section/hooks/useAggregateRecordsForRecordTableSection'; import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState'; @@ -25,21 +30,17 @@ import { Tag } from 'twenty-ui/components'; import { IconChevronDown } from 'twenty-ui/display'; import { AnimatedLightIconButton } from 'twenty-ui/input'; -const StyledDragDropHeaderPlaceholder = styled.div` - min-width: 16px; - width: 16px; - position: sticky; - left: 0; -`; - const StyledTrContainer = styled.div` cursor: pointer; display: flex; flex-direction: row; + + div:not(:first-of-type) { + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + } `; const StyledChevronContainer = styled.div` - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; border-right: none; color: ${({ theme }) => theme.font.color.secondary}; display: flex; @@ -50,16 +51,19 @@ const StyledChevronContainer = styled.div` position: sticky; left: 16px; + + z-index: ${TABLE_Z_INDEX.groupSection.stickyCell}; `; const StyledAnimatedLightIconButton = styled(AnimatedLightIconButton)` display: block; margin: auto; + + z-index: ${TABLE_Z_INDEX.groupSection.stickyCell}; `; const StyledRecordGroupSection = styled.div<{ width: number }>` align-items: center; - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; border-right: none; display: flex; flex-direction: row; @@ -70,24 +74,26 @@ const StyledRecordGroupSection = styled.div<{ width: number }>` position: sticky; left: 48px; + + z-index: ${TABLE_Z_INDEX.groupSection.stickyCell}; `; const StyledTag = styled(Tag)` flex-shrink: 0; `; -const StyledPlusButtonPlaceholderCell = styled.div` - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - height: 32px; - min-width: 32px; - width: 32px; -`; - const StyledFieldPlaceholderCell = styled.div<{ widthOfFields: number }>` - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; - height: 32px; + height: ${RECORD_TABLE_ROW_HEIGHT}px; min-width: ${({ widthOfFields }) => widthOfFields}px; width: ${({ widthOfFields }) => widthOfFields}px; + + z-index: ${TABLE_Z_INDEX.groupSection.normalCell}; +`; + +const StyledRecordTableDragAndDropPlaceholderCell = styled( + RecordTableDragAndDropPlaceholderCell, +)` + z-index: ${TABLE_Z_INDEX.groupSection.stickyCell}; `; export const RecordTableRecordGroupSection = () => { @@ -153,7 +159,7 @@ export const RecordTableRecordGroupSection = () => { return ( - + { - + + ); };