Drag & Drop Card
draggable card
import { FC, memo, useRef } from 'react';
import { ItemTypes } from './ItemTypes';
import { useDrag, useDrop, XYCoord } from 'react-dnd';
const style: React.CSSProperties = {
border: '1px dashed gray',
marginBottom: '.5rem',
backgroundColor: 'white',
cursor: 'move',
width: 200,
aspectRatio: '1/1',
};
interface CardProps {
id: string;
text: string;
index: number;
moveCard: (dragIndex: number, hoverIndex: number) => void;
}
interface DragItem {
index: number;
id: string;
type: string;
}
export const Card: FC<CardProps> = memo(function Card({
id,
text,
index,
moveCard,
}) {
const dragElRef = useRef<HTMLDivElement | null>(null);
const [{ isDragging, draggedId }, connectDrag, preview] = useDrag({
type: ItemTypes.CARD,
item: () => {
return { id, index };
},
collect: (monitor) => ({
draggedId: monitor.getItem()?.id,
isDragging: monitor.isDragging(),
}),
});
const [, connectDrop] = useDrop({
accept: ItemTypes.CARD,
hover: (item: DragItem, monitor) => {
if (!dragElRef.current) return;
const fromIndex = item.index;
const toIndex = index;
if (fromIndex === toIndex) return;
const hoverBoundingRect = dragElRef.current?.getBoundingClientRect();
const clientOffsetAsMousePosition = monitor.getClientOffset();
console.log(hoverBoundingRect, clientOffsetAsMousePosition);
const hoverMiddleX =
(hoverBoundingRect.right - hoverBoundingRect.left) / 2;
const hoverMiddleY =
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const hoverClientX =
(clientOffsetAsMousePosition as XYCoord).x - hoverBoundingRect.left;
const hoverClientY =
(clientOffsetAsMousePosition as XYCoord).y - hoverBoundingRect.top;
if (
fromIndex < toIndex &&
hoverClientY < hoverMiddleY &&
hoverClientX < hoverMiddleX
)
return;
if (
fromIndex > toIndex &&
hoverClientY > hoverMiddleY &&
hoverClientX > hoverMiddleX
)
return;
moveCard(fromIndex, toIndex);
// 중요!!
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
item.index = toIndex;
},
});
connectDrop(connectDrag(dragElRef));
let opacity = 1;
if (draggedId === id || isDragging) {
opacity = 0.3;
}
return (
<div ref={dragElRef} style={{ ...style, opacity, position: 'relative' }}>
<h1>{text}</h1>
{/*
<h1
style={{
width: 10,
height: 10,
background: 'rgba(255,0,0,.3)',
position: 'absolute',
left: 0,
top: 0,
}}
></h1>
<h1
style={{
width: 10,
height: 10,
background: 'rgba(0,255,0,.3)',
position: 'absolute',
left: 0,
top: 0,
}}
></h1> */}
<h1>
{' '}
boundingClientRect?.x :: {dragElRef.current?.getBoundingClientRect().x}
</h1>
<h1>
{' '}
boundingClientRect?.y :: {dragElRef.current?.getBoundingClientRect().y}
</h1>
{/* <h1> boundingClientRect?.x :: {boundingClientRect?.x}</h1>
<h1> boundingClientRect?.y :: {boundingClientRect?.y}</h1> */}
</div>
);
});
Container
import { FC, useCallback, useRef, useState } from 'react';
import { faker } from '@faker-js/faker';
import { Card } from './Card';
interface CardItem {
id: string;
text: string;
}
const buildCardData = (): CardItem[] => {
return Array.from({ length: 1000 }, (_, i) => ({
id: i.toString(),
text: faker.internet.username(),
}));
};
export const Container: FC = () => {
const [cards, setCards] = useState<CardItem[]>(buildCardData);
const requestRef = useRef<number | null>(null);
const moveCard = useCallback((fromIndex: number, toIndex: number) => {
if (fromIndex === toIndex) return;
setCards((prevCards) => {
const updatedCards = [...prevCards];
const [draggedItem] = updatedCards.splice(fromIndex, 1);
updatedCards.splice(toIndex, 0, draggedItem);
return updatedCards;
});
// requestRef.current = requestAnimationFrame(() =>
// setCards((prev) => [...prev]),
// );
}, []);
// useEffect(() => {
// return () => {
// if (requestRef.current) cancelAnimationFrame(requestRef.current);
// };
// }, []);
return (
<div className='flex flex-wrap p-4'>
{cards.map((card, idx) => (
<Card
key={card.id}
index={idx}
id={card.id}
text={card.text}
moveCard={moveCard}
/>
))}
</div>
);
};