Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -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
}
42 changes: 26 additions & 16 deletions apps/editor/db.json
Original file line number Diff line number Diff line change
@@ -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": "세상에 ν•˜λ‚˜λΏμΈ λ‹Ήμ‹ μ˜ μ›Ήμ‚¬μ΄νŠΈλ₯Ό λ§Œλ“€μ–΄λ³΄μ„Έμš”.",
Expand Down Expand Up @@ -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" },
Expand All @@ -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"
Expand All @@ -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": "직관적인 λ“œλž˜κ·Έ μ•€ λ“œλ‘­ μΈν„°νŽ˜μ΄μŠ€λ‘œ μ½”λ”© 없이 μ›Ήμ‚¬μ΄νŠΈλ₯Ό μ™„μ„±ν•˜μ„Έμš”. λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈλŠ” μ‚¬μš©μžκ°€ μ›ν•˜λŠ” λŒ€λ‘œ μ»€μŠ€ν„°λ§ˆμ΄μ§•ν•  수 μžˆμŠ΅λ‹ˆλ‹€."
},
Expand All @@ -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": {
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"
},
Expand All @@ -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"
},
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions apps/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions apps/editor/src/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "tailwindcss";
10 changes: 8 additions & 2 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
}
}
5 changes: 5 additions & 0 deletions packages/ui/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};

export default config;
1 change: 1 addition & 0 deletions packages/ui/src/core/BaseLayoutWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
49 changes: 0 additions & 49 deletions packages/ui/src/core/DraggableNodeWrapper.tsx

This file was deleted.

103 changes: 103 additions & 0 deletions packages/ui/src/core/EditorNodeWrapper.tsx
Original file line number Diff line number Diff line change
@@ -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<Layer>) => 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 (
<Rnd
size={{ width, height }}
position={{ x, y }}
scale={canvas.scale}
onDragStart={(e) => 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,
}}
>
<div
onClick={(e) => {
e.stopPropagation();
selectNode(id);
}}
style={wrapperStyle}
className={clsx(
"transition-shadow duration-200",
isSelected && selectedNodeGuideClasses.outline,
)}
>
{/* μ‹€μ œ μ»΄ν¬λ„ŒνŠΈ(Hero λ“±)λŠ” 이 μ•ˆμ— λ Œλ”λ§λ¨ */}
{children}
</div>
</Rnd>
);
}
21 changes: 0 additions & 21 deletions packages/ui/src/core/LiveModeWrapper.tsx

This file was deleted.

Loading