diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..d1d3a94
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,23 @@
+{
+ "editor.quickSuggestions": {
+ "strings": true
+ },
+ "css.validate": false,
+ "tailwindCSS.includeLanguages": {
+ "typescript": "javascript",
+ "typescriptreact": "javascript"
+ },
+ "files.associations": {
+ "*.css": "tailwindcss"
+ },
+ "tailwindCSS.experimental.classRegex": [
+ ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
+ ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"],
+ ["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"],
+ [
+ "resizeHandleClasses\\s*=\\s*\\{\\{([\\s\\S]*?)\\}\\}",
+ "[\"'`]([^\"'`]*).*?[\"'`]"
+ ]
+ ],
+ "todo-tree.tree.disableCompactFolders": true
+}
diff --git a/apps/editor/db.json b/apps/editor/db.json
index d16b921..2322c07 100644
--- a/apps/editor/db.json
+++ b/apps/editor/db.json
@@ -1,11 +1,12 @@
{
"nodes": [
{
- "id": 1001,
+ "id": "1001",
"page_id": 201,
"parent_id": null,
"type": "Hero",
"position": 0,
+ "layout": { "x": 0, "y": 0, "width": 1440, "height": 600, "zIndex": 1 },
"props": {
"heading": "WebCreator-X에 오신 것을 환영합니다",
"subheading": "세상에 하나뿐인 당신의 웹사이트를 만들어보세요.",
@@ -35,11 +36,12 @@
"created_at": "2025-11-13T06:18:00Z"
},
{
- "id": 1002,
+ "id": "1002",
"page_id": 201,
"parent_id": null,
"type": "Container",
"position": 1,
+ "layout": { "x": 0, "y": 600, "width": 1440, "height": 800, "zIndex": 1 },
"props": {},
"style": {
"layout": { "display": "block" },
@@ -54,11 +56,12 @@
"created_at": "2025-11-13T06:20:00Z"
},
{
- "id": 1003,
+ "id": "1003",
"page_id": 201,
- "parent_id": 1002,
+ "parent_id": "1002",
"type": "Heading",
"position": 0,
+ "layout": { "x": 100, "y": 50, "width": 1200, "height": 100, "zIndex": 2 },
"props": {
"text": "주요 기능 소개",
"level": "h2"
@@ -74,11 +77,12 @@
"created_at": "2025-11-13T06:21:00Z"
},
{
- "id": 1004,
+ "id": "1004",
"page_id": 201,
- "parent_id": 1002,
+ "parent_id": "1002",
"type": "Text",
"position": 1,
+ "layout": { "x": 100, "y": 150, "width": 800, "height": 200, "zIndex": 2 },
"props": {
"text": "직관적인 드래그 앤 드롭 인터페이스로 코딩 없이 웹사이트를 완성하세요. 모든 컴포넌트는 사용자가 원하는 대로 커스터마이징할 수 있습니다."
},
@@ -94,11 +98,12 @@
"created_at": "2025-11-13T06:22:00Z"
},
{
- "id": 1005,
+ "id": "1005",
"page_id": 201,
"parent_id": null,
"type": "Container",
"position": 2,
+ "layout": { "x": 0, "y": 1400, "width": 1440, "height": 600, "zIndex": 1 },
"props": { "id": "gallery" },
"style": {
"layout": {
@@ -117,11 +122,12 @@
"created_at": "2025-11-13T06:23:00Z"
},
{
- "id": 1006,
+ "id": "1006",
"page_id": 201,
- "parent_id": 1005,
+ "parent_id": "1005",
"type": "Image",
"position": 0,
+ "layout": { "x": 50, "y": 50, "width": 600, "height": 300, "zIndex": 2 },
"props": {
"src": "https://images.example.com/gallery-1.jpg",
"alt": "갤러리 이미지 1"
@@ -137,11 +143,12 @@
"created_at": "2025-11-13T06:24:00Z"
},
{
- "id": 1007,
+ "id": "1007",
"page_id": 201,
- "parent_id": 1005,
+ "parent_id": "1005",
"type": "Image",
"position": 1,
+ "layout": { "x": 700, "y": 50, "width": 600, "height": 300, "zIndex": 2 },
"props": {
"src": "https://images.example.com/gallery-2.jpg",
"alt": "갤러리 이미지 2"
@@ -157,11 +164,12 @@
"created_at": "2025-11-13T06:25:00Z"
},
{
- "id": 1008,
+ "id": "1008",
"page_id": 201,
- "parent_id": 1005,
+ "parent_id": "1005",
"type": "Text",
"position": 2,
+ "layout": { "x": 50, "y": 360, "width": 600, "height": 50, "zIndex": 2 },
"props": {
"text": "이미지 설명 1"
},
@@ -176,11 +184,12 @@
"created_at": "2025-11-13T06:26:00Z"
},
{
- "id": 1009,
+ "id": "1009",
"page_id": 201,
- "parent_id": 1005,
+ "parent_id": "1005",
"type": "Text",
"position": 3,
+ "layout": { "x": 700, "y": 360, "width": 600, "height": 50, "zIndex": 2 },
"props": {
"text": "이미지 설명 2"
},
@@ -195,11 +204,12 @@
"created_at": "2025-11-13T06:27:00Z"
},
{
- "id": 1010,
+ "id": "1010",
"page_id": 201,
"parent_id": null,
"type": "Button",
"position": 3,
+ "layout": { "x": 600, "y": 2100, "width": 200, "height": 60, "zIndex": 1 },
"props": {
"text": "더 알아보기",
"link": "/features"
diff --git a/apps/editor/package.json b/apps/editor/package.json
index e2d9611..7f23dda 100644
--- a/apps/editor/package.json
+++ b/apps/editor/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@repo/ui": "workspace:*",
"@tanstack/react-query": "^5.90.8",
+ "immer": "^11.1.3",
"json-server": "^1.0.0-beta.3",
"next": "15.5.9",
"react": "19.1.0",
diff --git a/apps/editor/src/styles.css b/apps/editor/src/styles.css
new file mode 100644
index 0000000..f1d8c73
--- /dev/null
+++ b/apps/editor/src/styles.css
@@ -0,0 +1 @@
+@import "tailwindcss";
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 45b7be5..4c6e5a1 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -2,7 +2,8 @@
"name": "@repo/ui",
"version": "0.0.0",
"exports": {
- "./renderer": "./src/renderer/NodeRenderer.tsx"
+ "./renderer": "./src/renderer/NodeRenderer.tsx",
+ "./*": "./src/*"
},
"scripts": {
"type-check": "tsc --noEmit"
@@ -12,12 +13,17 @@
"react": "19.1.0"
},
"devDependencies": {
+ "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4",
"typescript": "^5"
},
"dependencies": {
- "framer-motion": "^12.23.26"
+ "clsx": "^2.1.1",
+ "framer-motion": "^12.23.26",
+ "react-rnd": "^10.5.2"
}
}
diff --git a/packages/ui/postcss.config.mjs b/packages/ui/postcss.config.mjs
new file mode 100644
index 0000000..c7bcb4b
--- /dev/null
+++ b/packages/ui/postcss.config.mjs
@@ -0,0 +1,5 @@
+const config = {
+ plugins: ["@tailwindcss/postcss"],
+};
+
+export default config;
diff --git a/packages/ui/src/core/BaseLayoutWrapper.tsx b/packages/ui/src/core/BaseLayoutWrapper.tsx
index 4946a1e..0f3c7ef 100644
--- a/packages/ui/src/core/BaseLayoutWrapper.tsx
+++ b/packages/ui/src/core/BaseLayoutWrapper.tsx
@@ -5,6 +5,7 @@ interface Props {
node: BaseNode;
}
+//라이브 모드에서는 항상 사용, 에디터 모드에서는 상황에 따라 선택적으로 사용됩니다.
export default function BaseLayoutNodeWrapper({ children, node }: Props) {
const { x: top, y: left, width, height, zIndex } = node.layout;
return (
diff --git a/packages/ui/src/core/DraggableNodeWrapper.tsx b/packages/ui/src/core/DraggableNodeWrapper.tsx
deleted file mode 100644
index b896ee4..0000000
--- a/packages/ui/src/core/DraggableNodeWrapper.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-//에디터 모드전용 노드 렌더러 래퍼 컴포넌트
-import { BaseNode } from "types";
-
-interface WrapperProps {
- children: React.ReactNode;
- node: BaseNode;
- isSelected: boolean;
- onDragStart: (e: React.MouseEvent, id: string) => void;
-}
-
-export default function DraggableNodeWrapper({
- children,
- node,
- isSelected,
- onDragStart,
-}: WrapperProps) {
- const wrapperStyle: React.CSSProperties = {
- position: "absolute", // 핵심: 캔버스 내에서 자유 배치
- left: node["layout"].x,
- top: node["layout"].y,
- width: node["layout"].width,
- height: node["layout"].height,
- zIndex: node["layout"].zIndex,
-
- // 선택되었을 때 시각적 피드백 (테두리 등)
- outline: isSelected ? "2px solid blue" : "none",
- cursor: "move",
- };
-
- return (
-
onDragStart(e, node.id)}
- className="" //TODO - 필요시 클래스네임 추가(dnd상황에서 스타일 강조 등)
- >
- {/* 실제 컴포넌트(Hero 등)는 이 안에 렌더링됨 */}
- {children}
-
- {/* 리사이즈 핸들 등은 에디터 모드에서만 오버레이로 표시 */}
- {/* TODO-추후 호버시 리사이즈 핸들 렌더링 하도록 수정 필요! */}
- {isSelected && (
- <>
-
- {/* TODO-기타 리사이즈 핸들들 렌더링은 이곳에서 ... */}
- >
- )}
-
- );
-}
diff --git a/packages/ui/src/core/EditorNodeWrapper.tsx b/packages/ui/src/core/EditorNodeWrapper.tsx
new file mode 100644
index 0000000..94df8c0
--- /dev/null
+++ b/packages/ui/src/core/EditorNodeWrapper.tsx
@@ -0,0 +1,103 @@
+//에디터 모드전용 노드 렌더러 래퍼 컴포넌트
+import clsx from "clsx";
+import { Rnd } from "react-rnd";
+import { BaseNode } from "types";
+import { CanvasState, Layer } from "types/rnd";
+
+interface WrapperProps {
+ children: React.ReactNode;
+ node: BaseNode;
+ selectedId: string;
+ updateNode: (id: string, updates: Partial) => void; //노드의 레이아웃 업데이트 함수 from editor의 스토어 액션
+ selectNode: (id: string) => void;
+ canvas: CanvasState;
+}
+
+//에디터 전용 노드 렌더러 래퍼
+//래퍼 컴포넌트에서 rnd작업 발생할때 현재 액션이 일어나는 노드 id는 몰라도 될듯? -> 항상 스토어의 selectedId를 기준으로 데이터를 수정하면 된다.
+export default function EditorNodeWrapper({
+ children,
+ node,
+ selectedId,
+ updateNode,
+ selectNode,
+ canvas,
+}: WrapperProps) {
+ const isSelected = selectedId === node.id;
+ const wrapperStyle: React.CSSProperties = {
+ // 선택되었을 때 시각적 피드백 (테두리 등)
+ outline: isSelected ? "outline outline-gray-500 outline-2" : "none",
+ cursor: "move",
+ };
+
+ const { id } = node;
+ const { width, height, x, y } = node.layout;
+ const selectedNodeGuideClasses = {
+ handle: "bg-white border rounded-full border-gray-500 !w-3 !h-3",
+ outline: "ring ring-2 ring-gray-500",
+ };
+
+ //TODO- 노드 선택 로직 구현, 선택 ID 공유하는 zustand 스토어 구현 필요
+
+ return (
+ e.stopPropagation()}
+ //TODO-일단 이동중에 스토어 업데이트는 미루기 -> 성능 이슈
+ // onDrag={(e, d) => updateNode(id, { x: d.x, y: d.y })}
+ onDragStop={(e, d) => updateNode(id, { x: d.x, y: d.y })}
+ onResizeStart={(e) => e.stopPropagation()}
+ //TODO-일단 리사이징중에 스토어 업데이트는 미루기 -> 성능 이슈
+ /*
+ onResize={(e, dir, ref, delta, pos) =>
+ updateNode(id, {
+ width: parseInt(ref.style.width),
+ height: parseInt(ref.style.height),
+ ...pos,
+ })
+ }
+ */
+ onResizeStop={(e, dir, ref, delta, pos) =>
+ updateNode(id, {
+ width: parseInt(ref.style.width),
+ height: parseInt(ref.style.height),
+ ...pos,
+ })
+ }
+ enableResizing={isSelected ? undefined : false}
+ disableDragging={!isSelected}
+ className={clsx("group cursor-pointer", isSelected && "z-50")}
+ resizeHandleClasses={{
+ bottomLeft: isSelected
+ ? clsx(selectedNodeGuideClasses.handle, "-left-1.5 -bottom-1.5")
+ : undefined,
+ bottomRight: isSelected
+ ? clsx(selectedNodeGuideClasses.handle, "-right-1.5 -bottom-1.5")
+ : undefined,
+ topLeft: isSelected
+ ? clsx(selectedNodeGuideClasses.handle, "-left-1.5 -top-1.5")
+ : undefined,
+ topRight: isSelected
+ ? clsx(selectedNodeGuideClasses.handle, "-right-1.5 -top-1.5")
+ : undefined,
+ }}
+ >
+ {
+ e.stopPropagation();
+ selectNode(id);
+ }}
+ style={wrapperStyle}
+ className={clsx(
+ "transition-shadow duration-200",
+ isSelected && selectedNodeGuideClasses.outline,
+ )}
+ >
+ {/* 실제 컴포넌트(Hero 등)는 이 안에 렌더링됨 */}
+ {children}
+
+
+ );
+}
diff --git a/packages/ui/src/core/LiveModeWrapper.tsx b/packages/ui/src/core/LiveModeWrapper.tsx
deleted file mode 100644
index 038836b..0000000
--- a/packages/ui/src/core/LiveModeWrapper.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { BaseNode } from "types";
-
-//라이브 모드에서 사용되는 노드 렌더러 래퍼 컴포넌트입니다.
-interface props {
- children: React.ReactNode;
- node: BaseNode;
-}
-
-export default function LiveModeWrapper({ children, node }: props) {
- const { x, y, width, height, zIndex } = node.layout;
-
- const wrapperStyle: React.CSSProperties = {
- position: "absolute",
- left: x,
- top: y,
- width,
- height,
- zIndex,
- };
- return {children}
;
-}
diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css
new file mode 100644
index 0000000..f1d8c73
--- /dev/null
+++ b/packages/ui/src/styles.css
@@ -0,0 +1 @@
+@import "tailwindcss";
diff --git a/packages/ui/src/types/nodeAction.ts b/packages/ui/src/types/nodeAction.ts
index f8ed6e3..7fb6ada 100644
--- a/packages/ui/src/types/nodeAction.ts
+++ b/packages/ui/src/types/nodeAction.ts
@@ -3,7 +3,7 @@ export type ActionType =
| "scroll" // 3. 스크롤 이동
| "alert" // 2. 알림창
| "modal" // 4. 모달 열기
- | "mutation"; // 1. 데이터 변경 (핵심!)
+ | "mutation"; // 1. 데이터 변경 (핵심!) -> 무시
export interface NodeAction {
type: ActionType;
diff --git a/packages/ui/src/types/rnd.ts b/packages/ui/src/types/rnd.ts
new file mode 100644
index 0000000..19c3bdc
--- /dev/null
+++ b/packages/ui/src/types/rnd.ts
@@ -0,0 +1,15 @@
+export interface Layer {
+ id: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ fill: string;
+ content?: string;
+}
+
+export interface CanvasState {
+ scale: number;
+ dx: number;
+ dy: number;
+}
diff --git a/packages/ui/src/utils/applyStyles.ts b/packages/ui/src/utils/applyStyles.ts
index b4d9c0c..0683ee2 100644
--- a/packages/ui/src/utils/applyStyles.ts
+++ b/packages/ui/src/utils/applyStyles.ts
@@ -16,17 +16,36 @@ export default function applyStyles(
): CSSProperties | undefined {
if (!styleData) return;
- const combinedStyles = {};
+ const combinedStyles: Record = {};
+
+ // Wrapper(부모)가 제어해야 할 레이아웃 속성 목록 (블랙리스트)
+ const LAYOUT_PROPERTIES = new Set([
+ "width",
+ "height",
+ "position",
+ "top",
+ "bottom",
+ "left",
+ "right",
+ "zIndex",
+ "transform",
+ "margin", // 마진도 레이아웃에 영향을 주므로 제외하는 것이 안전함
+ ]);
+
for (const key in styleData) {
//카테고리별로 중첩된 스타일 데이터를 평탄화 시킴.
//⭐️ className은 평탄화 작업에서 안전하게 제외합니다.
if (key !== "className" && typeof styleData[key] === "object") {
- Object.assign(combinedStyles, styleData[key]);
- //스프레드 연산자 오버헤드 위험
+ const categoryStyles = styleData[key] as Record;
+
+ for (const styleKey in categoryStyles) {
+ // 레이아웃 속성이면 건너뜀 (Wrapper가 담당)
+ if (LAYOUT_PROPERTIES.has(styleKey)) continue;
+
+ combinedStyles[styleKey] = categoryStyles[styleKey];
+ }
}
}
- // ... (다른 특수 CSS 속성 변환 로직 추후 구현) ...
-
return combinedStyles;
}
diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js
new file mode 100644
index 0000000..af60256
--- /dev/null
+++ b/packages/ui/tailwind.config.js
@@ -0,0 +1,10 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./src/**/*.{js,ts,jsx,tsx,mdx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 576aab3..686d608 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
'@tanstack/react-query':
specifier: ^5.90.8
version: 5.90.10(react@19.1.0)
+ immer:
+ specifier: ^11.1.3
+ version: 11.1.3
json-server:
specifier: ^1.0.0-beta.3
version: 1.0.0-beta.3
@@ -34,7 +37,7 @@ importers:
version: 19.1.0(react@19.1.0)
zustand:
specifier: ^5.0.8
- version: 5.0.8(@types/react@19.2.6)(react@19.1.0)
+ version: 5.0.8(@types/react@19.2.6)(immer@11.1.3)(react@19.1.0)
devDependencies:
'@eslint/eslintrc':
specifier: ^3
@@ -87,6 +90,9 @@ importers:
packages/ui:
dependencies:
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
framer-motion:
specifier: ^12.23.26
version: 12.23.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -96,7 +102,13 @@ importers:
react:
specifier: 19.1.0
version: 19.1.0
+ react-rnd:
+ specifier: ^10.5.2
+ version: 10.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
devDependencies:
+ '@tailwindcss/postcss':
+ specifier: ^4
+ version: 4.1.17
'@types/node':
specifier: ^20
version: 20.19.25
@@ -106,6 +118,12 @@ importers:
'@types/react-dom':
specifier: ^19
version: 19.2.3(@types/react@19.2.6)
+ postcss:
+ specifier: ^8.5.6
+ version: 8.5.6
+ tailwindcss:
+ specifier: ^4
+ version: 4.1.17
typescript:
specifier: ^5
version: 5.9.3
@@ -902,6 +920,14 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ clsx@1.2.1:
+ resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
+ engines: {node: '>=6'}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1317,6 +1343,9 @@ packages:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
+ immer@11.1.3:
+ resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==}
+
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@@ -1832,14 +1861,32 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ re-resizable@6.11.2:
+ resolution: {integrity: sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==}
+ peerDependencies:
+ react: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react-dom@19.1.0:
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
peerDependencies:
react: ^19.1.0
+ react-draggable@4.4.6:
+ resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==}
+ peerDependencies:
+ react: '>= 16.3.0'
+ react-dom: '>= 16.3.0'
+
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ react-rnd@10.5.2:
+ resolution: {integrity: sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==}
+ peerDependencies:
+ react: '>=16.3.0'
+ react-dom: '>=16.3.0'
+
react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
@@ -2050,6 +2097,9 @@ packages:
tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+ tslib@2.6.2:
+ resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -2926,6 +2976,10 @@ snapshots:
client-only@0.0.1: {}
+ clsx@1.2.1: {}
+
+ clsx@2.1.1: {}
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -3473,6 +3527,8 @@ snapshots:
ignore@7.0.5: {}
+ immer@11.1.3: {}
+
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@@ -3917,13 +3973,33 @@ snapshots:
queue-microtask@1.2.3: {}
+ re-resizable@6.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
react-dom@19.1.0(react@19.1.0):
dependencies:
react: 19.1.0
scheduler: 0.26.0
+ react-draggable@4.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ clsx: 1.2.1
+ prop-types: 15.8.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
react-is@16.13.1: {}
+ react-rnd@10.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ re-resizable: 6.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-draggable: 4.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ tslib: 2.6.2
+
react@19.1.0: {}
readdirp@4.1.2: {}
@@ -4197,6 +4273,8 @@ snapshots:
minimist: 1.2.8
strip-bom: 3.0.0
+ tslib@2.6.2: {}
+
tslib@2.8.1: {}
turbo-darwin-64@2.6.1:
@@ -4353,7 +4431,8 @@ snapshots:
yocto-queue@0.1.0: {}
- zustand@5.0.8(@types/react@19.2.6)(react@19.1.0):
+ zustand@5.0.8(@types/react@19.2.6)(immer@11.1.3)(react@19.1.0):
optionalDependencies:
'@types/react': 19.2.6
+ immer: 11.1.3
react: 19.1.0