fix: can't properly drag and drop in empty record group (#9039)

Fix #8968 

Fix issue with drag and drop when record group are empty.
Happy to discuss the implementation, as it's limited to the
`@hello-pangea/dnd` api.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M 2024-12-13 09:37:23 +01:00 committed by GitHub
parent d56c815897
commit 07aaf0801c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 349 additions and 242 deletions

View File

@ -1,14 +1,6 @@
import React, { Ref, RefCallback } from 'react'; import { Ref, RefCallback } from 'react';
import { isFunction } from '@sniptt/guards'; import { combineRefs } from '~/utils/combineRefs';
export const useCombinedRefs = export const useCombinedRefs = <T>(
<T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> => ...refs: (Ref<T> | undefined)[]
(node: T) => { ): RefCallback<T> => combineRefs<T>(...refs);
for (const ref of refs) {
if (isFunction(ref)) {
ref(node);
} else if (ref !== null && ref !== undefined) {
(ref as React.MutableRefObject<T | null>).current = node;
}
}
};

View File

@ -14,13 +14,14 @@ import {
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator'; import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext'; import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext';
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 { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { mockPerformance } from './mock'; import { mockPerformance } from './mock';
@ -87,7 +88,7 @@ const meta: Meta = {
onCellMouseEnter: () => {}, onCellMouseEnter: () => {},
}} }}
> >
<RecordTableRowContext.Provider <RecordTableRowContextProvider
value={{ value={{
objectNameSingular: objectNameSingular:
mockPerformance.entityValue.__typename.toLocaleLowerCase(), mockPerformance.entityValue.__typename.toLocaleLowerCase(),
@ -99,43 +100,48 @@ const meta: Meta = {
mockPerformance.entityValue.__typename.toLocaleLowerCase(), mockPerformance.entityValue.__typename.toLocaleLowerCase(),
}) + mockPerformance.recordId, }) + mockPerformance.recordId,
isSelected: false, isSelected: false,
isDragging: false,
dragHandleProps: null,
inView: true,
isPendingRow: false, isPendingRow: false,
inView: true,
}} }}
> >
<RecordTableCellContext.Provider <RecordTableRowDraggableContextProvider
value={{ value={{
columnDefinition: mockPerformance.fieldDefinition, isDragging: false,
columnIndex: 0, dragHandleProps: null,
cellPosition: { row: 0, column: 0 },
hasSoftFocus: false,
isInEditMode: false,
}} }}
> >
<FieldContext.Provider <RecordTableCellContext.Provider
value={{ value={{
recordId: mockPerformance.recordId, columnDefinition: mockPerformance.fieldDefinition,
basePathToShowPage: '/object-record/', columnIndex: 0,
isLabelIdentifier: false, cellPosition: { row: 0, column: 0 },
fieldDefinition: { hasSoftFocus: false,
...mockPerformance.fieldDefinition, isInEditMode: false,
},
hotkeyScope: 'hotkey-scope',
}} }}
> >
<RelationFieldValueSetterEffect /> <FieldContext.Provider
<table> value={{
<tbody> recordId: mockPerformance.recordId,
<tr> basePathToShowPage: '/object-record/',
<Story /> isLabelIdentifier: false,
</tr> fieldDefinition: {
</tbody> ...mockPerformance.fieldDefinition,
</table> },
</FieldContext.Provider> hotkeyScope: 'hotkey-scope',
</RecordTableCellContext.Provider> }}
</RecordTableRowContext.Provider> >
<RelationFieldValueSetterEffect />
<table>
<tbody>
<tr>
<Story />
</tr>
</tbody>
</table>
</FieldContext.Provider>
</RecordTableCellContext.Provider>
</RecordTableRowDraggableContextProvider>
</RecordTableRowContextProvider>
</RecordTableBodyContextProvider> </RecordTableBodyContextProvider>
</RecordTableComponentInstance> </RecordTableComponentInstance>
</RecordTableContextProvider> </RecordTableContextProvider>

View File

