Fix dragging behavior below the last card when dragging below the new CTA button (#11781)
- Fixed an issue where dragging an item below the last card didn't work when the card was dragged below the new CTA button (`destination.index === items.length` case). - Moved the "new record" button outside of the draggable list ### Demo https://github.com/user-attachments/assets/370f2c1f-4bb2-403b-b8ed-4afda064c98d Closes #10197 --------- Co-authored-by: prastoin <paul@twenty.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -32,6 +32,7 @@ import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-
|
|||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
import { getIndexNeighboursElementsFromArray } from '~/utils/array/getIndexNeighboursElementsFromArray';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -173,14 +174,15 @@ export const RecordBoard = () => {
|
|||||||
)
|
)
|
||||||
: destinationRecordByGroupIds;
|
: destinationRecordByGroupIds;
|
||||||
|
|
||||||
const recordBeforeId =
|
const { before: recordBeforeId, after: recordAfterId } =
|
||||||
otherRecordIdsInDestinationColumn[destinationIndexInColumn - 1];
|
getIndexNeighboursElementsFromArray({
|
||||||
|
index: destinationIndexInColumn,
|
||||||
|
array: otherRecordIdsInDestinationColumn,
|
||||||
|
});
|
||||||
const recordBefore = recordBeforeId
|
const recordBefore = recordBeforeId
|
||||||
? getSnapshotValue(snapshot, recordStoreFamilyState(recordBeforeId))
|
? getSnapshotValue(snapshot, recordStoreFamilyState(recordBeforeId))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const recordAfterId =
|
|
||||||
otherRecordIdsInDestinationColumn[destinationIndexInColumn];
|
|
||||||
const recordAfter = recordAfterId
|
const recordAfter = recordAfterId
|
||||||
? getSnapshotValue(snapshot, recordStoreFamilyState(recordAfterId))
|
? getSnapshotValue(snapshot, recordStoreFamilyState(recordAfterId))
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/re
|
|||||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||||
import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState';
|
import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
const StyledColumnCardsContainer = styled.div`
|
const StyledColumnCardsContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -23,7 +22,6 @@ const StyledColumnCardsContainer = styled.div`
|
|||||||
const StyledNewButtonContainer = styled.div`
|
const StyledNewButtonContainer = styled.div`
|
||||||
padding-bottom: ${({ theme }) => theme.spacing(4)};
|
padding-bottom: ${({ theme }) => theme.spacing(4)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||||
const StyledSkeletonCardContainer = styled.div`
|
const StyledSkeletonCardContainer = styled.div`
|
||||||
background-color: ${({ theme }) => theme.background.secondary};
|
background-color: ${({ theme }) => theme.background.secondary};
|
||||||
@ -102,14 +100,13 @@ export const RecordBoardColumnCardsContainer = ({
|
|||||||
ref={draggableProvided?.innerRef}
|
ref={draggableProvided?.innerRef}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...draggableProvided?.draggableProps}
|
{...draggableProvided?.draggableProps}
|
||||||
>
|
></div>
|
||||||
<StyledNewButtonContainer>
|
|
||||||
<RecordBoardColumnNewRecordButton />
|
|
||||||
</StyledNewButtonContainer>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
{droppableProvided?.placeholder}
|
{droppableProvided?.placeholder}
|
||||||
|
<StyledNewButtonContainer>
|
||||||
|
<RecordBoardColumnNewRecordButton />
|
||||||
|
</StyledNewButtonContainer>
|
||||||
</StyledColumnCardsContainer>
|
</StyledColumnCardsContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { EachTestingContext } from 'twenty-shared/testing';
|
||||||
|
import { getIndexNeighboursElementsFromArray } from '../getIndexNeighboursElementsFromArray';
|
||||||
|
|
||||||
|
type TestCase = {
|
||||||
|
expected: ReturnType<typeof getIndexNeighboursElementsFromArray>;
|
||||||
|
} & Parameters<typeof getIndexNeighboursElementsFromArray>[0];
|
||||||
|
|
||||||
|
describe('getIndexNeighboursElementsFromArray', () => {
|
||||||
|
const testCases: EachTestingContext<TestCase>[] = [
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
'should return undefined for before and first element for after when index is 0',
|
||||||
|
context: {
|
||||||
|
index: 0,
|
||||||
|
array: [
|
||||||
|
'a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d',
|
||||||
|
'b2c3d4e5-f6a7-5b6c-9d8e-0f1a2b3c4d5e',
|
||||||
|
'c3d4e5f6-a7b8-6c7d-0e9f-1a2b3c4d5e6f',
|
||||||
|
],
|
||||||
|
expected: {
|
||||||
|
before: undefined,
|
||||||
|
after: 'a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
'should return last element for before and undefined for after when index is at end',
|
||||||
|
context: {
|
||||||
|
index: 3,
|
||||||
|
array: [
|
||||||
|
'a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d',
|
||||||
|
'b2c3d4e5-f6a7-5b6c-9d8e-0f1a2b3c4d5e',
|
||||||
|
'c3d4e5f6-a7b8-6c7d-0e9f-1a2b3c4d5e6f',
|
||||||
|
],
|
||||||
|
expected: {
|
||||||
|
before: 'c3d4e5f6-a7b8-6c7d-0e9f-1a2b3c4d5e6f',
|
||||||
|
after: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
'should return last element for before and undefined for after when index is above the end',
|
||||||
|
context: {
|
||||||
|
index: 42,
|
||||||
|
array: [
|
||||||
|
'a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d',
|
||||||
|
'b2c3d4e5-f6a7-5b6c-9d8e-0f1a2b3c4d5e',
|
||||||
|
'c3d4e5f6-a7b8-6c7d-0e9f-1a2b3c4d5e6f',
|
||||||
|
],
|
||||||
|
expected: {
|
||||||
|
before: 'c3d4e5f6-a7b8-6c7d-0e9f-1a2b3c4d5e6f',
|
||||||
|
after: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'should return correct before and after elements for middle index',
|
||||||
|
context: {
|
||||||
|
index: 1,
|
||||||
|
array: [
|
||||||
|
'a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d',
|
||||||
|
'b2c3d4e5-f6a7-5b6c-9d8e-0f1a2b3c4d5e',
|
||||||
|
'c3d4e5f6-a7b8-6c7d-0e9f-1a2b3c4d5e6f',
|
||||||
|
],
|
||||||
|
expected: {
|
||||||
|
before: 'a1b2c3d4-e5f6-4a5b-8c7d-9e0f1a2b3c4d',
|
||||||
|
after: 'b2c3d4e5-f6a7-5b6c-9d8e-0f1a2b3c4d5e',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'should handle empty array array',
|
||||||
|
context: {
|
||||||
|
index: 0,
|
||||||
|
array: [],
|
||||||
|
expected: {
|
||||||
|
before: undefined,
|
||||||
|
after: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(testCases)('$title', ({ context }) => {
|
||||||
|
const result = getIndexNeighboursElementsFromArray({
|
||||||
|
index: context.index,
|
||||||
|
array: context.array,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(context.expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
type GetIndexNeighboursElementsFromArrayArgs = {
|
||||||
|
index: number;
|
||||||
|
array: string[];
|
||||||
|
};
|
||||||
|
type GetIndexNeighboursElementsFromArrayReturnType = {
|
||||||
|
before?: string;
|
||||||
|
after?: string;
|
||||||
|
};
|
||||||
|
export const getIndexNeighboursElementsFromArray = ({
|
||||||
|
index,
|
||||||
|
array,
|
||||||
|
}: GetIndexNeighboursElementsFromArrayArgs): GetIndexNeighboursElementsFromArrayReturnType => {
|
||||||
|
if (index === 0) {
|
||||||
|
return {
|
||||||
|
after: array.at(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= array.length) {
|
||||||
|
return {
|
||||||
|
before: array.at(-1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
before: array.at(index - 1),
|
||||||
|
after: array.at(index),
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user