Fix safari broken table (#11417)
In this PR: - deprecate usage of useInView (react-intersection-observer) for record table - fixes #11254
This commit is contained in:
@ -3,8 +3,8 @@ import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryP
|
||||
import { UseFindManyRecordsParams } from '@/object-record/hooks/useFetchMoreRecordsWithPagination';
|
||||
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
type UseLazyFetchAllRecordIdsParams<T> = Omit<
|
||||
UseFindManyRecordsParams<T>,
|
||||
|
||||
@ -14,7 +14,6 @@ export const FieldContextProvider = ({
|
||||
fieldMetadataName,
|
||||
fieldPosition,
|
||||
isLabelIdentifier = false,
|
||||
isLabelHidden,
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
customUseUpdateOneObjectHook,
|
||||
@ -25,7 +24,6 @@ export const FieldContextProvider = ({
|
||||
fieldMetadataName: string;
|
||||
fieldPosition: number;
|
||||
isLabelIdentifier?: boolean;
|
||||
isLabelHidden?: boolean;
|
||||
objectNameSingular: string;
|
||||
objectRecordId: string;
|
||||
customUseUpdateOneObjectHook?: RecordUpdateHook;
|
||||
@ -65,7 +63,6 @@ export const FieldContextProvider = ({
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
isLabelIdentifier,
|
||||
isLabelHidden,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
showLabel: true,
|
||||
|
||||
@ -35,7 +35,6 @@ export type GenericFieldContextType = {
|
||||
isReadOnly: boolean;
|
||||
onOpenEditMode?: () => void;
|
||||
onCloseEditMode?: () => void;
|
||||
isLabelHidden?: boolean;
|
||||
};
|
||||
|
||||
export const FieldContext = createContext<GenericFieldContextType>(
|
||||
|
||||
@ -4,12 +4,13 @@ import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlur
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ChipFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ChipFieldDisplay';
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/ChipFieldDisplay',
|
||||
@ -22,18 +23,25 @@ const meta: Meta = {
|
||||
)!;
|
||||
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
<RecordTableComponentInstanceContext.Provider
|
||||
value={{
|
||||
indexIdentifierUrl: () => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: CoreObjectNamePlural.Company,
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
recordIndexId: instanceId,
|
||||
instanceId,
|
||||
onColumnsChange: () => {},
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</RecordIndexContextProvider>
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
indexIdentifierUrl: () => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: CoreObjectNamePlural.Company,
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
recordIndexId: instanceId,
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</RecordIndexContextProvider>
|
||||
</RecordTableComponentInstanceContext.Provider>
|
||||
);
|
||||
},
|
||||
MemoryRouterDecorator,
|
||||
|
||||
@ -8,17 +8,23 @@ import { isFieldText } from '@/object-record/record-field/types/guards/isFieldTe
|
||||
import { useRecordValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useChipFieldDisplay = () => {
|
||||
const {
|
||||
recordId,
|
||||
fieldDefinition,
|
||||
isLabelIdentifier,
|
||||
labelIdentifierLink,
|
||||
isLabelHidden,
|
||||
} = useContext(FieldContext);
|
||||
const { recordId, fieldDefinition, isLabelIdentifier, labelIdentifierLink } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isRecordTableScrolledLeftComponent = useRecoilComponentValueV2(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
);
|
||||
|
||||
const isLabelHidden =
|
||||
isMobile && isLabelIdentifier && !isRecordTableScrolledLeftComponent;
|
||||
|
||||
const { chipGeneratorPerObjectPerField } = useContext(
|
||||
PreComputedChipGeneratorsContext,
|
||||
|
||||
@ -52,7 +52,6 @@ export const RecordTableRecordGroupRows = () => {
|
||||
recordId={recordId}
|
||||
rowIndexForFocus={rowIndex}
|
||||
rowIndexForDrag={rowIndexInGroup}
|
||||
isPendingRow={!isRecordGroupTableSectionToggled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -24,6 +24,7 @@ const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type RecordTableWithWrappersProps = {
|
||||
|
||||
@ -23,9 +23,9 @@ import { RecordTableContextProvider } from '@/object-record/record-table/context
|
||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
|
||||
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { mockPerformance } from './mock';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
|
||||
const RelationFieldValueSetterEffect = () => {
|
||||
const setEntity = useSetRecoilState(
|
||||
@ -110,7 +110,6 @@ const meta: Meta = {
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
}) + mockPerformance.recordId,
|
||||
isSelected: false,
|
||||
isPendingRow: false,
|
||||
inView: true,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -7,7 +7,6 @@ export type RecordTableRowContextValue = {
|
||||
rowIndex: number;
|
||||
isSelected: boolean;
|
||||
inView: boolean;
|
||||
isPendingRow?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@ -33,6 +33,8 @@ export const RecordTableBodyLoading = () => {
|
||||
}}
|
||||
>
|
||||
<RecordTableTr
|
||||
recordId={`${rowIndex}`}
|
||||
focusIndex={0}
|
||||
isDragging={false}
|
||||
data-testid={`row-id-${rowIndex}`}
|
||||
data-selectable-id={`row-id-${rowIndex}`}
|
||||
|
||||
@ -5,9 +5,6 @@ import { RecordTableCellBaseContainer } from '@/object-record/record-table/recor
|
||||
import { RecordTableCellSoftFocusMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode';
|
||||
|
||||
import { RecordTableCellSoftFocusModeHotkeysSetterEffect } from '@/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusModeHotkeysSetterEffect';
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode';
|
||||
import { RecordTableCellEditMode } from './RecordTableCellEditMode';
|
||||
|
||||
@ -26,17 +23,13 @@ export const RecordTableCellContainer = ({
|
||||
}: RecordTableCellContainerProps) => {
|
||||
const { hasSoftFocus, isInEditMode } = useContext(RecordTableCellContext);
|
||||
|
||||
const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState);
|
||||
|
||||
return (
|
||||
<RecordTableCellBaseContainer>
|
||||
{isInEditMode ? (
|
||||
<RecordTableCellEditMode>{editModeContent}</RecordTableCellEditMode>
|
||||
) : hasSoftFocus ? (
|
||||
<>
|
||||
{currentHotkeyScope.scope === TableHotkeyScope.TableSoftFocus && (
|
||||
<RecordTableCellSoftFocusModeHotkeysSetterEffect />
|
||||
)}
|
||||
<RecordTableCellSoftFocusModeHotkeysSetterEffect />
|
||||
<RecordTableCellSoftFocusMode
|
||||
editModeContent={editModeContent}
|
||||
nonEditModeContent={nonEditModeContent}
|
||||
|
||||
@ -14,11 +14,8 @@ import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/co
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ReactNode, useContext } from 'react';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
import { isRecordTableScrolledLeftComponentState } from '../../states/isRecordTableScrolledLeftComponentState';
|
||||
|
||||
export const RecordTableCellFieldContext = ({
|
||||
children,
|
||||
@ -31,16 +28,6 @@ export const RecordTableCellFieldContext = ({
|
||||
const { recordId, isReadOnly: isTableRowReadOnly } =
|
||||
useRecordTableRowContextOrThrow();
|
||||
const updateRecord = useContext(RecordUpdateContext);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const isRecordTableScrolledLeft = useRecoilComponentValueV2(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
);
|
||||
|
||||
const isLabelHidden =
|
||||
isMobile &&
|
||||
columnDefinition?.isLabelIdentifier &&
|
||||
!isRecordTableScrolledLeft;
|
||||
|
||||
const computedHotkeyScope = (
|
||||
columnDefinition: ColumnDefinition<FieldMetadata>,
|
||||
@ -93,7 +80,6 @@ export const RecordTableCellFieldContext = ({
|
||||
objectMetadataItem,
|
||||
}),
|
||||
displayedMaxRows: 1,
|
||||
isLabelHidden,
|
||||
isReadOnly: isFieldReadOnly,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -1,27 +1,21 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { useRecordTableRowDraggableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { css } from '@emotion/react';
|
||||
import { IconListViewGrip } from 'twenty-ui/input';
|
||||
|
||||
export const TABLE_CELL_GRIP_WIDTH = '16px';
|
||||
|
||||
const StyledContainer = styled.div<{ isPendingRow?: boolean }>`
|
||||
const StyledContainer = styled.div`
|
||||
height: 32px;
|
||||
width: ${TABLE_CELL_GRIP_WIDTH};
|
||||
border-color: transparent;
|
||||
cursor: grab;
|
||||
display: flex;
|
||||
${({ isPendingRow }) =>
|
||||
!isPendingRow
|
||||
? css`
|
||||
&:hover .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
: ''};
|
||||
|
||||
&:hover .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
z-index: 200;
|
||||
`;
|
||||
@ -32,8 +26,6 @@ const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
|
||||
`;
|
||||
|
||||
export const RecordTableCellGrip = () => {
|
||||
const { isPendingRow } = useRecordTableRowContextOrThrow();
|
||||
|
||||
const { dragHandleProps, isDragging } =
|
||||
useRecordTableRowDraggableContextOrThrow();
|
||||
|
||||
@ -45,7 +37,7 @@ export const RecordTableCellGrip = () => {
|
||||
hasRightBorder={false}
|
||||
hasBottomBorder={false}
|
||||
>
|
||||
<StyledContainer isPendingRow={isPendingRow}>
|
||||
<StyledContainer>
|
||||
<StyledIconWrapper className="icon" isDragging={isDragging}>
|
||||
<IconListViewGrip />
|
||||
</StyledIconWrapper>
|
||||
|
||||
@ -12,9 +12,12 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
export const RecordTableCellSoftFocusModeHotkeysSetterEffect = () => {
|
||||
const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState);
|
||||
|
||||
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||
const { isReadOnly } = useContext(FieldContext);
|
||||
|
||||
@ -29,10 +32,14 @@ export const RecordTableCellSoftFocusModeHotkeysSetterEffect = () => {
|
||||
const clearField = useClearField();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentHotkeyScope.scope !== TableHotkeyScope.TableSoftFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSoftFocusUsingMouse) {
|
||||
scrollRef.current?.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}, [isSoftFocusUsingMouse]);
|
||||
}, [currentHotkeyScope.scope, isSoftFocusUsingMouse]);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Backspace, Key.Delete],
|
||||
|
||||
@ -9,7 +9,6 @@ export const recordTableRowContextValue: RecordTableRowContextValue = {
|
||||
recordId: 'recordId',
|
||||
pathToShowPage: '/',
|
||||
objectNameSingular: 'objectNameSingular',
|
||||
isPendingRow: false,
|
||||
inView: true,
|
||||
};
|
||||
|
||||
|
||||
@ -65,8 +65,9 @@ export const RecordTableActionRow = ({
|
||||
|
||||
return (
|
||||
<StyledRecordTableDraggableTr
|
||||
draggableId={draggableId}
|
||||
recordId={draggableId}
|
||||
draggableIndex={draggableIndex}
|
||||
focusIndex={draggableIndex}
|
||||
onClick={onClick}
|
||||
isDragDisabled
|
||||
>
|
||||
|
||||
@ -1,55 +1,44 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { Draggable, DraggableId } from '@hello-pangea/dnd';
|
||||
import { ReactNode, forwardRef } from 'react';
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
|
||||
import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
|
||||
import styled from '@emotion/styled';
|
||||
import { RecordTableTrEffect } from '@/object-record/record-table/record-table-row/components/RecordTableTrEffect';
|
||||
|
||||
type RecordTableDraggableTrProps = {
|
||||
className?: string;
|
||||
draggableId: DraggableId;
|
||||
recordId: string;
|
||||
draggableIndex: number;
|
||||
focusIndex: number;
|
||||
isDragDisabled?: boolean;
|
||||
onClick?: (event: React.MouseEvent<HTMLTableRowElement>) => void;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const StyledAbsoluteInViewContainer = styled.td`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
`;
|
||||
export const RecordTableDraggableTr = ({
|
||||
className,
|
||||
recordId,
|
||||
draggableIndex,
|
||||
focusIndex,
|
||||
isDragDisabled,
|
||||
onClick,
|
||||
children,
|
||||
}: RecordTableDraggableTrProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
export const RecordTableDraggableTr = forwardRef<
|
||||
HTMLTableCellElement,
|
||||
RecordTableDraggableTrProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
draggableId,
|
||||
draggableIndex,
|
||||
isDragDisabled,
|
||||
onClick,
|
||||
children,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
draggableId={draggableId}
|
||||
index={draggableIndex}
|
||||
isDragDisabled={isDragDisabled}
|
||||
>
|
||||
{(draggableProvided, draggableSnapshot) => (
|
||||
return (
|
||||
<Draggable
|
||||
draggableId={recordId}
|
||||
index={draggableIndex}
|
||||
isDragDisabled={isDragDisabled}
|
||||
>
|
||||
{(draggableProvided, draggableSnapshot) => (
|
||||
<>
|
||||
<RecordTableTrEffect recordId={recordId} />
|
||||
<RecordTableTr
|
||||
recordId={recordId}
|
||||
focusIndex={focusIndex}
|
||||
ref={draggableProvided.innerRef}
|
||||
className={className}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
@ -64,8 +53,9 @@ export const RecordTableDraggableTr = forwardRef<
|
||||
: 'transparent',
|
||||
}}
|
||||
isDragging={draggableSnapshot.isDragging}
|
||||
data-testid={`row-id-${draggableId}`}
|
||||
data-selectable-id={draggableId}
|
||||
data-testid={`row-id-${recordId}`}
|
||||
data-virtualized-id={recordId}
|
||||
data-selectable-id={recordId}
|
||||
onClick={onClick}
|
||||
>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
@ -75,13 +65,10 @@ export const RecordTableDraggableTr = forwardRef<
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<StyledAbsoluteInViewContainer
|
||||
ref={ref}
|
||||
></StyledAbsoluteInViewContainer>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableTr>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
},
|
||||
);
|
||||
</>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,33 +3,30 @@ import { RecordTableCellCheckbox } from '@/object-record/record-table/record-tab
|
||||
import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
|
||||
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
||||
import { RecordTableCells } from '@/object-record/record-table/record-table-row/components/RecordTableCells';
|
||||
import { RecordTableRowWrapper } from '@/object-record/record-table/record-table-row/components/RecordTableRowWrapper';
|
||||
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
||||
|
||||
type RecordTableRowProps = {
|
||||
recordId: string;
|
||||
rowIndexForFocus: number;
|
||||
rowIndexForDrag: number;
|
||||
isPendingRow?: boolean;
|
||||
};
|
||||
|
||||
export const RecordTableRow = ({
|
||||
recordId,
|
||||
rowIndexForFocus,
|
||||
rowIndexForDrag,
|
||||
isPendingRow,
|
||||
}: RecordTableRowProps) => {
|
||||
return (
|
||||
<RecordTableRowWrapper
|
||||
<RecordTableDraggableTr
|
||||
recordId={recordId}
|
||||
rowIndexForFocus={rowIndexForFocus}
|
||||
rowIndexForDrag={rowIndexForDrag}
|
||||
isPendingRow={isPendingRow}
|
||||
draggableIndex={rowIndexForDrag}
|
||||
focusIndex={rowIndexForFocus}
|
||||
>
|
||||
<RecordTableCellGrip />
|
||||
<RecordTableCellCheckbox />
|
||||
<RecordTableCells />
|
||||
<RecordTableLastEmptyCell />
|
||||
<RecordValueSetterEffect recordId={recordId} />
|
||||
</RecordTableRowWrapper>
|
||||
</RecordTableDraggableTr>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
|
||||
type RecordTableRowWrapperProps = {
|
||||
recordId: string;
|
||||
rowIndexForFocus: number;
|
||||
rowIndexForDrag: number;
|
||||
isPendingRow?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const RecordTableRowWrapper = ({
|
||||
recordId,
|
||||
rowIndexForFocus,
|
||||
rowIndexForDrag,
|
||||
isPendingRow,
|
||||
children,
|
||||
}: RecordTableRowWrapperProps) => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
const currentRowSelected = useRecoilComponentFamilyValueV2(
|
||||
isRowSelectedComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||
|
||||
const { ref: elementRef, inView } = useInView({
|
||||
root: scrollWrapperHTMLElement,
|
||||
rootMargin: '1000px',
|
||||
});
|
||||
|
||||
// TODO: find a better way to emit this event
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
onIndexRecordsLoaded?.();
|
||||
}
|
||||
}, [inView, onIndexRecordsLoaded]);
|
||||
|
||||
return (
|
||||
<RecordTableDraggableTr
|
||||
ref={elementRef}
|
||||
key={recordId}
|
||||
draggableId={recordId}
|
||||
draggableIndex={rowIndexForDrag}
|
||||
isDragDisabled={isPendingRow}
|
||||
>
|
||||
<RecordTableRowContextProvider
|
||||
value={{
|
||||
recordId,
|
||||
rowIndex: rowIndexForFocus,
|
||||
pathToShowPage:
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
}) + recordId,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
isSelected: currentRowSelected,
|
||||
isPendingRow,
|
||||
inView,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecordTableRowContextProvider>
|
||||
</RecordTableDraggableTr>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,11 @@
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { isRowVisibleComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowVisibleComponentFamilyState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode, forwardRef } from 'react';
|
||||
|
||||
const StyledTr = styled.tr<{ isDragging: boolean }>`
|
||||
position: relative;
|
||||
@ -9,4 +16,51 @@ const StyledTr = styled.tr<{ isDragging: boolean }>`
|
||||
transition: border-left-color 0.2s ease-in-out;
|
||||
`;
|
||||
|
||||
export const RecordTableTr = StyledTr;
|
||||
type RecordTableTrProps = {
|
||||
children: ReactNode;
|
||||
recordId: string;
|
||||
focusIndex: number;
|
||||
isDragging?: boolean;
|
||||
} & React.ComponentProps<typeof StyledTr>;
|
||||
|
||||
export const RecordTableTr = forwardRef<
|
||||
HTMLTableRowElement,
|
||||
RecordTableTrProps
|
||||
>(({ children, recordId, focusIndex, isDragging = false, ...props }, ref) => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const currentRowSelected = useRecoilComponentFamilyValueV2(
|
||||
isRowSelectedComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
const isRowVisible = useRecoilComponentFamilyValueV2(
|
||||
isRowVisibleComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
return (
|
||||
<RecordTableRowContextProvider
|
||||
value={{
|
||||
recordId: recordId,
|
||||
rowIndex: focusIndex,
|
||||
pathToShowPage:
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
}) + recordId,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
isSelected: currentRowSelected,
|
||||
inView: isRowVisible,
|
||||
}}
|
||||
>
|
||||
<StyledTr
|
||||
data-virtualized-id={recordId}
|
||||
isDragging={isDragging}
|
||||
ref={ref}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</StyledTr>
|
||||
</RecordTableRowContextProvider>
|
||||
);
|
||||
});
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { isRowVisibleComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowVisibleComponentFamilyState';
|
||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type RecordTableTrEffectProps = {
|
||||
recordId: string;
|
||||
};
|
||||
|
||||
export const RecordTableTrEffect = ({ recordId }: RecordTableTrEffectProps) => {
|
||||
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||
|
||||
const setIsRowVisible = useSetRecoilComponentFamilyStateV2(
|
||||
isRowVisibleComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const options = {
|
||||
root: scrollWrapperHTMLElement,
|
||||
rootMargin: '1000px',
|
||||
threshold: 0.1,
|
||||
};
|
||||
|
||||
const callback = (entries: IntersectionObserverEntry[]) => {
|
||||
entries.forEach((entry) => {
|
||||
const isIntersecting = entry.isIntersecting;
|
||||
|
||||
if (isIntersecting) {
|
||||
onIndexRecordsLoaded?.();
|
||||
setIsRowVisible(true);
|
||||
}
|
||||
|
||||
if (!isIntersecting) {
|
||||
setIsRowVisible(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(callback, options);
|
||||
|
||||
observer.observe(
|
||||
document.querySelector(
|
||||
`[data-virtualized-id="${recordId}"]`,
|
||||
) as HTMLElement,
|
||||
);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [
|
||||
onIndexRecordsLoaded,
|
||||
recordId,
|
||||
scrollWrapperHTMLElement,
|
||||
setIsRowVisible,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||
|
||||
export const isRowVisibleComponentFamilyState = createComponentFamilyStateV2<
|
||||
boolean,
|
||||
string
|
||||
>({
|
||||
key: 'isRowVisibleComponentFamilyState',
|
||||
defaultValue: true,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
@ -20,7 +20,6 @@ import { flushSync } from 'react-dom';
|
||||
import { Keys } from 'react-hotkeys-hook';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
import { useDropdown } from '../hooks/useDropdown';
|
||||
|
||||
const StyledDropdownFallbackAnchor = styled.div`
|
||||
@ -115,8 +114,6 @@ export const Dropdown = ({
|
||||
dropdownHotkeyScope,
|
||||
);
|
||||
|
||||
await sleep(100);
|
||||
|
||||
toggleDropdown();
|
||||
onClickOutside?.();
|
||||
},
|
||||
|
||||
@ -14,6 +14,8 @@ const StyledScrollWrapper = styled.div`
|
||||
&.scroll-wrapper-y-enabled {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
Reference in New Issue
Block a user