@ -4,7 +4,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
export type RecordTableCellContextProps = { export type RecordTableCellContextValue = {
columnDefinition: ColumnDefinition<FieldMetadata>; columnDefinition: ColumnDefinition<FieldMetadata>;
columnIndex: number; columnIndex: number;
isInEditMode: boolean; isInEditMode: boolean;
@ -13,4 +13,4 @@ export type RecordTableCellContextProps = {
}; };
export const RecordTableCellContext = export const RecordTableCellContext =
createContext<RecordTableCellContextProps>({} as RecordTableCellContextProps); createContext<RecordTableCellContextValue>({} as RecordTableCellContextValue);

View File

@ -1,19 +1,14 @@
import { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; import { createRequiredContext } from '~/utils/createRequiredContext';
import { createContext } from 'react';
export type RecordTableRowContextProps = { export type RecordTableRowContextValue = {
pathToShowPage: string; pathToShowPage: string;
objectNameSingular: string; objectNameSingular: string;
recordId: string; recordId: string;
rowIndex: number; rowIndex: number;
isSelected: boolean; isSelected: boolean;
inView: boolean;
isPendingRow?: boolean; isPendingRow?: boolean;
isDragging: boolean;
dragHandleProps: DraggableProvidedDragHandleProps | null;
inView?: boolean;
isDragDisabled?: boolean;
}; };
export const RecordTableRowContext = createContext<RecordTableRowContextProps>( export const [RecordTableRowContextProvider, useRecordTableRowContextOrThrow] =
{} as RecordTableRowContextProps, createRequiredContext<RecordTableRowContextValue>('RecordTableRowContext');
);

View File

@ -0,0 +1,14 @@
import { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
import { createRequiredContext } from '~/utils/createRequiredContext';
export type RecordTableRowDraggableContextValue = {
isDragging: boolean;
dragHandleProps: DraggableProvidedDragHandleProps | null;
};
export const [
RecordTableRowDraggableContextProvider,
useRecordTableRowDraggableContextOrThrow,
] = createRequiredContext<RecordTableRowDraggableContextValue>(
'RecordTableRowDraggableContext',
);

View File

@ -1,3 +1,5 @@
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox'; import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip'; import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
import { RecordTableCellLoading } from '@/object-record/record-table/record-table-cell/components/RecordTableCellLoading'; import { RecordTableCellLoading } from '@/object-record/record-table/record-table-cell/components/RecordTableCellLoading';
@ -13,18 +15,36 @@ export const RecordTableBodyLoading = () => {
return ( return (
<tbody> <tbody>
{Array.from({ length: 8 }).map((_, rowIndex) => ( {Array.from({ length: 8 }).map((_, rowIndex) => (
<RecordTableTr <RecordTableRowContextProvider
isDragging={false}
data-testid={`row-id-${rowIndex}`}
data-selectable-id={`row-id-${rowIndex}`}
key={rowIndex} key={rowIndex}
value={{
pathToShowPage: '',
objectNameSingular: '',
recordId: `${rowIndex}`,
rowIndex,
isSelected: false,
inView: true,
}}
> >
<RecordTableCellGrip /> <RecordTableRowDraggableContextProvider
<RecordTableCellCheckbox /> value={{
{visibleTableColumns.map((column) => ( dragHandleProps: {} as any,
<RecordTableCellLoading key={column.fieldMetadataId} /> isDragging: false,
))} }}
</RecordTableTr> >
<RecordTableTr
isDragging={false}
data-testid={`row-id-${rowIndex}`}
data-selectable-id={`row-id-${rowIndex}`}
>
<RecordTableCellGrip />
<RecordTableCellCheckbox />
{visibleTableColumns.map((column) => (
<RecordTableCellLoading key={column.fieldMetadataId} />
))}
</RecordTableTr>
</RecordTableRowDraggableContextProvider>
</RecordTableRowContextProvider>
))} ))}
</tbody> </tbody>
); );

View File

@ -35,9 +35,9 @@ export const RecordTableRecordGroupsBody = () => {
key={recordGroupId} key={recordGroupId}
recordGroupId={recordGroupId} recordGroupId={recordGroupId}
> >
{index > 0 && <RecordTableRecordGroupEmptyRow />}
<RecordGroupContext.Provider value={{ recordGroupId }}> <RecordGroupContext.Provider value={{ recordGroupId }}>
<RecordTableBodyDroppable recordGroupId={recordGroupId}> <RecordTableBodyDroppable recordGroupId={recordGroupId}>
{index > 0 && <RecordTableRecordGroupEmptyRow />}
<RecordTableRecordGroupSection /> <RecordTableRecordGroupSection />
<RecordTableRecordGroupRows /> <RecordTableRecordGroupRows />
</RecordTableBodyDroppable> </RecordTableBodyDroppable>

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useCallback, useContext } from 'react'; import { useCallback } from 'react';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowContextOrThrow } 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 { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected'; import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
import { Checkbox } from 'twenty-ui'; import { Checkbox } from 'twenty-ui';
@ -16,7 +16,7 @@ const StyledContainer = styled.div`
`; `;
export const RecordTableCellCheckbox = () => { export const RecordTableCellCheckbox = () => {
const { isSelected } = useContext(RecordTableRowContext); const { isSelected } = useRecordTableRowContextOrThrow();
const { setCurrentRowSelected } = useSetCurrentRowSelected(); const { setCurrentRowSelected } = useSetCurrentRowSelected();

View File

@ -7,7 +7,7 @@ import { isFieldSelect } from '@/object-record/record-field/types/guards/isField
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext'; import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope'; import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
@ -22,7 +22,7 @@ export const RecordTableCellFieldContextWrapper = ({
const { columnDefinition } = useContext(RecordTableCellContext); const { columnDefinition } = useContext(RecordTableCellContext);
const { recordId, pathToShowPage } = useContext(RecordTableRowContext); const { recordId, pathToShowPage } = useRecordTableRowContextOrThrow();
const updateRecord = useContext(RecordUpdateContext); const updateRecord = useContext(RecordUpdateContext);

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useContext } from 'react';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; 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 { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import { IconListViewGrip } from 'twenty-ui'; import { IconListViewGrip } from 'twenty-ui';
@ -30,9 +30,10 @@ const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
`; `;
export const RecordTableCellGrip = () => { export const RecordTableCellGrip = () => {
const { dragHandleProps, isDragging, isPendingRow } = useContext( const { isPendingRow } = useRecordTableRowContextOrThrow();
RecordTableRowContext,
); const { dragHandleProps, isDragging } =
useRecordTableRowDraggableContextOrThrow();
return ( return (
<RecordTableTd <RecordTableTd

View File

@ -1,8 +1,8 @@
import { useContext, useMemo } from 'react'; import { useMemo } from 'react';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper'; import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
import { isSoftFocusOnTableCellComponentFamilyState } from '@/object-record/record-table/states/isSoftFocusOnTableCellComponentFamilyState'; import { isSoftFocusOnTableCellComponentFamilyState } from '@/object-record/record-table/states/isSoftFocusOnTableCellComponentFamilyState';
import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState'; import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
@ -19,7 +19,7 @@ export const RecordTableCellWrapper = ({
columnIndex: number; columnIndex: number;
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
const { rowIndex } = useContext(RecordTableRowContext); const { rowIndex } = useRecordTableRowContextOrThrow();
const currentTableCellPosition: TableCellPosition = useMemo( const currentTableCellPosition: TableCellPosition = useMemo(
() => ({ () => ({

View File

@ -1,10 +1,8 @@
import { useContext } from 'react'; import { useRecordTableRowContextOrThrow } 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';
export const RecordTableLastEmptyCell = () => { export const RecordTableLastEmptyCell = () => {
const { isSelected } = useContext(RecordTableRowContext); const { isSelected } = useRecordTableRowContextOrThrow();
return ( return (
<> <>

View File

@ -1,20 +1,24 @@
import { RecordTableCellContextProps } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContextValue } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContextProps } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContextValue } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { RecordTableRowDraggableContextValue } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { FieldMetadataType } from '~/generated/graphql';
export const recordTableRow: RecordTableRowContextProps = { export const recordTableRowContextValue: RecordTableRowContextValue = {
rowIndex: 2, rowIndex: 2,
isSelected: false, isSelected: false,
recordId: 'recordId', recordId: 'recordId',
pathToShowPage: '/', pathToShowPage: '/',
objectNameSingular: 'objectNameSingular', objectNameSingular: 'objectNameSingular',
dragHandleProps: {} as any,
isDragging: false,
inView: true,
isPendingRow: false, isPendingRow: false,
inView: true,
}; };
export const recordTableCell: RecordTableCellContextProps = { export const recordTableRowDraggableContextValue: RecordTableRowDraggableContextValue = {
dragHandleProps: {} as any,
isDragging: false,
};
export const recordTableCellContextValue: RecordTableCellContextValue = {
columnIndex: 3, columnIndex: 3,
columnDefinition: { columnDefinition: {
size: 1, size: 1,

View File

@ -1,19 +1,28 @@
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { recordTableCell, recordTableRow } from '../__mocks__/cell'; import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import {
recordTableCellContextValue,
recordTableRowContextValue,
recordTableRowDraggableContextValue,
} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useCurrentTableCellPosition } from '../useCurrentCellPosition'; import { useCurrentTableCellPosition } from '../useCurrentCellPosition';
describe('useCurrentTableCellPosition', () => { describe('useCurrentTableCellPosition', () => {
it('should return the current table cell position', () => { it('should return the current table cell position', () => {
const wrapper = ({ children }: { children: React.ReactNode }) => ( const wrapper = ({ children }: { children: React.ReactNode }) => (
<RecordTableRowContext.Provider value={recordTableRow}> <RecordTableRowContextProvider value={recordTableRowContextValue}>
<RecordTableCellContext.Provider value={recordTableCell}> <RecordTableRowDraggableContextProvider
{children} value={recordTableRowDraggableContextValue}
</RecordTableCellContext.Provider> >
</RecordTableRowContext.Provider> <RecordTableCellContext.Provider value={recordTableCellContextValue}>
{children}
</RecordTableCellContext.Provider>
</RecordTableRowDraggableContextProvider>
</RecordTableRowContextProvider>
); );
const { result } = renderHook(() => useCurrentTableCellPosition(), { const { result } = renderHook(() => useCurrentTableCellPosition(), {

View File

@ -3,10 +3,12 @@ import { RecoilRoot } from 'recoil';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { import {
recordTableCell, recordTableCellContextValue,
recordTableRow, recordTableRowContextValue,
recordTableRowDraggableContextValue,
} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell'; } from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useIsSoftFocusOnCurrentTableCell } from '@/object-record/record-table/record-table-cell/hooks/useIsSoftFocusOnCurrentTableCell'; import { useIsSoftFocusOnCurrentTableCell } from '@/object-record/record-table/record-table-cell/hooks/useIsSoftFocusOnCurrentTableCell';
@ -16,11 +18,15 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
recordTableId="scopeId" recordTableId="scopeId"
onColumnsChange={jest.fn()} onColumnsChange={jest.fn()}
> >
<RecordTableRowContext.Provider value={recordTableRow}> <RecordTableRowContextProvider value={recordTableRowContextValue}>
<RecordTableCellContext.Provider value={recordTableCell}> <RecordTableRowDraggableContextProvider
{children} value={recordTableRowDraggableContextValue}
</RecordTableCellContext.Provider> >
</RecordTableRowContext.Provider> <RecordTableCellContext.Provider value={recordTableCellContextValue}>
{children}
</RecordTableCellContext.Provider>
</RecordTableRowDraggableContextProvider>
</RecordTableRowContextProvider>
</RecordTableComponentInstance> </RecordTableComponentInstance>
</RecoilRoot> </RecoilRoot>
); );

View File

@ -3,10 +3,12 @@ import { CallbackInterface, RecoilRoot } from 'recoil';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { import {
recordTableCell, recordTableCellContextValue,
recordTableRow, recordTableRowContextValue,
recordTableRowDraggableContextValue,
} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell'; } from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useMoveSoftFocusToCurrentCellOnHover } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCurrentCellOnHover'; import { useMoveSoftFocusToCurrentCellOnHover } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCurrentCellOnHover';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
@ -80,11 +82,15 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
recordTableId="scopeId" recordTableId="scopeId"
onColumnsChange={jest.fn()} onColumnsChange={jest.fn()}
> >
<RecordTableRowContext.Provider value={recordTableRow}> <RecordTableRowContextProvider value={recordTableRowContextValue}>
<RecordTableCellContext.Provider value={recordTableCell}> <RecordTableRowDraggableContextProvider
{children} value={recordTableRowDraggableContextValue}
</RecordTableCellContext.Provider> >
</RecordTableRowContext.Provider> <RecordTableCellContext.Provider value={recordTableCellContextValue}>
{children}
</RecordTableCellContext.Provider>
</RecordTableRowDraggableContextProvider>
</RecordTableRowContextProvider>
</RecordTableComponentInstance> </RecordTableComponentInstance>
</RecoilRoot> </RecoilRoot>
); );

View File

@ -8,10 +8,12 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { import {
recordTableCell, recordTableCellContextValue,
recordTableRow, recordTableRowContextValue,
recordTableRowDraggableContextValue,
} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell'; } from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup'; import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup';
import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState'; import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState';
@ -55,13 +57,17 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
isLabelIdentifier: false, isLabelIdentifier: false,
}} }}
> >
<RecordTableRowContext.Provider value={recordTableRow}> <RecordTableRowContextProvider value={recordTableRowContextValue}>
<RecordTableCellContext.Provider <RecordTableRowDraggableContextProvider
value={{ ...recordTableCell, columnIndex: 0 }} value={recordTableRowDraggableContextValue}
> >
{children} <RecordTableCellContext.Provider
</RecordTableCellContext.Provider> value={{ ...recordTableCellContextValue, columnIndex: 0 }}
</RecordTableRowContext.Provider> >
{children}
</RecordTableCellContext.Provider>
</RecordTableRowDraggableContextProvider>
</RecordTableRowContextProvider>
</FieldContext.Provider> </FieldContext.Provider>
</RecordTableContextProvider> </RecordTableContextProvider>
</RecordTableComponentInstance> </RecordTableComponentInstance>

View File

@ -8,10 +8,12 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { import {
recordTableCell, recordTableCellContextValue,
recordTableRow, recordTableRowContextValue,
recordTableRowDraggableContextValue,
} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell'; } from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup'; import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup';
import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState'; import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState';
@ -54,13 +56,17 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
isLabelIdentifier: false, isLabelIdentifier: false,
}} }}
> >
<RecordTableRowContext.Provider value={recordTableRow}> <RecordTableRowContextProvider value={recordTableRowContextValue}>
<RecordTableCellContext.Provider <RecordTableRowDraggableContextProvider
value={{ ...recordTableCell, columnIndex: 0 }} value={recordTableRowDraggableContextValue}
> >
{children} <RecordTableCellContext.Provider
</RecordTableCellContext.Provider> value={{ ...recordTableCellContextValue, columnIndex: 0 }}
</RecordTableRowContext.Provider> >
{children}
</RecordTableCellContext.Provider>
</RecordTableRowDraggableContextProvider>
</RecordTableRowContextProvider>
</FieldContext.Provider> </FieldContext.Provider>
</RecordTableContextProvider> </RecordTableContextProvider>
</RecordTableComponentInstance> </RecordTableComponentInstance>

View File

@ -5,7 +5,7 @@ import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useI
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext'; import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition'; import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@ -30,9 +30,9 @@ export type OpenTableCellArgs = {
export const useOpenRecordTableCellFromCell = () => { export const useOpenRecordTableCellFromCell = () => {
const customCellHotkeyScope = useContext(CellHotkeyScopeContext); const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
const { recordId, fieldDefinition } = useContext(FieldContext); const { recordId, fieldDefinition } = useContext(FieldContext);
const { pathToShowPage, objectNameSingular } = useContext(
RecordTableRowContext, const { pathToShowPage, objectNameSingular } =
); useRecordTableRowContextOrThrow();
const { onOpenTableCell } = useRecordTableBodyContextOrThrow(); const { onOpenTableCell } = useRecordTableBodyContextOrThrow();

View File

@ -1,12 +1,12 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
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 { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { IconComponent } from 'twenty-ui'; import { IconComponent } from 'twenty-ui';
const StyledTrContainer = styled.tr` const StyledRecordTableDraggableTr = styled(RecordTableDraggableTr)`
cursor: pointer; cursor: pointer;
`; `;
@ -36,33 +36,40 @@ const StyledEmptyTd = styled.td`
`; `;
type RecordTableActionRowProps = { type RecordTableActionRowProps = {
draggableId: string;
draggableIndex: number;
LeftIcon: IconComponent; LeftIcon: IconComponent;
text: string; text: string;
onClick?: (event?: React.MouseEvent<HTMLTableRowElement>) => void; onClick?: (event?: React.MouseEvent<HTMLTableRowElement>) => void;
}; };
export const RecordTableActionRow = ({ export const RecordTableActionRow = ({
draggableId,
draggableIndex,
LeftIcon, LeftIcon,
text, text,
onClick, onClick,
}: RecordTableActionRowProps) => { }: RecordTableActionRowProps) => {
const theme = useTheme(); const theme = useTheme();
const visibleColumns = useRecoilComponentValueV2( const { visibleTableColumns } = useRecordTableContextOrThrow();
visibleTableColumnsComponentSelector,
);
return ( return (
<StyledTrContainer onClick={onClick}> <StyledRecordTableDraggableTr
draggableId={draggableId}
draggableIndex={draggableIndex}
onClick={onClick}
isDragDisabled
>
<td aria-hidden /> <td aria-hidden />
<StyledIconContainer> <StyledIconContainer>
<LeftIcon size={theme.icon.size.sm} color={theme.font.color.tertiary} /> <LeftIcon size={theme.icon.size.sm} color={theme.font.color.tertiary} />
</StyledIconContainer> </StyledIconContainer>
<StyledRecordTableTdTextContainer colSpan={visibleColumns.length}> <StyledRecordTableTdTextContainer colSpan={visibleTableColumns.length}>
<StyledText>{text}</StyledText> <StyledText>{text}</StyledText>
</StyledRecordTableTdTextContainer> </StyledRecordTableTdTextContainer>
<StyledEmptyTd /> <StyledEmptyTd />
<StyledEmptyTd /> <StyledEmptyTd />
</StyledTrContainer> </StyledRecordTableDraggableTr>
); );
}; };

View File

@ -1,11 +1,12 @@
import { useContext } from 'react'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useRecordTableRowDraggableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellsEmpty } from '@/object-record/record-table/record-table-row/components/RecordTableCellsEmpty'; import { RecordTableCellsEmpty } from '@/object-record/record-table/record-table-row/components/RecordTableCellsEmpty';
import { RecordTableCellsVisible } from '@/object-record/record-table/record-table-row/components/RecordTableCellsVisible'; import { RecordTableCellsVisible } from '@/object-record/record-table/record-table-row/components/RecordTableCellsVisible';
export const RecordTableCells = () => { export const RecordTableCells = () => {
const { inView, isDragging } = useContext(RecordTableRowContext); const { inView } = useRecordTableRowContextOrThrow();
const { isDragging } = useRecordTableRowDraggableContextOrThrow();
const areCellsVisible = inView || isDragging; const areCellsVisible = inView || isDragging;

View File

@ -1,11 +1,10 @@
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowContextOrThrow } 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 } = useRecordTableRowContextOrThrow();
const visibleTableColumns = useRecoilComponentValueV2( const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector, visibleTableColumnsComponentSelector,

View File

@ -1,20 +1,15 @@
import { useContext } from 'react'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useRecordTableRowDraggableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCell'; import { RecordTableCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCell';
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 { isSelected, isDragging } = useContext(RecordTableRowContext); const { isSelected } = useRecordTableRowContextOrThrow();
const [tableCellWidths] = useRecoilComponentStateV2( const { isDragging } = useRecordTableRowDraggableContextOrThrow();
tableCellWidthsComponentState,
);
const visibleTableColumns = useRecoilComponentValueV2( const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector, visibleTableColumnsComponentSelector,
@ -28,7 +23,7 @@ export const RecordTableCellsVisible = () => {
<RecordTableTd <RecordTableTd
isSelected={isSelected} isSelected={isSelected}
isDragging={isDragging} isDragging={isDragging}
width={tableCellWidths[2]} width={visibleTableColumns[0].size}
> >
<RecordTableCell /> <RecordTableCell />
</RecordTableTd> </RecordTableTd>
@ -42,7 +37,7 @@ export const RecordTableCellsVisible = () => {
<RecordTableTd <RecordTableTd
isSelected={isSelected} isSelected={isSelected}
isDragging={isDragging} isDragging={isDragging}
width={tableCellWidths[columnIndex + 3]} width={column.size}
> >
<RecordTableCell /> <RecordTableCell />
</RecordTableTd> </RecordTableTd>

View File

@ -0,0 +1,63 @@
import { useTheme } from '@emotion/react';
import { Draggable, DraggableId } from '@hello-pangea/dnd';
import { forwardRef, 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 { combineRefs } from '~/utils/combineRefs';
type RecordTableDraggableTrProps = {
draggableId: DraggableId;
draggableIndex: number;
isDragDisabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLTableRowElement>) => void;
children: ReactNode;
};
export const RecordTableDraggableTr = forwardRef<
HTMLTableRowElement,
RecordTableDraggableTrProps
>(({ draggableId, draggableIndex, isDragDisabled, onClick, children }, ref) => {
const theme = useTheme();
return (
<Draggable
draggableId={draggableId}
index={draggableIndex}
isDragDisabled={isDragDisabled}
>
{(draggableProvided, draggableSnapshot) => (
<RecordTableTr
ref={combineRefs<HTMLTableRowElement>(
ref,
draggableProvided.innerRef,
)}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
style={{
...draggableProvided.draggableProps.style,
background: draggableSnapshot.isDragging
? theme.background.transparent.light
: 'none',
borderColor: draggableSnapshot.isDragging
? `${theme.border.color.medium}`
: 'transparent',
}}
isDragging={draggableSnapshot.isDragging}
data-testid={`row-id-${draggableId}`}
data-selectable-id={draggableId}
onClick={onClick}
>
<RecordTableRowDraggableContextProvider
value={{
isDragging: draggableSnapshot.isDragging,
dragHandleProps: draggableProvided.dragHandleProps,
}}
>
{children}
</RecordTableRowDraggableContextProvider>
</RecordTableTr>
)}
</Draggable>
);
});

View File

@ -1,18 +1,14 @@
import { useTheme } from '@emotion/react'; import { ReactNode, useContext, useEffect } from 'react';
import { Draggable } from '@hello-pangea/dnd';
import { ReactNode, useContext, useEffect, useRef } from 'react';
import { useInView } from 'react-intersection-observer';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr'; 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 { 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 { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useInView } from 'react-intersection-observer';
type RecordTableRowWrapperProps = { type RecordTableRowWrapperProps = {
recordId: string; recordId: string;
@ -29,18 +25,15 @@ export const RecordTableRowWrapper = ({
isPendingRow, isPendingRow,
children, children,
}: RecordTableRowWrapperProps) => { }: RecordTableRowWrapperProps) => {
const trRef = useRef<HTMLTableRowElement>(null);
const { objectMetadataItem } = useRecordTableContextOrThrow(); const { objectMetadataItem } = useRecordTableContextOrThrow();
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
const theme = useTheme();
const currentRowSelected = useRecoilComponentFamilyValueV2( const currentRowSelected = useRecoilComponentFamilyValueV2(
isRowSelectedComponentFamilyState, isRowSelectedComponentFamilyState,
recordId, recordId,
); );
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
const scrollWrapperRef = useContext( const scrollWrapperRef = useContext(
RecordTableWithWrappersScrollWrapperContext, RecordTableWithWrappersScrollWrapperContext,
); );
@ -52,24 +45,6 @@ export const RecordTableRowWrapper = ({
rootMargin: '1000px', rootMargin: '1000px',
}); });
const setTableCellWidths = useSetRecoilComponentStateV2(
tableCellWidthsComponentState,
);
useEffect(() => {
if (rowIndexForFocus === 0) {
const tdArray = Array.from(
trRef.current?.getElementsByTagName('td') ?? [],
);
const tdWidths = tdArray.map((td) => {
return td.getBoundingClientRect().width;
});
setTableCellWidths(tdWidths);
}
}, [trRef, rowIndexForFocus, 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) {
@ -78,55 +53,29 @@ export const RecordTableRowWrapper = ({
}, [inView, onIndexRecordsLoaded]); }, [inView, onIndexRecordsLoaded]);
return ( return (
<Draggable <RecordTableDraggableTr
ref={elementRef}
key={recordId} key={recordId}
draggableId={recordId} draggableId={recordId}
index={rowIndexForDrag} draggableIndex={rowIndexForDrag}
isDragDisabled={isPendingRow} isDragDisabled={isPendingRow}
> >
{(draggableProvided, draggableSnapshot) => ( <RecordTableRowContextProvider
<RecordTableTr value={{
ref={(node) => { recordId,
// @ts-expect-error - TS doesn't know that node.current is assignable rowIndex: rowIndexForFocus,
trRef.current = node; pathToShowPage:
elementRef(node); getBasePathToShowPage({
draggableProvided.innerRef(node);
}}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
style={{
...draggableProvided.draggableProps.style,
background: draggableSnapshot.isDragging
? theme.background.transparent.light
: 'none',
borderColor: draggableSnapshot.isDragging
? `${theme.border.color.medium}`
: 'transparent',
}}
isDragging={draggableSnapshot.isDragging}
data-testid={`row-id-${recordId}`}
data-selectable-id={recordId}
>
<RecordTableRowContext.Provider
value={{
recordId,
rowIndex: rowIndexForFocus,
pathToShowPage:
getBasePathToShowPage({
objectNameSingular: objectMetadataItem.nameSingular,
}) + recordId,
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
isSelected: currentRowSelected, }) + recordId,
isPendingRow, objectNameSingular: objectMetadataItem.nameSingular,
isDragging: draggableSnapshot.isDragging, isSelected: currentRowSelected,
dragHandleProps: draggableProvided.dragHandleProps, isPendingRow,
inView, inView,
}} }}
> >
{children} {children}
</RecordTableRowContext.Provider> </RecordTableRowContextProvider>
</RecordTableTr> </RecordTableDraggableTr>
)}
</Draggable>
); );
}; };

View File

@ -1,13 +1,12 @@
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
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 { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
export const useSetCurrentRowSelected = () => { export const useSetCurrentRowSelected = () => {
const { recordId } = useContext(RecordTableRowContext); const { recordId } = useRecordTableRowContextOrThrow();
const isRowSelectedFamilyState = useRecoilComponentCallbackStateV2( const isRowSelectedFamilyState = useRecoilComponentCallbackStateV2(
isRowSelectedComponentFamilyState, isRowSelectedComponentFamilyState,

View File

@ -1,9 +1,11 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow'; import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -17,6 +19,10 @@ export const RecordTableRecordGroupSectionAddNew = () => {
currentRecordGroupId, currentRecordGroupId,
); );
const recordIds = useRecoilComponentValueV2(
recordIndexAllRecordIdsComponentSelector,
);
const { createNewTableRecordInGroup } = const { createNewTableRecordInGroup } =
useCreateNewTableRecord(recordTableId); useCreateNewTableRecord(recordTableId);
@ -28,6 +34,8 @@ export const RecordTableRecordGroupSectionAddNew = () => {
return ( return (
<RecordTableActionRow <RecordTableActionRow
draggableId={`add-new-record-${currentRecordGroupId}`}
draggableIndex={recordIds.length + 2}
LeftIcon={IconPlus} LeftIcon={IconPlus}
text="Add new" text="Add new"
onClick={handleAddNewRecord} onClick={handleAddNewRecord}

View File

@ -1,9 +1,11 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable'; import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState'; import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow'; import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconArrowDown } from 'twenty-ui'; import { IconArrowDown } from 'twenty-ui';
export const RecordTableRecordGroupSectionLoadMore = () => { export const RecordTableRecordGroupSectionLoadMore = () => {
@ -18,6 +20,10 @@ export const RecordTableRecordGroupSectionLoadMore = () => {
currentRecordGroupId, currentRecordGroupId,
); );
const recordIds = useRecoilComponentValueV2(
recordIndexAllRecordIdsComponentSelector,
);
const handleLoadMore = () => { const handleLoadMore = () => {
fetchMoreRecords(); fetchMoreRecords();
}; };
@ -28,6 +34,8 @@ export const RecordTableRecordGroupSectionLoadMore = () => {
return ( return (
<RecordTableActionRow <RecordTableActionRow
draggableId={`load-more-records-${currentRecordGroupId}`}
draggableIndex={recordIds.length + 1}
LeftIcon={IconArrowDown} LeftIcon={IconArrowDown}
text="Load more" text="Load more"
onClick={handleLoadMore} onClick={handleLoadMore}

View File

@ -0,0 +1,15 @@
import { isFunction } from '@sniptt/guards';
import { MutableRefObject, Ref } from 'react';
import { isDefined } from '~/utils/isDefined';
export const combineRefs = <T>(...refs: (Ref<T> | undefined)[]) => {
return (node: T) => {
for (const ref of refs) {
if (isFunction(ref)) {
ref(node);
} else if (isDefined(ref) && 'current' in ref) {
(ref as MutableRefObject<T | null>).current = node;
}
}
};
};