이번 프로젝트에서 좀 재밌게 웹페이지를 구현해보고 싶어서 UI 대한 고민을 많이 하였다.
그 중 사이드 네비게이션이 좀 맘에 안 들었고, 어떻게 재밌게 할 수 없을까? 라는 생각에 드래그 앤 드롭으로 화면 전환을 해보자! 라는 생각에 바로 구현을 들어가게 되었다.
제일 처음에 아무것도 모르고 한 프로젝트에서 드래그 앤 드롭 기능으로 react-beautiful-dnd
을 사용했었다.
드래그 앤 드롭 라이브러리를 여러 개 찾아본 결과, 이 라이브러리가 현재도 많이 사용하고 있는 것 같고 기능도 괜찮았기에 채택하게 되었다.
그땐 적용하는 데에 바빠 어떤 내용이 있었는지도 기억이 하나도 나지 않기 때문에 이번엔 그런 불상사를 막으려 라이브러리를 조금 파헤쳐보려한닷
typescript를 사용하면
npm i --save-dev @types/react-beautiful-dnd
아니라면
npm i react-beautiful-dnd
으로 설치해주면 된다.
나는 타입스크립트를 사용하기 때문에 전자를 선택
여기서 사용하는 컴포넌트는 크게 총 세 가지가 있다.
DragDropContext, Droppable, Draggable
DragDropContext : 드래그 앤 드롭을 사용하기 위해 정의
Droppable : 드롭한 요소를 놓을 수 있는 곳
Draggable : 드롭할 요소
-> 피그마로 대충 짠 UI다.
발바닥을 드래그하고, 본문에 드롭하면 페이지가 넘어가도록 하는 로직을 만들 것이다.
그러려면 Droppable은 2개, Draggable은 한 개가 필요하다
제일 먼저 드래그 앤 드롭을 사용하기 위하여 DragDropContext를 선언해준다.
<DragDropContext onDragEnd={}>
</DragDropContext>
-> onDragEnd는 Drop을 하였을 때 나오는 이벤트로, 원하는 로직을 넣어주면 된다.
<DragDropContext onDragEnd={}>
<Droppable droppableId="left-droppable">
// provided에는 드래그앤드롭시에 필요한 데이터, snapshot엔 드래그할 때의 상태를 담고 있다.
{(provided, snapshot) => (
// ref와 ...provided.droppableProps는 필수적인 속성!
<div ref={provided.innerRef} {...provided.droppableProps} >
// 이것도 필수적으로 써줘야 하는 것!! 드래그 하는 동안의 공간을 만드는 역할을 함
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<DragDropContext onDragEnd={}>
<Droppable droppableId="left-droppable">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps} >
<Draggable draggableId={id} index={index}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} >
// 여긴 원하는 UI (나는 발바닥을 넣었다)
</div>
)}
</Draggable>
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
'use client';
import CenterContent from '@/components/Layout/CenterContent';
import Sidebar from '@/components/Sidebar/Sidebar';
import { Stack } from '@mui/material';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
const Home = () => {
const router = useRouter();
const [isBrowser, setIsBrowser] = useState(false);
useEffect(() => {
setIsBrowser(process.browser);
}, []);
const dragHandler = (result: DropResult) => {
if (
result.destination?.droppableId === 'right-droppable' &&
result.source.droppableId === 'left-droppable'
) {
console.log(result.source.index);
router.push(`/${result.source.index}`);
}
};
const sidebarList = {
sidebarDto: {
categoryId: 0,
categoryName: '프론트엔드',
postTitleDtos: [
{
postId: 0,
postTitle: 'HTML',
},
{
postId: 1,
postTitle: 'CSS',
},
{
postId: 2,
postTitle: 'JS',
},
],
},
};
const writeList = sidebarList.sidebarDto.postTitleDtos;
return (
<>
{isBrowser ? (
<DragDropContext onDragEnd={dragHandler}>
<Sidebar />
<CenterContent>
<Stack gap={8} width="100%" height="100%" direction="row">
<Droppable droppableId="left-droppable">
{(provided, snapshot) => (
<div
className="top-container"
style={{ backgroundColor: snapshot.isDraggingOver ? 'blue' : 'grey' }}
{...provided.droppableProps}
ref={provided.innerRef}>
<Stack gap={4}>
{writeList.map((write) => {
return (
<Draggable
key={write.postId}
draggableId={`container-${write.postId}`}
index={write.postId}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
<Stack direction="row" alignItems="center" gap={4}>
<Stack>{write.postTitle}</Stack>
<Stack
bgcolor="#ffffff"
borderRadius="50%"
width="50px"
height="50px"></Stack>
</Stack>
</div>
)}
</Draggable>
);
})}
</Stack>
{provided.placeholder}
</div>
)}
</Droppable>
<Droppable droppableId="right-droppable">
{(provided) => {
return (
<div
className="right-container"
{...provided.droppableProps}
style={{ width: '100%', height: '100%' }}
ref={provided.innerRef}>
<Stack width="70%" height="100%" color="black" bgcolor="#ffffff">
asdf
</Stack>
{provided.placeholder}
</div>
);
}}
</Droppable>
</Stack>
</CenterContent>
</DragDropContext>
) : null}
</>
);
};
export default Home;