From 05b30554c352ccd122ad2c1882d52a5567fa0751 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 2 Dec 2025 18:51:06 +0100 Subject: [PATCH] Add back first column shrink on mobile (#16244) This PR fixes https://github.com/twentyhq/twenty/issues/14829 I created two states because one was needed for the table and the other for the ChipFieldDisplay, which can appear anywhere. The states tell if the first column and the ChipFieldDisplay should be shrinked. I also removed the usage of `useRecordTableLastColumnWidthToFill` to avoid unnecessary re-renders of the whole table on scroll. ## Before https://github.com/user-attachments/assets/8b6886b3-8976-41c2-9937-5d7ea396ec56 ## After https://github.com/user-attachments/assets/dc3b5ff9-59c4-4954-a973-57f6edc2508e --- .../display/components/ChipFieldDisplay.tsx | 10 +-- .../perf/ChipFieldDisplay.perf.stories.tsx | 2 + ...ecordIndexLabelIdentifierComponentState.ts | 9 +++ .../RecordTableColumnWidthEffect.tsx | 70 +++++++++++++++++-- .../components/RecordTableContent.tsx | 4 -- .../components/RecordTableEmpty.tsx | 12 ++-- .../RecordTableScrollAndZIndexEffect.tsx | 27 ++++++- .../components/RecordTableStyleWrapper.tsx | 34 ++------- .../useRecordTableLastColumnWidthToFill.ts | 8 ++- .../components/RecordTableColumnHead.tsx | 11 ++- .../hooks/useResizeTableHeader.ts | 24 +++++-- ...actRecordTableFirstColumnComponentState.ts | 9 +++ .../computeLastRecordTableColumnWidth.ts | 6 +- .../computeVisibleRecordFieldsWidthOnTable.ts | 12 ++-- .../RecordTableVirtualizedBodyPlaceholder.tsx | 38 ++-------- 15 files changed, 179 insertions(+), 97 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState.ts 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 4b921623d1..f681a6731d 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,8 +1,9 @@ import { RecordChip } from '@/object-record/components/RecordChip'; import { useChipFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay'; +import { shouldCompactRecordIndexLabelIdentifierComponentState } from '@/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState'; +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'; export const ChipFieldDisplay = () => { const { @@ -15,10 +16,11 @@ export const ChipFieldDisplay = () => { onRecordChipClick, } = useChipFieldDisplay(); - const isMobile = useIsMobile(); + const shouldCompactRecordIndexLabelIdentifier = useRecoilComponentValue( + shouldCompactRecordIndexLabelIdentifierComponentState, + ); - // TODO: reimplement scrolled horizontally here. - const isLabelIdentifierCompact = isMobile; + const isLabelIdentifierCompact = shouldCompactRecordIndexLabelIdentifier; if (!isDefined(recordValue)) { return null; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/__stories__/perf/ChipFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/__stories__/perf/ChipFieldDisplay.perf.stories.tsx index 3a13d453ec..990c159556 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/__stories__/perf/ChipFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/__stories__/perf/ChipFieldDisplay.perf.stories.tsx @@ -8,6 +8,7 @@ import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { ComponentDecorator } from 'twenty-ui/testing'; import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator'; +import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; @@ -16,6 +17,7 @@ import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockO const meta: Meta = { title: 'UI/Data/Field/Display/ChipFieldDisplay', decorators: [ + ContextStoreDecorator, (Story) => { const instanceId = 'child-field-display-scope'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState.ts b/packages/twenty-front/src/modules/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState.ts new file mode 100644 index 0000000000..795895336d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState.ts @@ -0,0 +1,9 @@ +import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const shouldCompactRecordIndexLabelIdentifierComponentState = + createComponentState({ + key: 'shouldCompactRecordIndexLabelIdentifierComponentState', + componentInstanceContext: ContextStoreComponentInstanceContext, + defaultValue: false, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableColumnWidthEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableColumnWidthEffect.tsx index 78798ad02d..e0065981b9 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableColumnWidthEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableColumnWidthEffect.tsx @@ -1,11 +1,21 @@ +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_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnLastEmptyColumnWidthVariableName'; +import { RECORD_TABLE_COLUMN_MIN_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnMinWidth'; import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthVariableName'; +import { RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE } from '@/object-record/record-table/constants/RecordTableLabelIdentifierColumnWidthOnMobile'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useRecordTableLastColumnWidthToFill } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill'; +import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState'; import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState'; +import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState'; +import { computeLastRecordTableColumnWidth } from '@/object-record/record-table/utils/computeLastRecordTableColumnWidth'; +import { computeVisibleRecordFieldsWidthOnTable } from '@/object-record/record-table/utils/computeVisibleRecordFieldsWidthOnTable'; import { getRecordTableColumnFieldWidthCSSVariableName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthCSSVariableName'; import { updateRecordTableCSSVariable } from '@/object-record/record-table/utils/updateRecordTableCSSVariable'; +import { RECORD_TABLE_VIRTUALIZATION_BODY_PLACEHOLDER_WIDTH_CSS_VARIABLE_NAME } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedBodyPlaceholder'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useEffect } from 'react'; import { isDefined } from 'twenty-shared/utils'; @@ -15,15 +25,47 @@ export const RecordTableColumnWidthEffect = () => { resizedFieldMetadataIdComponentState, ); - const { lastColumnWidth } = useRecordTableLastColumnWidthToFill(); - const { visibleRecordFields } = useRecordTableContextOrThrow(); + const shouldCompactRecordTableFirstColumn = useRecoilComponentValue( + shouldCompactRecordTableFirstColumnComponentState, + ); + + const recordTableWidth = useRecoilComponentValue( + recordTableWidthComponentState, + ); + useEffect(() => { if (isDefined(resizedFieldMetadataItemId)) { return; } + const { lastColumnWidth } = computeLastRecordTableColumnWidth({ + recordFields: visibleRecordFields, + shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn, + tableWidth: recordTableWidth, + }); + + const { visibleRecordFieldsWidth } = computeVisibleRecordFieldsWidthOnTable( + { + shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn, + visibleRecordFields, + }, + ); + + const totalTableBodyWidth = + visibleRecordFieldsWidth + + RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH + + RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH + + lastColumnWidth + + visibleRecordFields.length; + + updateRecordTableCSSVariable( + RECORD_TABLE_VIRTUALIZATION_BODY_PLACEHOLDER_WIDTH_CSS_VARIABLE_NAME, + `${totalTableBodyWidth}px`, + ); + updateRecordTableCSSVariable( RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME, `${lastColumnWidth}px`, @@ -40,7 +82,27 @@ export const RecordTableColumnWidthEffect = () => { `${recordField.size}px`, ); } - }, [lastColumnWidth, resizedFieldMetadataItemId, visibleRecordFields]); + + if (shouldCompactRecordTableFirstColumn) { + updateRecordTableCSSVariable( + getRecordTableColumnFieldWidthCSSVariableName(0), + `${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px`, + ); + } else { + const firstColumnWidth = + visibleRecordFields[0]?.size ?? RECORD_TABLE_COLUMN_MIN_WIDTH; + + updateRecordTableCSSVariable( + getRecordTableColumnFieldWidthCSSVariableName(0), + `${firstColumnWidth}px`, + ); + } + }, [ + resizedFieldMetadataItemId, + visibleRecordFields, + recordTableWidth, + shouldCompactRecordTableFirstColumn, + ]); 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 965fa5ee6e..cca2c04d4e 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 @@ -4,7 +4,6 @@ import { RecordTableStyleWrapper } from '@/object-record/record-table/components import { RecordTableWidthEffect } from '@/object-record/record-table/components/RecordTableWidthEffect'; import { RECORD_TABLE_HTML_ID } from '@/object-record/record-table/constants/RecordTableHtmlId'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useRecordTableLastColumnWidthToFill } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill'; 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'; @@ -72,8 +71,6 @@ export const RecordTableContent = ({ const { visibleRecordFields } = useRecordTableContextOrThrow(); - const { lastColumnWidth } = useRecordTableLastColumnWidthToFill(); - const setRecordTableHoverPosition = useSetRecoilComponentState( recordTableHoverPositionComponentState, ); @@ -103,7 +100,6 @@ export const RecordTableContent = ({ ref={tableBodyRef} isDragging={isDragging} visibleRecordFields={visibleRecordFields} - lastColumnWidth={lastColumnWidth} id={RECORD_TABLE_HTML_ID} onMouseLeave={handleMouseLeave} hasRecordGroups={hasRecordGroups} 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 06358b144d..eb221d98d0 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 @@ -8,17 +8,16 @@ import { RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH } from '@/object-record/record- import { RECORD_TABLE_HTML_ID } from '@/object-record/record-table/constants/RecordTableHtmlId'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState'; -import { useRecordTableLastColumnWidthToFill } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill'; import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState'; import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState'; import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState'; +import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState'; import { computeVisibleRecordFieldsWidthOnTable } from '@/object-record/record-table/utils/computeVisibleRecordFieldsWidthOnTable'; import { RecordTableVirtualizedDataChangedEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedDataChangedEffect'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; -import { useIsMobile } from 'twenty-ui/utilities'; const StyledEmptyStateContainer = styled.div<{ width: number }>` height: 100%; @@ -47,7 +46,9 @@ export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => { const isResizing = isDefined(resizedFieldMetadataId); - const isMobile = useIsMobile(); + const shouldCompactRecordTableFirstColumn = useRecoilComponentValue( + shouldCompactRecordTableFirstColumnComponentState, + ); const resizeOffsetToAddOnlyIfItMakesTableContainerGrow = isResizing ? resizeFieldOffset > 0 @@ -58,12 +59,10 @@ export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => { const totalColumnsBorderWidth = visibleRecordFields.length; const { visibleRecordFieldsWidth } = computeVisibleRecordFieldsWidthOnTable({ - isMobile, + shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn, visibleRecordFields, }); - const { lastColumnWidth } = useRecordTableLastColumnWidthToFill(); - const emptyTableContainerComputedWidth = visibleRecordFieldsWidth + RECORD_TABLE_COLUMN_CHECKBOX_WIDTH + @@ -86,7 +85,6 @@ export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => { diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableScrollAndZIndexEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableScrollAndZIndexEffect.tsx index ec6df71956..462d5962c1 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableScrollAndZIndexEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableScrollAndZIndexEffect.tsx @@ -1,23 +1,35 @@ +import { shouldCompactRecordIndexLabelIdentifierComponentState } from '@/object-record/record-index/states/shouldCompactRecordIndexLabelIdentifierComponentState'; import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName'; import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName'; import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState'; import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState'; +import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState'; import { updateRecordTableCSSVariable } from '@/object-record/record-table/utils/updateRecordTableCSSVariable'; import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useEffect } from 'react'; import { isDefined } from 'twenty-shared/utils'; +import { useIsMobile } from 'twenty-ui/utilities'; export const RecordTableScrollAndZIndexEffect = () => { const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement(); - + const isMobile = useIsMobile(); const [ isRecordTableScrolledHorizontally, setIsRecordTableScrolledHorizontally, ] = useRecoilComponentState(isRecordTableScrolledHorizontallyComponentState); + const setShouldCompactRecordTableFirstColumn = useSetRecoilComponentState( + shouldCompactRecordTableFirstColumnComponentState, + ); + + const setShouldCompactRecordIndexLabelIdentifier = useSetRecoilComponentState( + shouldCompactRecordIndexLabelIdentifierComponentState, + ); + const [isRecordTableScrolledVertically, setIsRecordTableScrolledVertically] = useRecoilComponentState(isRecordTableScrolledVerticallyComponentState); @@ -57,6 +69,16 @@ export const RecordTableScrollAndZIndexEffect = () => { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME, newVisibilityOfShadows, ); + + if (isMobile) { + if (newIsScrolledHorizontally) { + setShouldCompactRecordTableFirstColumn(true); + setShouldCompactRecordIndexLabelIdentifier(true); + } else { + setShouldCompactRecordTableFirstColumn(false); + setShouldCompactRecordIndexLabelIdentifier(false); + } + } } }; @@ -71,6 +93,9 @@ export const RecordTableScrollAndZIndexEffect = () => { isRecordTableScrolledHorizontally, setIsRecordTableScrolledVertically, setIsRecordTableScrolledHorizontally, + isMobile, + setShouldCompactRecordTableFirstColumn, + setShouldCompactRecordIndexLabelIdentifier, ]); return <>; 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 b8027d34fd..1507f7b569 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 @@ -10,7 +10,6 @@ import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/obj import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthClassName'; import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthVariableName'; import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName'; -import { RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE } from '@/object-record/record-table/constants/RecordTableLabelIdentifierColumnWidthOnMobile'; import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName'; import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex'; @@ -18,7 +17,6 @@ import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record- import { getRecordTableColumnFieldWidthCSSVariableName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthCSSVariableName'; import { css, type Theme } from '@emotion/react'; import styled from '@emotion/styled'; -import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; export const VerticalScrollBoxShadowCSS = ({ theme }: { theme: Theme }) => css` &::before { @@ -64,7 +62,6 @@ export const HorizontalScrollBoxShadowCSS = ({ const StyledTable = styled.div<{ isDragging?: boolean; visibleRecordFields: RecordField[]; - lastColumnWidth: number; hasRecordGroups: boolean; }>` & > * { @@ -128,12 +125,6 @@ const StyledTable = styled.div<{ : TABLE_Z_INDEX.headerColumns.withoutGroups.headerColumnsSticky}; ${HorizontalScrollBoxShadowCSS} - - @media (max-width: ${MOBILE_VIEWPORT}px) { - width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; - max-width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; - min-width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; - } } div.table-cell:nth-of-type(1) { @@ -192,7 +183,7 @@ const StyledTable = styled.div<{ max-width: ${RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH}px; } - ${({ visibleRecordFields, lastColumnWidth }) => { + ${({ visibleRecordFields }) => { let returnedCSS = ''; for (let i = 0; i < visibleRecordFields.length; i++) { @@ -200,28 +191,13 @@ const StyledTable = styled.div<{ } for (let i = 0; i < visibleRecordFields.length; i++) { - returnedCSS += `div.${getRecordTableColumnFieldWidthClassName(i)} { - width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); - min-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); - max-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); + returnedCSS += `div.${getRecordTableColumnFieldWidthClassName(i)} { + width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); + min-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); + max-width: var(${getRecordTableColumnFieldWidthCSSVariableName(i)}); } \n`; - - const isLabelIdentifierColumn = i === 0; - - if (isLabelIdentifierColumn) { - returnedCSS += `div.${getRecordTableColumnFieldWidthClassName(i)} { - @media (max-width: ${MOBILE_VIEWPORT}px) { - width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; - max-width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; - min-width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px; - } - } \n`; - } } - returnedCSS += `${RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME}: ${lastColumnWidth}px;`; - returnedCSS += `${RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME}: ${lastColumnWidth}px;`; - return returnedCSS; }}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill.ts index 40262ef2dc..bab0e5c406 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill.ts @@ -1,8 +1,8 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState'; +import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState'; import { computeLastRecordTableColumnWidth } from '@/object-record/record-table/utils/computeLastRecordTableColumnWidth'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useIsMobile } from 'twenty-ui/utilities'; export const useRecordTableLastColumnWidthToFill = () => { const { visibleRecordFields } = useRecordTableContextOrThrow(); @@ -11,12 +11,14 @@ export const useRecordTableLastColumnWidthToFill = () => { recordTableWidthComponentState, ); - const isMobile = useIsMobile(); + const shouldCompactRecordTableFirstColumn = useRecoilComponentValue( + shouldCompactRecordTableFirstColumnComponentState, + ); const { lastColumnWidth } = computeLastRecordTableColumnWidth({ recordFields: visibleRecordFields, tableWidth: recordTableWidth, - isMobile, + shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn, }); return { 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 c6b6f0aa69..893304502c 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 @@ -4,6 +4,8 @@ import styled from '@emotion/styled'; import { fieldMetadataItemByIdSelector } from '@/object-metadata/states/fieldMetadataItemByIdSelector'; import { isFieldMetadataItemLabelIdentifierSelector } from '@/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector'; import { type RecordField } from '@/object-record/record-field/types/RecordField'; +import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilValue } from 'recoil'; import { useIcons } from 'twenty-ui/display'; import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; @@ -69,10 +71,15 @@ export const RecordTableColumnHead = ({ }), ); - // TODO: fix previous behavior with isRecordTableScrolledHorizontally + const shouldCompactRecordTableFirstColumn = useRecoilComponentValue( + shouldCompactRecordTableFirstColumnComponentState, + ); + + const shouldHideTitle = + shouldCompactRecordTableFirstColumn && isLabelIdentifier; return ( - + diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useResizeTableHeader.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useResizeTableHeader.ts index b6822feb7a..9fbf61d0a1 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useResizeTableHeader.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/hooks/useResizeTableHeader.ts @@ -5,10 +5,12 @@ import { RECORD_TABLE_COLUMN_MIN_WIDTH } from '@/object-record/record-table/cons import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthVariableName'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; -import { useRecordTableLastColumnWidthToFill } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill'; +import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState'; import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState'; import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState'; +import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState'; +import { computeLastRecordTableColumnWidth } from '@/object-record/record-table/utils/computeLastRecordTableColumnWidth'; import { getRecordTableColumnFieldWidthCSSVariableName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthCSSVariableName'; import { updateRecordTableCSSVariable } from '@/object-record/record-table/utils/updateRecordTableCSSVariable'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; @@ -16,6 +18,7 @@ import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPoin import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { useSaveRecordFields } from '@/views/hooks/useSaveRecordFields'; @@ -55,7 +58,13 @@ export const useResizeTableHeader = () => { const { updateRecordField } = useUpdateRecordField(); - const { lastColumnWidth } = useRecordTableLastColumnWidthToFill(); + const recordTableWidth = useRecoilComponentValue( + recordTableWidthComponentState, + ); + + const shouldCompactRecordTableFirstColumn = useRecoilComponentValue( + shouldCompactRecordTableFirstColumnComponentState, + ); const handleResizeHandlerStart = useCallback( ({ x }) => { @@ -90,6 +99,12 @@ export const useResizeTableHeader = () => { `${newWidth}px`, ); + const { lastColumnWidth } = computeLastRecordTableColumnWidth({ + recordFields: visibleRecordFields, + shouldCompactFirstColumn: shouldCompactRecordTableFirstColumn, + tableWidth: recordTableWidth, + }); + const newLastColumnWidth = lastColumnWidth - newResizeOffset; updateRecordTableCSSVariable( @@ -110,11 +125,12 @@ export const useResizeTableHeader = () => { setResizeFieldOffset(x - initialPointerPositionX); }, [ - setResizeFieldOffset, initialPointerPositionX, recordField, - lastColumnWidth, visibleRecordFields, + shouldCompactRecordTableFirstColumn, + recordTableWidth, + setResizeFieldOffset, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState.ts new file mode 100644 index 0000000000..8d22de113c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState.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 shouldCompactRecordTableFirstColumnComponentState = + createComponentState({ + key: 'shouldCompactRecordTableFirstColumnComponentState', + componentInstanceContext: RecordTableComponentInstanceContext, + defaultValue: false, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/computeLastRecordTableColumnWidth.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/computeLastRecordTableColumnWidth.ts index 3f43acdbd9..f738fce9e6 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/utils/computeLastRecordTableColumnWidth.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/utils/computeLastRecordTableColumnWidth.ts @@ -7,14 +7,14 @@ import { computeVisibleRecordFieldsWidthOnTable } from '@/object-record/record-t export const computeLastRecordTableColumnWidth = ({ recordFields, tableWidth, - isMobile, + shouldCompactFirstColumn, }: { recordFields: Pick[]; tableWidth: number; - isMobile: boolean; + shouldCompactFirstColumn: boolean; }) => { const { visibleRecordFieldsWidth } = computeVisibleRecordFieldsWidthOnTable({ - isMobile, + shouldCompactFirstColumn, visibleRecordFields: recordFields, }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/computeVisibleRecordFieldsWidthOnTable.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/computeVisibleRecordFieldsWidthOnTable.ts index 935917bb76..401fec846e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/utils/computeVisibleRecordFieldsWidthOnTable.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/utils/computeVisibleRecordFieldsWidthOnTable.ts @@ -3,10 +3,10 @@ import { RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE } from '@/object-r import { sumByProperty } from 'twenty-shared/utils'; export const computeVisibleRecordFieldsWidthOnTable = ({ - isMobile, + shouldCompactFirstColumn, visibleRecordFields, }: { - isMobile: boolean; + shouldCompactFirstColumn: boolean; visibleRecordFields: Pick[]; }) => { const visibleRecordFieldsWithoutFirst = visibleRecordFields.slice(1); @@ -18,12 +18,14 @@ export const computeVisibleRecordFieldsWidthOnTable = ({ const sumWithAllFields = visibleRecordFields.reduce(sumByProperty('size'), 0); - const sumForMobile = + const sumForCompactedFirstColumn = RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE + sumWithoutFirstField; - const sumForNonMobile = sumWithAllFields; + const sumForNoCompactColumn = sumWithAllFields; - const visibleRecordFieldsWidth = isMobile ? sumForMobile : sumForNonMobile; + const visibleRecordFieldsWidth = shouldCompactFirstColumn + ? sumForCompactedFirstColumn + : sumForNoCompactColumn; return { visibleRecordFieldsWidth, diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedBodyPlaceholder.tsx b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedBodyPlaceholder.tsx index 553b397def..97ce5112c9 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedBodyPlaceholder.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedBodyPlaceholder.tsx @@ -1,54 +1,30 @@ -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_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useRecordTableLastColumnWidthToFill } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill'; -import { computeVisibleRecordFieldsWidthOnTable } from '@/object-record/record-table/utils/computeVisibleRecordFieldsWidthOnTable'; import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { styled } from '@linaria/react'; import { isDefined } from 'twenty-shared/utils'; -import { useIsMobile } from 'twenty-ui/utilities'; + +export const RECORD_TABLE_VIRTUALIZATION_BODY_PLACEHOLDER_WIDTH_CSS_VARIABLE_NAME = + '--record-table-virtualization-body-placeholder-width'; const StyledVirtualizationContainer = styled.div<{ - width: number; height: number; }>` height: ${({ height }) => height}px; - width: ${({ width }) => width}px; + width: var( + ${RECORD_TABLE_VIRTUALIZATION_BODY_PLACEHOLDER_WIDTH_CSS_VARIABLE_NAME} + ); `; export const RecordTableVirtualizedBodyPlaceholder = () => { - const { visibleRecordFields } = useRecordTableContextOrThrow(); - - const isMobile = useIsMobile(); - - const { visibleRecordFieldsWidth } = computeVisibleRecordFieldsWidthOnTable({ - isMobile, - visibleRecordFields, - }); - const totalNumberOfRecordsToVirtualize = useRecoilComponentValue( totalNumberOfRecordsToVirtualizeComponentState, ); - const { lastColumnWidth } = useRecordTableLastColumnWidthToFill(); - - const totalWidth = - visibleRecordFieldsWidth + - RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH + - RECORD_TABLE_COLUMN_CHECKBOX_WIDTH + - RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH + - lastColumnWidth + - visibleRecordFields.length; - const totalHeight = isDefined(totalNumberOfRecordsToVirtualize) ? totalNumberOfRecordsToVirtualize * (RECORD_TABLE_ROW_HEIGHT + 1) : 0; - return ( - - ); + return ; };