mirror of
https://github.com/lingble/twenty.git
synced 2025-11-01 21:27:58 +00:00
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:
@@ -23,14 +23,13 @@ import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
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 {
|
||||
AnimatedEaseInOut,
|
||||
AvatarChipVariant,
|
||||
Checkbox,
|
||||
CheckboxVariant,
|
||||
ChipSize,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
LightIconButton,
|
||||
@@ -147,10 +146,6 @@ const StyledCompactIconContainer = styled.div`
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledRecordInlineCellPlaceholder = styled.div`
|
||||
height: 24px;
|
||||
`;
|
||||
|
||||
export const RecordBoardCard = ({
|
||||
isCreating = false,
|
||||
onCreateSuccess,
|
||||
@@ -240,7 +235,7 @@ export const RecordBoardCard = ({
|
||||
|
||||
const scrollWrapperRef = useContext(RecordBoardScrollWrapperContext);
|
||||
|
||||
const { ref: cardRef, inView } = useInView({
|
||||
const { ref: cardRef } = useInView({
|
||||
root: scrollWrapperRef?.ref.current,
|
||||
rootMargin: '1000px',
|
||||
});
|
||||
@@ -256,126 +251,123 @@ export const RecordBoardCard = ({
|
||||
return (
|
||||
<StyledBoardCardWrapper onContextMenu={handleActionMenuDropdown}>
|
||||
{!isCreating && <RecordValueSetterEffect recordId={recordId} />}
|
||||
<StyledBoardCard
|
||||
ref={cardRef}
|
||||
selected={isCurrentCardSelected}
|
||||
onMouseLeave={onMouseLeaveBoard}
|
||||
onClick={() => {
|
||||
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}
|
||||
<InView>
|
||||
<StyledBoardCard
|
||||
ref={cardRef}
|
||||
selected={isCurrentCardSelected}
|
||||
onMouseLeave={onMouseLeaveBoard}
|
||||
onClick={() => {
|
||||
if (!isCreating) {
|
||||
setIsCurrentCardSelected(!isCurrentCardSelected);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledBoardCardBody>
|
||||
{visibleFieldDefinitionsFiltered.map((fieldDefinition) => (
|
||||
<PreventSelectOnClickContainer
|
||||
key={fieldDefinition.fieldMetadataId}
|
||||
>
|
||||
<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,
|
||||
}}
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!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 />
|
||||
) : (
|
||||
<StyledRecordInlineCellPlaceholder />
|
||||
)}
|
||||
</FieldContext.Provider>
|
||||
</PreventSelectOnClickContainer>
|
||||
))}
|
||||
</StyledBoardCardBody>
|
||||
</AnimatedEaseInOut>
|
||||
</StyledBoardCard>
|
||||
</FieldContext.Provider>
|
||||
</PreventSelectOnClickContainer>
|
||||
))}
|
||||
</StyledBoardCardBody>
|
||||
</AnimatedEaseInOut>
|
||||
</StyledBoardCard>
|
||||
</InView>
|
||||
</StyledBoardCardWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
import { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type RecordTableRowContextProps = {
|
||||
pathToShowPage: string;
|
||||
|
||||
@@ -9,11 +9,10 @@ import { Checkbox } from 'twenty-ui';
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
height: 32px;
|
||||
|
||||
justify-content: center;
|
||||
min-width: 24px;
|
||||
`;
|
||||
|
||||
export const RecordTableCellCheckbox = () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ const StyledTd = styled.td<{
|
||||
left?: number;
|
||||
hasRightBorder?: boolean;
|
||||
hasBottomBorder?: boolean;
|
||||
width?: number;
|
||||
}>`
|
||||
border-bottom: 1px solid
|
||||
${({ borderColor, hasBottomBorder }) =>
|
||||
@@ -32,13 +33,12 @@ const StyledTd = styled.td<{
|
||||
|
||||
background: ${({ backgroundColor }) => backgroundColor};
|
||||
z-index: ${({ zIndex }) => (isDefined(zIndex) ? zIndex : 'auto')};
|
||||
|
||||
${({ isDragging }) =>
|
||||
isDragging
|
||||
? `
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
`
|
||||
`
|
||||
: ''}
|
||||
|
||||
${({ freezeFirstColumns }) =>
|
||||
@@ -60,6 +60,7 @@ export const RecordTableTd = ({
|
||||
left,
|
||||
hasRightBorder = true,
|
||||
hasBottomBorder = true,
|
||||
width,
|
||||
...dragHandleProps
|
||||
}: {
|
||||
className?: string;
|
||||
@@ -72,6 +73,7 @@ export const RecordTableTd = ({
|
||||
hasRightBorder?: boolean;
|
||||
hasBottomBorder?: boolean;
|
||||
left?: number;
|
||||
width?: number;
|
||||
} & (Partial<DraggableProvidedDragHandleProps> | null)) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
@@ -94,6 +96,7 @@ export const RecordTableTd = ({
|
||||
left={left}
|
||||
hasRightBorder={hasRightBorder}
|
||||
hasBottomBorder={hasBottomBorder}
|
||||
width={width}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...dragHandleProps}
|
||||
>
|
||||
|
||||
@@ -175,6 +175,7 @@ export const RecordTableHeaderCell = ({
|
||||
resizedFieldKey,
|
||||
resizeFieldOffsetState,
|
||||
tableColumnsByKey,
|
||||
setResizedFieldKey,
|
||||
tableColumns,
|
||||
handleColumnsChange,
|
||||
],
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const RecordTableCellsEmpty = () => {
|
||||
const { isSelected } = useContext(RecordTableRowContext);
|
||||
|
||||
@@ -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 { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
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';
|
||||
|
||||
export const RecordTableCellsVisible = () => {
|
||||
const { isDragging, isSelected } = useContext(RecordTableRowContext);
|
||||
const { isSelected, isDragging } = useContext(RecordTableRowContext);
|
||||
|
||||
const [tableCellWidths] = useRecoilComponentStateV2(
|
||||
tableCellWidthsComponentState,
|
||||
);
|
||||
|
||||
const visibleTableColumns = useRecoilComponentValueV2(
|
||||
visibleTableColumnsComponentSelector,
|
||||
@@ -19,22 +25,29 @@ export const RecordTableCellsVisible = () => {
|
||||
return (
|
||||
<>
|
||||
<RecordTableCellWrapper column={visibleTableColumns[0]} columnIndex={0}>
|
||||
<RecordTableTd isSelected={isSelected}>
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
isDragging={isDragging}
|
||||
width={tableCellWidths[2]}
|
||||
>
|
||||
<RecordTableCell />
|
||||
</RecordTableTd>
|
||||
</RecordTableCellWrapper>
|
||||
{!isDragging &&
|
||||
tableColumnsAfterFirst.map((column, columnIndex) => (
|
||||
<RecordTableCellWrapper
|
||||
key={column.fieldMetadataId}
|
||||
column={column}
|
||||
columnIndex={columnIndex + 1}
|
||||
{tableColumnsAfterFirst.map((column, columnIndex) => (
|
||||
<RecordTableCellWrapper
|
||||
key={column.fieldMetadataId}
|
||||
column={column}
|
||||
columnIndex={columnIndex + 1}
|
||||
>
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
isDragging={isDragging}
|
||||
width={tableCellWidths[columnIndex + 3] - 1}
|
||||
>
|
||||
<RecordTableTd isSelected={isSelected}>
|
||||
<RecordTableCell />
|
||||
</RecordTableTd>
|
||||
</RecordTableCellWrapper>
|
||||
))}
|
||||
<RecordTableCell />
|
||||
</RecordTableTd>
|
||||
</RecordTableCellWrapper>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
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 { 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 { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
|
||||
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 { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
|
||||
export const RecordTableRowWrapper = ({
|
||||
recordId,
|
||||
@@ -23,6 +25,8 @@ export const RecordTableRowWrapper = ({
|
||||
isPendingRow?: boolean;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const trRef = useRef<HTMLTableRowElement>(null);
|
||||
|
||||
const { objectMetadataItem } = useContext(RecordTableContext);
|
||||
const { onIndexRecordsLoaded } = useContext(RecordIndexRootPropsContext);
|
||||
|
||||
@@ -44,6 +48,24 @@ export const RecordTableRowWrapper = ({
|
||||
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
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
@@ -56,6 +78,8 @@ export const RecordTableRowWrapper = ({
|
||||
{(draggableProvided, draggableSnapshot) => (
|
||||
<RecordTableTr
|
||||
ref={(node) => {
|
||||
// @ts-expect-error - TS doesn't know that node.current is assignable
|
||||
trRef.current = node;
|
||||
elementRef(node);
|
||||
draggableProvided.innerRef(node);
|
||||
}}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
Reference in New Issue
Block a user