fix: content getting hide on drag and drop in stage view cards (#7621)

## ISSUE

-  Closes #7388 

## Demo



https://github.com/user-attachments/assets/193813aa-def9-406b-9fe7-397627bb1242

- [ ] Table Row Drag WIP

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Nabhag Motivaras
2024-11-05 20:33:24 +05:30
committed by GitHub
parent 84b0b78b6f
commit 3793f6c451
9 changed files with 184 additions and 145 deletions

View File

@ -23,14 +23,13 @@ import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ReactNode, useContext, useState } from 'react'; import { ReactNode, useContext, useState } from 'react';
import { useInView } from 'react-intersection-observer'; import { InView, useInView } from 'react-intersection-observer';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { import {
AnimatedEaseInOut, AnimatedEaseInOut,
AvatarChipVariant, AvatarChipVariant,
Checkbox, Checkbox,
CheckboxVariant, CheckboxVariant,
ChipSize,
IconEye, IconEye,
IconEyeOff, IconEyeOff,
LightIconButton, LightIconButton,
@ -147,10 +146,6 @@ const StyledCompactIconContainer = styled.div`
margin-left: ${({ theme }) => theme.spacing(1)}; margin-left: ${({ theme }) => theme.spacing(1)};
`; `;
const StyledRecordInlineCellPlaceholder = styled.div`
height: 24px;
`;
export const RecordBoardCard = ({ export const RecordBoardCard = ({
isCreating = false, isCreating = false,
onCreateSuccess, onCreateSuccess,
@ -240,7 +235,7 @@ export const RecordBoardCard = ({
const scrollWrapperRef = useContext(RecordBoardScrollWrapperContext); const scrollWrapperRef = useContext(RecordBoardScrollWrapperContext);
const { ref: cardRef, inView } = useInView({ const { ref: cardRef } = useInView({
root: scrollWrapperRef?.ref.current, root: scrollWrapperRef?.ref.current,
rootMargin: '1000px', rootMargin: '1000px',
}); });
@ -256,126 +251,123 @@ export const RecordBoardCard = ({
return ( return (
<StyledBoardCardWrapper onContextMenu={handleActionMenuDropdown}> <StyledBoardCardWrapper onContextMenu={handleActionMenuDropdown}>
{!isCreating && <RecordValueSetterEffect recordId={recordId} />} {!isCreating && <RecordValueSetterEffect recordId={recordId} />}
<StyledBoardCard <InView>
ref={cardRef} <StyledBoardCard
selected={isCurrentCardSelected} ref={cardRef}
onMouseLeave={onMouseLeaveBoard} selected={isCurrentCardSelected}
onClick={() => { onMouseLeave={onMouseLeaveBoard}
if (!isCreating) { onClick={() => {
setIsCurrentCardSelected(!isCurrentCardSelected); if (!isCreating) {
} setIsCurrentCardSelected(!isCurrentCardSelected);
}} }
> }}
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
{isCreating && position !== undefined ? (
<RecordInlineCellEditMode>
<StyledTextInput
autoFocus
value={newLabelValue}
onInputEnter={() =>
handleInputEnter(
labelIdentifierField?.label ?? '',
newLabelValue,
position,
onCreateSuccess,
)
}
onBlur={() =>
handleBlur(
labelIdentifierField?.label ?? '',
newLabelValue,
position,
onCreateSuccess,
)
}
onChange={(text: string) => setNewLabelValue(text)}
placeholder={labelIdentifierField?.label}
/>
</RecordInlineCellEditMode>
) : (
<RecordIdentifierChip
objectNameSingular={objectMetadataItem.nameSingular}
record={record as ObjectRecord}
variant={AvatarChipVariant.Transparent}
size={ChipSize.Large}
/>
)}
{!isCreating && (
<>
{isCompactModeActive && (
<StyledCompactIconContainer className="compact-icon-container">
<LightIconButton
Icon={isCardExpanded ? IconEyeOff : IconEye}
accent="tertiary"
onClick={(e) => {
e.stopPropagation();
setIsCardExpanded((prev) => !prev);
}}
/>
</StyledCompactIconContainer>
)}
<StyledCheckboxContainer className="checkbox-container">
<Checkbox
hoverable
checked={isCurrentCardSelected}
onChange={() =>
setIsCurrentCardSelected(!isCurrentCardSelected)
}
variant={CheckboxVariant.Secondary}
/>
</StyledCheckboxContainer>
</>
)}
</StyledBoardCardHeader>
<AnimatedEaseInOut
isOpen={isCardExpanded || !isCompactModeActive}
initial={false}
> >
<StyledBoardCardBody> <StyledBoardCardHeader showCompactView={isCompactModeActive}>
{visibleFieldDefinitionsFiltered.map((fieldDefinition) => ( {isCreating && position !== undefined ? (
<PreventSelectOnClickContainer <RecordInlineCellEditMode>
key={fieldDefinition.fieldMetadataId} <StyledTextInput
> autoFocus
<FieldContext.Provider value={newLabelValue}
value={{ onInputEnter={() =>
recordId: isCreating ? '' : recordId, handleInputEnter(
maxWidth: 156, labelIdentifierField?.label ?? '',
recoilScopeId: newLabelValue,
(isCreating ? 'new' : recordId) + position,
fieldDefinition.fieldMetadataId, onCreateSuccess,
isLabelIdentifier: false, )
fieldDefinition: { }
disableTooltip: false, onBlur={() =>
fieldMetadataId: fieldDefinition.fieldMetadataId, handleBlur(
label: fieldDefinition.label, labelIdentifierField?.label ?? '',
iconName: fieldDefinition.iconName, newLabelValue,
type: fieldDefinition.type, position,
metadata: fieldDefinition.metadata, onCreateSuccess,
defaultValue: fieldDefinition.defaultValue, )
editButtonIcon: getFieldButtonIcon({ }
metadata: fieldDefinition.metadata, onChange={(text: string) => setNewLabelValue(text)}
type: fieldDefinition.type, placeholder={labelIdentifierField?.label}
}), />
settings: fieldDefinition.settings, </RecordInlineCellEditMode>
}, ) : (
useUpdateRecord: useUpdateOneRecordHook, <RecordIdentifierChip
hotkeyScope: InlineCellHotkeyScope.InlineCell, objectNameSingular={objectMetadataItem.nameSingular}
}} record={record as ObjectRecord}
variant={AvatarChipVariant.Transparent}
/>
)}
{!isCreating && (
<>
{isCompactModeActive && (
<StyledCompactIconContainer className="compact-icon-container">
<LightIconButton
Icon={isCardExpanded ? IconEyeOff : IconEye}
accent="tertiary"
onClick={(e) => {
e.stopPropagation();
setIsCardExpanded((prev) => !prev);
}}
/>
</StyledCompactIconContainer>
)}
<StyledCheckboxContainer className="checkbox-container">
<Checkbox
hoverable
checked={isCurrentCardSelected}
onChange={() =>
setIsCurrentCardSelected(!isCurrentCardSelected)
}
variant={CheckboxVariant.Secondary}
/>
</StyledCheckboxContainer>
</>
)}
</StyledBoardCardHeader>
<AnimatedEaseInOut
isOpen={isCardExpanded || !isCompactModeActive}
initial={false}
>
<StyledBoardCardBody>
{visibleFieldDefinitionsFiltered.map((fieldDefinition) => (
<PreventSelectOnClickContainer
key={fieldDefinition.fieldMetadataId}
> >
{inView ? ( <FieldContext.Provider
value={{
recordId: isCreating ? '' : recordId,
maxWidth: 156,
recoilScopeId:
(isCreating ? 'new' : recordId) +
fieldDefinition.fieldMetadataId,
isLabelIdentifier: false,
fieldDefinition: {
disableTooltip: false,
fieldMetadataId: fieldDefinition.fieldMetadataId,
label: fieldDefinition.label,
iconName: fieldDefinition.iconName,
type: fieldDefinition.type,
metadata: fieldDefinition.metadata,
defaultValue: fieldDefinition.defaultValue,
editButtonIcon: getFieldButtonIcon({
metadata: fieldDefinition.metadata,
type: fieldDefinition.type,
}),
settings: fieldDefinition.settings,
},
useUpdateRecord: useUpdateOneRecordHook,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell /> <RecordInlineCell />
) : ( </FieldContext.Provider>
<StyledRecordInlineCellPlaceholder /> </PreventSelectOnClickContainer>
)} ))}
</FieldContext.Provider> </StyledBoardCardBody>
</PreventSelectOnClickContainer> </AnimatedEaseInOut>
))} </StyledBoardCard>
</StyledBoardCardBody> </InView>
</AnimatedEaseInOut>
</StyledBoardCard>
</StyledBoardCardWrapper> </StyledBoardCardWrapper>
); );
}; };

View File

@ -1,5 +1,5 @@
import { createContext } from 'react';
import { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; import { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
import { createContext } from 'react';
export type RecordTableRowContextProps = { export type RecordTableRowContextProps = {
pathToShowPage: string; pathToShowPage: string;

View File

@ -9,11 +9,10 @@ import { Checkbox } from 'twenty-ui';
const StyledContainer = styled.div` const StyledContainer = styled.div`
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
height: 32px; height: 32px;
justify-content: center; justify-content: center;
min-width: 24px;
`; `;
export const RecordTableCellCheckbox = () => { export const RecordTableCellCheckbox = () => {

View File

@ -16,6 +16,7 @@ const StyledTd = styled.td<{
left?: number; left?: number;
hasRightBorder?: boolean; hasRightBorder?: boolean;
hasBottomBorder?: boolean; hasBottomBorder?: boolean;
width?: number;
}>` }>`
border-bottom: 1px solid border-bottom: 1px solid
${({ borderColor, hasBottomBorder }) => ${({ borderColor, hasBottomBorder }) =>
@ -32,13 +33,12 @@ const StyledTd = styled.td<{
background: ${({ backgroundColor }) => backgroundColor}; background: ${({ backgroundColor }) => backgroundColor};
z-index: ${({ zIndex }) => (isDefined(zIndex) ? zIndex : 'auto')}; z-index: ${({ zIndex }) => (isDefined(zIndex) ? zIndex : 'auto')};
${({ isDragging }) => ${({ isDragging }) =>
isDragging isDragging
? ` ? `
background-color: transparent; background-color: transparent;
border-color: transparent; border-color: transparent;
` `
: ''} : ''}
${({ freezeFirstColumns }) => ${({ freezeFirstColumns }) =>
@ -60,6 +60,7 @@ export const RecordTableTd = ({
left, left,
hasRightBorder = true, hasRightBorder = true,
hasBottomBorder = true, hasBottomBorder = true,
width,
...dragHandleProps ...dragHandleProps
}: { }: {
className?: string; className?: string;
@ -72,6 +73,7 @@ export const RecordTableTd = ({
hasRightBorder?: boolean; hasRightBorder?: boolean;
hasBottomBorder?: boolean; hasBottomBorder?: boolean;
left?: number; left?: number;
width?: number;
} & (Partial<DraggableProvidedDragHandleProps> | null)) => { } & (Partial<DraggableProvidedDragHandleProps> | null)) => {
const { theme } = useContext(ThemeContext); const { theme } = useContext(ThemeContext);
@ -94,6 +96,7 @@ export const RecordTableTd = ({
left={left} left={left}
hasRightBorder={hasRightBorder} hasRightBorder={hasRightBorder}
hasBottomBorder={hasBottomBorder} hasBottomBorder={hasBottomBorder}
width={width}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
{...dragHandleProps} {...dragHandleProps}
> >

View File

@ -175,6 +175,7 @@ export const RecordTableHeaderCell = ({
resizedFieldKey, resizedFieldKey,
resizeFieldOffsetState, resizeFieldOffsetState,
tableColumnsByKey, tableColumnsByKey,
setResizedFieldKey,
tableColumns, tableColumns,
handleColumnsChange, handleColumnsChange,
], ],

View File

@ -1,9 +1,8 @@
import { useContext } from 'react';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd'; import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useContext } from 'react';
export const RecordTableCellsEmpty = () => { export const RecordTableCellsEmpty = () => {
const { isSelected } = useContext(RecordTableRowContext); const { isSelected } = useContext(RecordTableRowContext);

View File

@ -5,10 +5,16 @@ import { RecordTableCell } from '@/object-record/record-table/record-table-cell/
import { RecordTableCellWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellWrapper'; import { RecordTableCellWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellWrapper';
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd'; import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { tableCellWidthsComponentState } from '@/object-record/record-table/states/tableCellWidthsComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordTableCellsVisible = () => { export const RecordTableCellsVisible = () => {
const { isDragging, isSelected } = useContext(RecordTableRowContext); const { isSelected, isDragging } = useContext(RecordTableRowContext);
const [tableCellWidths] = useRecoilComponentStateV2(
tableCellWidthsComponentState,
);
const visibleTableColumns = useRecoilComponentValueV2( const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector, visibleTableColumnsComponentSelector,
@ -19,22 +25,29 @@ export const RecordTableCellsVisible = () => {
return ( return (
<> <>
<RecordTableCellWrapper column={visibleTableColumns[0]} columnIndex={0}> <RecordTableCellWrapper column={visibleTableColumns[0]} columnIndex={0}>
<RecordTableTd isSelected={isSelected}> <RecordTableTd
isSelected={isSelected}
isDragging={isDragging}
width={tableCellWidths[2]}
>
<RecordTableCell /> <RecordTableCell />
</RecordTableTd> </RecordTableTd>
</RecordTableCellWrapper> </RecordTableCellWrapper>
{!isDragging && {tableColumnsAfterFirst.map((column, columnIndex) => (
tableColumnsAfterFirst.map((column, columnIndex) => ( <RecordTableCellWrapper
<RecordTableCellWrapper key={column.fieldMetadataId}
key={column.fieldMetadataId} column={column}
column={column} columnIndex={columnIndex + 1}
columnIndex={columnIndex + 1} >
<RecordTableTd
isSelected={isSelected}
isDragging={isDragging}
width={tableCellWidths[columnIndex + 3] - 1}
> >
<RecordTableTd isSelected={isSelected}> <RecordTableCell />
<RecordTableCell /> </RecordTableTd>
</RecordTableTd> </RecordTableCellWrapper>
</RecordTableCellWrapper> ))}
))}
</> </>
); );
}; };

View File

@ -1,6 +1,6 @@
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { Draggable } from '@hello-pangea/dnd'; import { Draggable } from '@hello-pangea/dnd';
import { ReactNode, useContext, useEffect } from 'react'; import { ReactNode, useContext, useEffect, useRef } from 'react';
import { useInView } from 'react-intersection-observer'; import { useInView } from 'react-intersection-observer';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
@ -9,8 +9,10 @@ import { RecordTableContext } from '@/object-record/record-table/contexts/Record
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr'; import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
import { tableCellWidthsComponentState } from '@/object-record/record-table/states/tableCellWidthsComponentState';
import { RecordTableWithWrappersScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts'; import { RecordTableWithWrappersScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
export const RecordTableRowWrapper = ({ export const RecordTableRowWrapper = ({
recordId, recordId,
@ -23,6 +25,8 @@ export const RecordTableRowWrapper = ({
isPendingRow?: boolean; isPendingRow?: boolean;
children: ReactNode; children: ReactNode;
}) => { }) => {
const trRef = useRef<HTMLTableRowElement>(null);
const { objectMetadataItem } = useContext(RecordTableContext); const { objectMetadataItem } = useContext(RecordTableContext);
const { onIndexRecordsLoaded } = useContext(RecordIndexRootPropsContext); const { onIndexRecordsLoaded } = useContext(RecordIndexRootPropsContext);
@ -44,6 +48,24 @@ export const RecordTableRowWrapper = ({
rootMargin: '1000px', rootMargin: '1000px',
}); });
const [, setTableCellWidths] = useRecoilComponentStateV2(
tableCellWidthsComponentState,
);
useEffect(() => {
if (rowIndex === 0) {
const tdArray = Array.from(
trRef.current?.getElementsByTagName('td') ?? [],
);
const tdWidths = tdArray.map((td) => {
return td.getBoundingClientRect().width;
});
setTableCellWidths(tdWidths);
}
}, [trRef, rowIndex, setTableCellWidths]);
// TODO: find a better way to emit this event // TODO: find a better way to emit this event
useEffect(() => { useEffect(() => {
if (inView) { if (inView) {
@ -56,6 +78,8 @@ export const RecordTableRowWrapper = ({
{(draggableProvided, draggableSnapshot) => ( {(draggableProvided, draggableSnapshot) => (
<RecordTableTr <RecordTableTr
ref={(node) => { ref={(node) => {
// @ts-expect-error - TS doesn't know that node.current is assignable
trRef.current = node;
elementRef(node); elementRef(node);
draggableProvided.innerRef(node); draggableProvided.innerRef(node);
}} }}

View File

@ -0,0 +1,8 @@
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const tableCellWidthsComponentState = createComponentStateV2<number[]>({
key: 'tableCellWidthsComponentState',
defaultValue: [],
componentInstanceContext: RecordTableComponentInstanceContext,
});