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