오늘의 목표
- 마이 페이지 Grid Layout 구현
- grid 외부에서 drag & drop으로 컴포넌트 추가 기능 구현
수행 내역
- react-grid-layout 예제 참고하여 droppable 기능 구현
import React, { useState } from 'react';
import ReactGridLayout from 'react-grid-layout';
import './MyPage.scss';
const initialLayout = [
{ i: 'BED', x: 0, y: 0, w: 1, h: 1 },
{ i: 'dark-magician', x: 1, y: 0, w: 1, h: 1 },
{ i: 'kuriboh', x: 2, y: 0, w: 1, h: 1 },
{ i: 'spell-caster', x: 3, y: 0, w: 1, h: 1 },
{ i: 'summoned-skull', x: 0, y: 1, w: 1, h: 1 },
];
const MyPage = () => {
const [layout, setLayout] = useState(initialLayout);
const [newItemCounter, setNewItemCounter] = useState(0);
const onDrop = (currentLayout, droppedItemProps) => {
setNewItemCounter((prevCounter) => {
const newId = `new-item-${prevCounter}`;
const newItem = {
i: newId,
x: droppedItemProps.x,
y: droppedItemProps.y,
w: 1,
h: 1,
};
setLayout((prevLayout) => [...prevLayout, newItem]);
console.log(newItem);
return prevCounter + 1;
});
console.log('레이아웃', layout);
console.log('아이템', droppedItemProps);
setNewItemCounter((prevCounter) => prevCounter + 1);
};
const renderGridItems = () => {
return layout.map((item) => {
const isNewItem = item.i.startsWith('new-item');
const itemText = isNewItem
? `New Item ${item.i.split('-').pop()}`
: item.i.replace(/-/g, ' ').toUpperCase();
return (
<div className={isNewItem ? 'new-grid-item' : item.i} key={item.i}>
<div className='grid-item-text'>{itemText}</div>
</div>
);
});
};
return (
<div id='MyPage'>
<div className='user-info-area'>
<div className='profile-photo'>
<img id='user-profile-photo' src='' alt='' />
</div>
<div className='user-introduction'>
<textarea></textarea>
</div>
</div>
<div className='blog-area'>
<div className='floating-droppables-ui'>
<div
className='droppable-element'
draggable={true}
unselectable='on'
onDragStart={(e) => e.dataTransfer.setData('text/plain', '')}
>
Drag Element1
</div>
<div
className='droppable-element'
draggable={true}
unselectable='on'
onDragStart={(e) => e.dataTransfer.setData('text/plain', '')}
>
Drag Element2
</div>
<div
className='droppable-element'
draggable={true}
unselectable='on'
onDragStart={(e) => e.dataTransfer.setData('text/plain', '')}
>
Drag Element3
</div>
</div>
<ReactGridLayout
layout={layout}
cols={5}
rowHeight={170}
width={900}
onDrop={onDrop}
isDroppable={true}
compactType={'vertical'}
>
{renderGridItems()}
</ReactGridLayout>
</div>
</div>
);
};
export default MyPage;
#MyPage {
position: relative;
display: flex;
gap: 30px;
.user-info-area {
display: flex;
flex-direction: column;
align-items: center;
gap: 40px;
padding-top: 60px;
flex: 0.3;
.profile-photo {
width: 250px;
height: 250px;
border-radius: 50%;
background-color: bisque;
}
.user-introduction {
width: 100%;
textarea {
width: 100%;
height: 150px;
}
}
}
.blog-area {
flex: 1;
.react-grid-layout {
position: relative;
transition: height 200ms ease;
background-color: azure;
margin: 0 auto;
overflow: hidden;
.react-grid-item {
cursor: pointer;
transition: all 200ms ease;
background-color: #9ee7e7;
transition-property: left, top, width, height;
border-radius: 10px;
a {
display: block;
width: 100%;
height: 100%;
}
img {
pointer-events: none;
user-select: none;
}
&.cssTransforms {
transition-property: transform, width, height;
}
&.resizing {
transition: none;
z-index: 1;
will-change: width, height;
}
&.react-draggable-dragging {
transition: none;
z-index: 3;
will-change: transform;
}
&.dropping {
visibility: hidden;
}
&.react-grid-placeholder {
border: 3px dashed #c80cf7;
opacity: 0.2;
transition-duration: 100ms;
z-index: 2;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
&.placeholder-resizing {
transition: none;
}
}
> .react-resizable-handle {
position: absolute;
width: 20px;
height: 20px;
&::after {
content: '';
position: absolute;
right: 6px;
bottom: 6px;
width: 5px;
height: 5px;
border-right: 2px solid #00000066;
border-bottom: 2px solid #00000066;
}
&.react-resizable-handle-sw {
bottom: 0;
left: 0;
cursor: sw-resize;
transform: rotate(90deg);
}
&.react-resizable-handle-se {
bottom: 0;
right: 0;
cursor: se-resize;
}
&.react-resizable-handle-nw {
top: 0;
left: 0;
cursor: nw-resize;
transform: rotate(180deg);
}
&.react-resizable-handle-ne {
top: 0;
right: 0;
cursor: ne-resize;
transform: rotate(270deg);
}
&.react-resizable-handle-w,
&.react-resizable-handle-e {
top: 50%;
margin-top: -10px;
cursor: ew-resize;
}
&.react-resizable-handle-w {
left: 0;
transform: rotate(135deg);
}
&.react-resizable-handle-e {
right: 0;
transform: rotate(315deg);
}
&.react-resizable-handle-n,
&.react-resizable-handle-s {
left: 50%;
margin-left: -10px;
cursor: ns-resize;
}
&.react-resizable-handle-n {
top: 0;
transform: rotate(225deg);
}
&.react-resizable-handle-s {
bottom: 0;
transform: rotate(45deg);
}
}
}
}
}
.floating-droppables-ui {
position: fixed;
bottom: 5%;
left: 50%;
margin-left: -150px;
display: flex;
justify-content: space-between;
width: 300px;
height: 50px;
z-index: 5;
background-color: pink;
.droppable-element {
width: 80px;
height: 50px;
text-align: center;
border-radius: 10px;
background-color: #9ee7e7;
}
}
.react-resizable-hide {
> .react-resizable-handle {
display: none;
}
}
}
문제 및 이슈
- 인식 되는 layout 내의 좌표와 실제 추가 되는 컴포넌트의 위치가 다름
- resizable 요소를 크기 조절 한 후 새로운 컴포넌트를 추가하면 크기가 기본 크기인 1x1 으로 되돌아감
- 클릭 이벤트 사용 시 드래그 앤 드랍으로 이동하는 경우에도 클릭 이벤트 발생
참고자료
지원요청
내일 계획