react-drag-panel
Version:
Drag & drop panel grid layout for React
1 lines • 23.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/PanelGrid.tsx","../src/components/PanelGrid.module.css","../src/utils/utils.ts","../src/utils/calculateUtils.ts"],"sourcesContent":["import React, { cloneElement, isValidElement, useMemo, useRef, useState } from 'react';\nimport type { JSX } from 'react';\nimport styles from './PanelGrid.module.css';\nimport { calculateContainerHeight, calculatePanelStyle, getDropPosition, resolveCollisions } from '../utils/calculateUtils';\nimport type { Panel } from '../types/types';\nimport { isColliding } from '../utils/utils';\n\ninterface PanelGridProps {\n /** \n * 렌더링할 패널 배열 \n * 각 패널은 위치(x, y), 크기(w, h), 고유 id 등을 포함함\n */\n panels?: Panel[];\n\n /** \n * 한 줄에 배치할 컬럼 수 \n * 기본값은 12\n */\n cols?: number;\n\n /** \n * 한 grid row 당 높이 (px 단위) \n * 패널 높이 계산에 사용됨 \n */\n rowHeight?: number;\n\n /** \n * 전체 grid 컨테이너의 너비 (px 단위) \n */\n width?: number;\n\n /** \n * 각 패널 사이의 간격: [수평(px), 수직(px)] \n * 기본값은 [0, 0]\n */\n margin?: [number, number];\n\n /** \n * grid 컨테이너의 안쪽 여백: [좌우(px), 상하(px)] \n * 기본값은 [0, 0]\n */\n padding?: [number, number];\n \n /**\n * 드래그 앤 드롭 기능 활성화 여부\n * 기본값은 true\n */\n isDraggable?: boolean;\n\n /**\n * 드래그 앤 드롭 시 패널 간 충돌 방지 여부\n * 기본값은 true\n */\n preventCollision?: boolean;\n\n /**\n * 패널 위치가 변경될 때 호출되는 콜백 함수\n */\n onLayoutChange?: (layout: Panel[]) => void;\n\n /** \n * 각 패널에 대응되는 자식 React 요소들 \n */\n children?: React.ReactNode;\n}\n\ninterface GridChildProps extends React.HTMLAttributes<HTMLDivElement> {\n /** \n * 외부에서 전달할 style 속성 (override 가능)\n */\n style?: React.CSSProperties;\n\n /** \n * 클래스명을 문자열 형태로 전달\n */\n className?: string;\n\n /** \n * 테스트 용도의 식별자 (data-testid)\n */\n 'data-testid'?: string;\n};\n\n/**\n * PanelGrid는 주어진 패널 데이터를 기반으로 고정된 컬럼 수와 행 높이에 따라\n * 격자 형태로 자식 컴포넌트를 배치하고, 드래그 앤 드롭을 통해 순서를 재배치할 수 있는 컴포넌트입니다.\n */\nconst PanelGrid = ({\n panels = [],\n cols = 12,\n rowHeight = 50,\n width = 1200,\n margin = [0, 0],\n padding = [0, 0],\n isDraggable = true,\n preventCollision = true,\n onLayoutChange,\n children,\n}: PanelGridProps): JSX.Element => {\n const [panelList, setPanelList] = useState<Panel[]>(panels);\n const dragItem = useRef<Panel | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const originalPanels = useRef<Panel[]>(panels);\n const lastDragOverPosRef = useRef<{ x: number; y: number } | null>(null);\n\n const [dragOverPosition, setDragOverPosition] = useState<{\n x: number;\n y: number;\n w: number;\n h: number;\n } | null>(null);\n\n const unitWidth = width / cols;\n const containerHeight = calculateContainerHeight({\n panels: panelList,\n rowHeight,\n marginY: margin[1],\n paddingY: padding[1],\n });\n const childrenArray = useMemo(() => React.Children.toArray(children), [children]);\n\n\n /**\n * 드래그 시작 시 호출되는 함수\n */\n const handleDragStart = (panel: Panel) => {\n dragItem.current = panel;\n originalPanels.current = panelList.map(p => ({ ...p }));\n };\n\n /**\n * 드래그가 지속되는 동안 호출되는 함수\n */\n const handleDrag = (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault();\n if (dragItem.current === null || !containerRef.current) return;\n\n const draggedPanel = dragItem.current;\n const { w = 1, h = 1 } = draggedPanel;\n\n const containerRect = containerRef.current.getBoundingClientRect();\n const maxRows = Math.floor(containerHeight / rowHeight);\n\n if (event.clientX === 0 && event.clientY === 0) return;\n\n const { x, y } = getDropPosition({\n event,\n config: {\n containerRect,\n unitWidth,\n rowHeight,\n padding,\n margin,\n cols,\n maxRows,\n panelSize: { w, h },\n },\n });\n\n const last = lastDragOverPosRef.current;\n if (last && last.x === x && last.y === y) return;\n\n lastDragOverPosRef.current = { x, y };\n setDragOverPosition({ x, y, w, h });\n\n const tempDragged = { ...draggedPanel, x, y };\n const isOverlap = panelList.some(\n (panel) => panel.id !== draggedPanel.id && isColliding(tempDragged, panel)\n );\n\n let nextList = originalPanels.current.map((p) => ({ ...p }));\n if (preventCollision && isOverlap) {\n nextList = resolveCollisions(tempDragged, panelList);\n }\n\n const hasChanged = nextList.some((panel, i) => {\n const prev = panelList[i];\n return (\n panel.x !== prev.x ||\n panel.y !== prev.y ||\n panel.w !== prev.w ||\n panel.h !== prev.h\n );\n });\n\n if (hasChanged) {\n setPanelList(nextList);\n }\n };\n\n /**\n * 드래그가 종료되었을 때 호출되는 함수\n */\n const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {\n if (dragItem.current === null || !containerRef.current) return;\n\n const draggedPanel = dragItem.current;\n const { w = 1, h = 1 } = draggedPanel;\n\n const containerRect = containerRef.current.getBoundingClientRect();\n const maxRows = Math.floor(containerHeight / rowHeight);\n\n const { x, y } = getDropPosition({\n event,\n config: {\n containerRect,\n unitWidth,\n rowHeight,\n padding,\n margin,\n cols,\n maxRows,\n panelSize: { w, h },\n },\n });\n\n const tempDragged = { ...draggedPanel, x, y };\n const isOverlap = panelList.some(\n (panel) => panel.id !== draggedPanel.id && isColliding(tempDragged, panel)\n );\n\n let updatedPanels = panelList;\n if (preventCollision && isOverlap) {\n updatedPanels = resolveCollisions(tempDragged, panelList);\n } else {\n updatedPanels = panelList.map((panel) =>\n panel.id === draggedPanel.id ? { ...panel, x, y } : panel\n );\n }\n\n const hasChanged = updatedPanels.some((panel, i) => {\n const prev = panelList[i];\n return (\n panel.x !== prev.x ||\n panel.y !== prev.y ||\n panel.w !== prev.w ||\n panel.h !== prev.h\n );\n });\n\n if (hasChanged) {\n requestAnimationFrame(() => {\n setPanelList(updatedPanels);\n onLayoutChange?.(updatedPanels);\n setDragOverPosition(null);\n lastDragOverPosRef.current = null;\n });\n } else {\n setDragOverPosition(null);\n lastDragOverPosRef.current = null;\n }\n };\n\n /**\n * 드래그 중인 패널의 스타일을 계산하는 함수\n */\n const dropIndicatorElement = useMemo(() => {\n if (!dragOverPosition) return null;\n\n return (\n <div\n className={styles.dropIndicator}\n style={calculatePanelStyle({\n dimensions: dragOverPosition,\n unitWidth,\n rowHeight,\n cols,\n marginX: margin[0],\n marginY: margin[1],\n paddingX: padding[0],\n paddingY: padding[1],\n })}\n />\n );\n }, [dragOverPosition, unitWidth, rowHeight, cols, margin, padding]);\n\n\n /**\n * 자식 요소들을 스타일링하여 반환하는 함수\n */\n const styledChildren = useMemo(() => {\n return childrenArray.map((child, index) => {\n if (!isValidElement(child)) return null;\n\n const panel = panelList[index];\n const style = calculatePanelStyle({\n dimensions: {\n x: panel?.x,\n y: panel?.y,\n w: panel?.w,\n h: panel?.h,\n },\n unitWidth,\n rowHeight,\n cols,\n marginX: margin[0],\n marginY: margin[1],\n paddingX: padding[0],\n paddingY: padding[1],\n });\n\n const element = child as React.ReactElement<GridChildProps>;\n\n return cloneElement(element, {\n 'data-testid': 'grid-panel',\n key: panel?.id || `default-key-${index}`,\n style: { ...style, ...(element.props.style || {}) },\n className: [styles.gridItemPanel, element.props.className]\n .filter(Boolean)\n .join(' '),\n draggable: isDraggable !== false,\n onDragStart: isDraggable !== false ? () => handleDragStart(panel) : undefined,\n onDrag: isDraggable !== false ? handleDrag : undefined,\n onDragEnd: isDraggable !== false ? handleDrop : undefined,\n });\n });\n }, [\n childrenArray,\n panelList,\n unitWidth,\n rowHeight,\n cols,\n margin,\n padding,\n ]);\n\n\n return (\n <div\n ref={containerRef}\n style={{ height: containerHeight, width }}\n className={styles.gridContainer}\n data-testid=\"grid-container\"\n >\n {dropIndicatorElement}\n {styledChildren}\n </div>\n );\n};\n\nexport default PanelGrid;",".gridContainer {\n position: relative;\n}\n\n.gridItemPanel {\n position: absolute;\n}\n\n.dropIndicator {\n position: absolute;\n background-color: rgba(0, 128, 255, 0.2);\n border: 2px dashed #007aff;\n border-radius: 6px;\n pointer-events: none;\n z-index: 999;\n}","import { Panel } from \"../types/types\";\n\n/**\n * container 내부로만 배치되도록 제한 (좌표 제한)\n * @return container 내부 좌표 최대값을 넘어갈 경우 최대값으로 반환\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n};\n\n/**\n * 두 패널이 겹치는지 여부를 반환합니다.\n * @returns true = 겹침, false = 겹치지 않음\n */\nexport function isColliding(panel1: Panel, panel2: Panel): boolean {\n const a = { x: 0, y: 0, w: 0, h: 0, ...panel1 };\n const b = { x: 0, y: 0, w: 0, h: 0, ...panel2 };\n\n return !(\n a.x + a.w <= b.x ||\n a.x >= b.x + b.w ||\n a.y + a.h <= b.y ||\n a.y >= b.y + b.h\n );\n};","import { Panel } from \"../types/types\";\nimport { clamp, isColliding } from \"./utils\";\n\ntype Dimension = Pick<Panel, 'w' | 'h' | 'x' | 'y'>;\ninterface CalculatePanelStyleProps {\n /** 패널의 위치와 크기 정보 (x, y, w, h) */\n dimensions: Dimension;\n /** 1 grid column 당 픽셀 너비 */\n unitWidth: number;\n /** 1 grid row 당 픽셀 높이 */\n rowHeight: number;\n\n /** grid 컬럼 수 (x 범위 제한에 사용) - 기본값: 12*/\n cols?: number;\n\n /**\n * 각 grid column 사이의 수평 margin(px) — 기본값: 0 \n * → x축 방향으로 패널 간 간격을 두고 싶을 때 사용\n */\n marginX?: number;\n\n /**\n * 각 grid row 사이의 수직 margin(px) — 기본값: 0 \n * → y축 방향으로 패널 간 간격을 두고 싶을 때 사용\n */\n marginY?: number;\n\n /**\n * 전체 grid의 왼쪽 padding(px) — 기본값: 0 \n * → 전체 컨테이너에서 좌측 여백을 지정할 때 사용\n */\n paddingX?: number;\n\n /**\n * 전체 grid의 상단 padding(px) — 기본값: 0 \n * → 전체 컨테이너에서 상단 여백을 지정할 때 사용\n */\n paddingY?: number;\n}\ninterface CalculateContainerHeightProps {\n /** 렌더링할 패널 배열 */\n panels: Panel[];\n\n /** 한 row의 픽셀 높이 */\n rowHeight: number;\n\n /** 각 row 간의 수직 마진 (px) */\n marginY?: number;\n\n /** 컨테이너의 상하 padding 값 (px) */\n paddingY?: number;\n}\ninterface DropPositionConfig {\n /**\n * grid 컨테이너의 DOM 좌표 (client 기준 좌상단 좌표와 너비/높이 정보 포함)\n * → 마우스 위치를 grid 내부 좌표로 환산하기 위해 필요\n */\n containerRect: DOMRect;\n\n /**\n * 1 grid column의 너비 (px)\n * → 마우스 위치를 컬럼 단위로 환산할 때 사용\n */\n unitWidth: number;\n\n /**\n * 1 grid row의 높이 (px)\n * → 마우스 위치를 row 단위로 환산할 때 사용\n */\n rowHeight: number;\n\n /**\n * [왼쪽 padding, 위쪽 padding] (px)\n * → 전체 grid의 바깥쪽 여백 (container 기준 내부 시작 위치 보정용)\n */\n padding: [number, number];\n\n /**\n * [컬럼 간 간격, 행 간 간격] (px)\n * → 각 grid 요소 사이의 간격 (패널 사이 여백 포함)\n */\n margin: [number, number];\n\n /**\n * grid의 총 컬럼 수\n * → 배치 가능한 x 범위를 제한하는데 사용\n */\n cols: number;\n\n /**\n * grid에서 배치 가능한 최대 row 수\n * → 배치 가능한 y 범위를 제한하는데 사용\n */\n maxRows: number;\n\n /**\n * 드래그 중인 패널의 크기 (w: 너비 컬럼 수, h: 높이 row 수)\n * → 현재 좌표가 이 panel을 담을 수 있는 공간인지 판단하기 위해 필요\n */\n panelSize: {\n w: number;\n h: number;\n };\n}\ninterface DropPositionInput {\n /**\n * 드래그 종료 시 전달되는 마우스 이벤트 (clientX, clientY 좌표 포함)\n */\n event: React.DragEvent<HTMLDivElement>;\n\n /**\n * 위치 계산에 필요한 컨텍스트 정보\n */\n config: DropPositionConfig;\n}\n\n\n/**\n * 개별 패널의 style 객체를 계산하는 함수\n *\n * @returns React에서 사용할 수 있는 객체\n */\nexport function calculatePanelStyle({\n dimensions: { x = 0, y = 0, w = 1, h = 1 },\n unitWidth,\n rowHeight,\n cols = 12,\n marginX = 0,\n marginY = 0,\n paddingX = 0,\n paddingY = 0,\n}: CalculatePanelStyleProps) {\n const clampedX = Math.min(x, cols - w);\n\n const left = clampedX * (unitWidth + marginX) + paddingX;\n const top = y * (rowHeight + marginY) + paddingY;\n const width = unitWidth * w + marginX * (w - 1);\n const height = rowHeight * h + marginY * (h - 1);\n\n return {\n transform: `translate(${left}px, ${top}px)`,\n width,\n height\n };\n}\n\n/**\n * 전체 grid container의 높이를 계산하는 함수\n * 모든 패널의 y+h 중 최댓값을 기준으로 계산합니다.\n *\n * @returns 컨테이너 전체 높이 (px 단위)\n */\nexport function calculateContainerHeight({\n panels,\n rowHeight,\n marginY = 0,\n paddingY = 0,\n}: CalculateContainerHeightProps): number {\n if (panels.length === 0) return 0;\n const maxY = Math.max(...panels.map(p => (p?.y || 0) + (p?.h || 1)));\n\n return (\n maxY * rowHeight +\n (maxY - 1) * marginY +\n paddingY * 2\n );\n};\n\n\n/**\n * 마우스 좌표를 기반으로 grid 좌표를 계산합니다.\n * @returns grid 상의 x, y 좌표\n */\nexport function getDropPosition({\n event,\n config: {\n containerRect,\n unitWidth,\n rowHeight,\n padding,\n margin,\n cols,\n maxRows,\n panelSize,\n },\n}: DropPositionInput): { x: number; y: number } {\n const mouseLeft = event.clientX - containerRect.left;\n const mouseTop = event.clientY - containerRect.top;\n\n const panelWidth = unitWidth * panelSize.w + margin[0] * (panelSize.w - 1);\n const panelHeight = rowHeight * panelSize.h + margin[1] * (panelSize.h - 1);\n\n const adjustedLeft = mouseLeft - padding[0] - panelWidth / 2;\n const adjustedTop = mouseTop - padding[1] - panelHeight / 2;\n\n const newX = Math.round(adjustedLeft / (unitWidth + margin[0]));\n const newY = Math.round(adjustedTop / (rowHeight + margin[1]));\n\n const maxX = cols - panelSize.w;\n const maxY = maxRows - panelSize.h;\n\n return {\n x: clamp(newX, 0, maxX),\n y: clamp(newY, 0, maxY),\n };\n}\n\n/**\n * 드롭된 패널을 해당 위치에 고정시키고,\n * 충돌하는 다른 패널들을 재귀적으로 아래로 밀어주는 함수\n * \n * @param draggedPanel 드롭 위치가 적용된 이동된 패널\n * @param panels 기존 패널 배열 (draggedPanel 포함)\n * @returns 충돌이 해결된 새로운 패널 배열\n */\nexport function resolveCollisions(\n draggedPanel: Panel,\n panels: Panel[]\n): Panel[] {\n const newPanels = panels.map(p =>\n p.id === draggedPanel.id ? draggedPanel : { ...p }\n );\n\n const visited = new Set<string>();\n\n function pushDown(panel: Panel) {\n if (!panel.id) return;\n if (visited.has(panel.id)) return;\n visited.add(panel.id);\n\n for (const other of newPanels) {\n if (panel.id === other.id) continue;\n\n if (isColliding(panel, other)) {\n // 겹치는 패널을 아래로 한 칸 밀기\n other.y = (panel.y || 0) + (panel.h || 1);\n pushDown(other); // 재귀적으로 처리\n }\n }\n }\n\n pushDown(draggedPanel);\n\n return newPanels;\n}"],"mappings":"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","names":["PanelGrid_default"]}