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
189 changes: 189 additions & 0 deletions .notes/troubleshooting/stack-item-resize-drag-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# νŠΈλŸ¬λΈ”μŠˆνŒ…: Stack Item λ¦¬μ‚¬μ΄μ¦ˆ ν›„ λ“œλž˜κ·Έ μ‹œ λ…Έλ“œ 점프 버그

> **λ°œμƒμΌ**: 2026-02-25
> **파일**: `packages/ui/src/core/EditorNodeWrapper.tsx`
> **라이브러리**: `react-rnd@10.5.2`

---

## 1. 버그 증상

Stack λ‚΄λΆ€μ˜ `position: relative` μ•„μ΄ν…œ λ…Έλ“œλ₯Ό **λ¦¬μ‚¬μ΄μ¦ˆν•œ ν›„ λ“œλž˜κ·Έλ₯Ό μ‹œμž‘ν•˜λ©΄**, λ…Έλ“œκ°€ μ—‰λš±ν•œ μœ„μΉ˜λ‘œ μˆœκ°„μ΄λ™(점프)ν•˜λŠ” ν˜„μƒμ΄ λ°œμƒν–ˆλ‹€.

- λ¦¬μ‚¬μ΄μ¦ˆ μ€‘μ—λŠ” μ •μƒμ μœΌλ‘œ flexbox 정렬을 따름
- λ“œλž˜κ·Έλ₯Ό μ‹œμž‘ν•˜λŠ” μˆœκ°„λ§Œ 점프 λ°œμƒ
- λ¦¬μ‚¬μ΄μ¦ˆ 없이 λ°”λ‘œ λ“œλž˜κ·Έν•˜λ©΄ 정상 λ™μž‘

---

## 2. 원인 뢄석

### 2-1. react-rnd의 μœ„μΉ˜ μ œμ–΄ 방식

`react-rnd`λŠ” λ…Έλ“œμ˜ μœ„μΉ˜λ₯Ό **CSS `transform: translate(x, y)`** 둜 μ œμ–΄ν•œλ‹€.
`position` prop에 `{ x, y }` 값을 μ „λ‹¬ν•˜λ©΄, λ‚΄λΆ€μ μœΌλ‘œ ν•΄λ‹Ή 값을 `transform`에 λ°˜μ˜ν•œλ‹€.

### 2-2. Stack Item의 특수 처리 (`!transform-none`)

Stack λ‚΄λΆ€ μ•„μ΄ν…œ(`position: relative`)은 Flexbox 정렬을 μœ μ§€ν•΄μ•Ό ν•˜λ―€λ‘œ,
`isTransformActive`κ°€ `false`일 λ•Œ `!transform-none` CSS 클래슀λ₯Ό κ°•μ œ μ μš©ν•΄
react-rnd의 transform을 무λ ₯ν™”ν•˜κ³  μžˆμ—ˆλ‹€.

```tsx
className={clsx(
"group cursor-pointer",
hasRelativePosition && !isTransformActive && "!transform-none",
)}
```

λ“œλž˜κ·Έ μ‹œμž‘(`onDragStart`) μ‹œμ—λŠ” `setIsTransformActive(true)`둜 이 ν΄λž˜μŠ€κ°€ 제거되고,
react-rnd의 transform이 λ‹€μ‹œ ν™œμ„±ν™”λœλ‹€.

### 2-3. λ²„κ·Έμ˜ 핡심: λ¦¬μ‚¬μ΄μ¦ˆ 쀑 λ‚΄λΆ€ position λˆ„μ 

**`!transform-none`이 μ μš©λ˜μ–΄ DOMμ—μ„œλŠ” transform이 보이지 μ•Šλ”λΌλ„,
react-rnd λΌμ΄λΈŒλŸ¬λ¦¬λŠ” λ¦¬μ‚¬μ΄μ¦ˆ 쀑에 λ‚΄λΆ€ position μƒνƒœλ₯Ό 계속 λˆ„μ  λ³€κ²½ν•˜κ³  μžˆλ‹€.**

특히 **top λ°©ν–₯ / left λ°©ν–₯ ν•Έλ“€**둜 λ¦¬μ‚¬μ΄μ¦ˆν•  경우, μš”μ†Œκ°€ μ‹œκ°μ μœΌλ‘œ 이동해야 ν•˜λ―€λ‘œ
react-rndκ°€ λ‚΄λΆ€μ μœΌλ‘œ `translate(x, y)` 값을 계산해 λ‚΄λΆ€ ref에 μ €μž₯ν•œλ‹€.

```
λ¦¬μ‚¬μ΄μ¦ˆ μ „: λ‚΄λΆ€ position = { x: 0, y: 0 }
↓ top-left λ°©ν–₯으둜 λ¦¬μ‚¬μ΄μ¦ˆ
λ¦¬μ‚¬μ΄μ¦ˆ ν›„: λ‚΄λΆ€ position = { x: -50, y: -30 } ← λˆ„μ λ¨!
(DOMμ—μ„œλŠ” !transform-none으둜 κ°€λ €μ Έ μžˆμ–΄μ„œ 보이지 μ•ŠμŒ)
```

### 2-4. λ“œλž˜κ·Έ μ‹œμž‘ μ‹œ 점프 λ°œμƒ 흐름

```
1. onDragStart μ‹€ν–‰
2. setIsTransformActive(true) β†’ !transform-none 클래슀 제거
3. react-rndκ°€ λ¦¬μ‚¬μ΄μ¦ˆ 쀑 λˆ„μ λœ λ‚΄λΆ€ position { x: -50, y: -30 }을
transform에 κ·ΈλŒ€λ‘œ 반영 β†’ λ…Έλ“œ 점프! πŸ’₯
4. d.node.offsetLeft / offsetTop 읽음
β†’ 이미 νŠ€μ–΄λ²„λ¦° μœ„μΉ˜ κΈ°μ€€μœΌλ‘œ μ’Œν‘œλ₯Ό 읽음 β†’ 잘λͺ»λœ μ’Œν‘œλ‘œ 이동
```

---

## 3. μ‹œλ„ν•œ ν•΄κ²°μ±…κ³Ό μ™œ μ‹€νŒ¨ν–ˆλŠ”κ°€

### ❌ μ‹œλ„ 1: `onResizeStop`μ—μ„œ position μ΄ˆκΈ°ν™”

```tsx
// onResizeStop λ‚΄λΆ€
if (hasRelativePosition) {
setDragPosition({ x: 0, y: 0 });
rndRef.current?.updatePosition({ x: 0, y: 0 });
}
```

**μ‹€νŒ¨ 원인:**
`onResizeStop`μ—μ„œ μ΄ˆκΈ°ν™”ν•΄λ„, 이후 μ‚¬μš©μžκ°€ λ¦¬μ‚¬μ΄μ¦ˆ 핸듀을 λ‹€μ‹œ μ‘°μž‘ν•˜λ©΄
λ˜λ‹€μ‹œ λ‚΄λΆ€ position이 λˆ„μ λœλ‹€.
κ²°μ •μ μœΌλ‘œ, `onResizeStop` 이후와 `onDragStart` 사이에 μ‚¬μš©μž μΈν„°λž™μ…˜μ΄ 없어도
react-rnd λ‚΄λΆ€μ—μ„œ 좔가적인 position λ³€ν™”κ°€ 일어날 수 μžˆμ–΄ 근본적인 해결이 μ–΄λ ΅λ‹€.
즉, **"λ¦¬μ‚¬μ΄μ¦ˆκ°€ 끝날 λ•Œ" μ΄ˆκΈ°ν™”λŠ” 타이밍이 λ§žμ§€ μ•ŠλŠ”λ‹€.**

---

## 4. μ΅œμ’… ν•΄κ²°μ±…

### βœ… `onDragStart`μ—μ„œ 항상 λ¨Όμ € μ΄ˆκΈ°ν™”

λ“œλž˜κ·Έκ°€ μ‹œμž‘λ˜λŠ” λ°”λ‘œ κ·Έ μ‹œμ , **transform이 ν™œμ„±ν™”λ˜κΈ° 직전**에
`rndRef.current?.updatePosition({ x: 0, y: 0 })`을 ν˜ΈμΆœν•΄
react-rnd λ‚΄λΆ€ position을 κ°•μ œλ‘œ `{x:0, y:0}`으둜 μ΄ˆκΈ°ν™”ν•œλ‹€.

```tsx
onDragStart={(e, d) => {
e.stopPropagation();
setDraggingId(id);

if (hasRelativePosition) {
// βœ… 핡심: isTransformActiveλ₯Ό true둜 λ°”κΎΈκΈ° 전에
// react-rnd λ‚΄λΆ€ position을 λ°˜λ“œμ‹œ λ¨Όμ € 리셋해야 ν•œλ‹€.
// λ¦¬μ‚¬μ΄μ¦ˆ 쀑 λˆ„μ λœ λ‚΄λΆ€ position이 λ“œλž˜κ·Έ ν™œμ„±ν™”μ™€ λ™μ‹œμ—
// transform에 λ°˜μ˜λ˜μ–΄ λ…Έλ“œκ°€ νŠ€λŠ” 버그λ₯Ό λ°©μ§€.
rndRef.current?.updatePosition({ x: 0, y: 0 });

const { offsetLeft, offsetTop } = d.node;
setIsTransformActive(true);
// ...
}
}}
```

### μ™œ 이 방법이 λ™μž‘ν•˜λŠ”κ°€?

| 단계 | 이전(버그) | 이후(μˆ˜μ •) |
|---|---|---|
| λ¦¬μ‚¬μ΄μ¦ˆ 쀑 | λ‚΄λΆ€ position λˆ„μ  | λ‚΄λΆ€ position λˆ„μ  (동일) |
| `onDragStart` μ§„μž… | λˆ„μ λœ κ°’ κ·ΈλŒ€λ‘œ μœ μ§€ | `updatePosition(0,0)` 으둜 κ°•μ œ 리셋 |
| `setIsTransformActive(true)` | λˆ„μ λœ position이 transform에 반영 β†’ 점프 | λ‚΄λΆ€ position이 `{0,0}`μ΄λ―€λ‘œ μ•ˆμ „ν•˜κ²Œ transform ν™œμ„±ν™” |
| `offsetLeft/offsetTop` 읽기 | 잘λͺ»λœ μœ„μΉ˜ κΈ°μ€€ | μ˜¬λ°”λ₯Έ flexbox μœ„μΉ˜ κΈ°μ€€ |

**타이밍이 핡심이닀.** `setIsTransformActive(true)`둜 transform을 ν™œμ„±ν™”ν•˜κΈ° **이전에**
λ‚΄λΆ€ position을 μ΄ˆκΈ°ν™”ν•΄μ•Ό React의 λ Œλ”λ§ μ‚¬μ΄ν΄μ—μ„œ μ˜¬λ°”λ₯Έ μˆœμ„œκ°€ 보μž₯λœλ‹€.

---

## 5. 핡심 κ΅ν›ˆ

### "React μƒνƒœ μ΄ˆκΈ°ν™”"와 "라이브러리 λ‚΄λΆ€ μƒνƒœ μ΄ˆκΈ°ν™”"λŠ” λ‹€λ₯΄λ‹€

```
setDragPosition({ x: 0, y: 0 })
β†’ Reactκ°€ κ΄€λ¦¬ν•˜λŠ” state만 μ΄ˆκΈ°ν™”. react-rnd λ‚΄λΆ€ refμ—λŠ” 영ν–₯ μ—†μŒ.

rndRef.current?.updatePosition({ x: 0, y: 0 })
β†’ react-rndκ°€ λ‚΄λΆ€μ μœΌλ‘œ κ΄€λ¦¬ν•˜λŠ” DOM transform μƒνƒœκΉŒμ§€ 직접 μ΄ˆκΈ°ν™”.
```

react-rnd처럼 **uncontrolled λ°©μ‹μœΌλ‘œ DOM을 직접 μ‘°μž‘ν•˜λŠ” 라이브러리**λŠ”
React의 state/props와 λ³„λ„λ‘œ 자체적인 λ‚΄λΆ€ μƒνƒœλ₯Ό κ°€μ§„λ‹€.
Zustandλ‚˜ useState둜 아무리 μƒνƒœλ₯Ό 바꿔도, **라이브러리 λ‚΄λΆ€ μƒνƒœλŠ” λ³„λ„λ‘œ μ΄ˆκΈ°ν™”**ν•΄μ•Ό ν•œλ‹€.

### λ¦¬μ‚¬μ΄μ¦ˆ λ°©ν–₯에 따라 position이 λ³€ν•œλ‹€

react-rndλŠ” `bottom-right` λ°©ν–₯ λ¦¬μ‚¬μ΄μ¦ˆλ§Œ ν•  λ•ŒλŠ” position이 λ³€ν•˜μ§€ μ•ŠλŠ”λ‹€.
κ·ΈλŸ¬λ‚˜ **`top`, `left`, `top-left`, `top-right`, `bottom-left` λ°©ν–₯**으둜 λ¦¬μ‚¬μ΄μ¦ˆν•˜λ©΄
μš”μ†Œκ°€ λ°˜λŒ€ λ°©ν–₯으둜 이동해야 ν•˜λ―€λ‘œ λ‚΄λΆ€ position을 μžλ™μœΌλ‘œ λ³€κ²½ν•œλ‹€.
이 λ™μž‘μ€ CSS의 `transform-origin`κ³Ό λ‹€λ₯Έ, react-rnd 고유의 position 계산 방식이닀.

### μ΄ˆκΈ°ν™” 타이밍은 "ν™œμ„±ν™” 직전"이어야 ν•œλ‹€

- `onResizeStop`: λ„ˆλ¬΄ 이름 β€” 이후 또 λ‹€λ₯Έ μ‘°μž‘μ΄ λ°œμƒν•  수 있음
- `onDragStart` (transform ν™œμ„±ν™” 직전): βœ… κ°€μž₯ μ•ˆμ „ν•œ 타이밍

---

## 6. κ΄€λ ¨ μ½”λ“œ μœ„μΉ˜

| ν•­λͺ© | μœ„μΉ˜ |
|---|---|
| μˆ˜μ • 파일 | `packages/ui/src/core/EditorNodeWrapper.tsx` |
| μˆ˜μ • ν•¨μˆ˜ | `onDragStart` ν•Έλ“€λŸ¬ λ‚΄λΆ€ |
| 핡심 API | `rndRef.current?.updatePosition({ x: 0, y: 0 })` |
| react-rnd νƒ€μž… μ •μ˜ | `node_modules/react-rnd/lib/index.d.ts` |

---

## 7. μ΅œμ’… 적용 diff

```diff
onDragStart={(e, d) => {
e.stopPropagation();
setDraggingId(id);
if (hasRelativePosition) {
+ rndRef.current?.updatePosition({ x: 0, y: 0 }); // λ‚΄λΆ€ position κ°•μ œ 리셋
const { offsetLeft, offsetTop } = d.node;
setIsTransformActive(true);
- flushSync(() => {
- updateNode(id, { x: offsetLeft, y: offsetTop });
- setDragPosition({ x: offsetLeft, y: offsetTop });
- });
}
}}
```
10 changes: 4 additions & 6 deletions apps/editor/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"justifyContent": "center",
"gap": "20px",
"padding": "50px 24px",
"maxWidth": "1200px",
"backgroundColor": "#FFFFFF",
"className": "content-section"
},
Expand All @@ -28,8 +27,8 @@
"type": "Heading",
"position": 0,
"layout": {
"x": 0,
"y": 0,
"x": 1000,
"y": 1000,
"width": 1200,
"height": 100,
"zIndex": 2
Expand Down Expand Up @@ -94,7 +93,6 @@
"justifyContent": "center",
"gap": "20px",
"padding": "50px 24px",
"maxWidth": "1200px",
"backgroundColor": "#F9FAFB",
"className": "gallery-section"
},
Expand Down Expand Up @@ -133,8 +131,8 @@
},
"style": {
"position": "absolute",
"top": "50px",
"left": "700px",
"top": "0px",
"left": "0px",
"width": "600px",
"height": "300px",
"objectFit": "cover",
Expand Down
6 changes: 6 additions & 0 deletions apps/editor/src/components/editor/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import handleWheel from "@/utils/editor/handleWheel";
import { DragProvider } from "@repo/ui/context/dragContext";
import EditorNodeWrapper from "@repo/ui/core/EditorNodeWrapper";
import NodeRenderer from "@repo/ui/core/NodeRenderer";
import SelectionOverlay from "@repo/ui/core/SelectionOverlay";
import { WcxNode } from "@repo/ui/types/nodes";
import React, { useRef } from "react";

Expand Down Expand Up @@ -134,6 +135,11 @@ export default function Canvas() {
<DragProvider value={useDragStore}>
{renderTree({ id: null })}
</DragProvider>
{/* ν¬νƒˆ 기반 선택 μ˜€λ²„λ ˆμ΄ β€” λ…Έλ“œ DOM 트리 λ°”κΉ₯μ—μ„œ λ Œλ”λ§ */}
<SelectionOverlay
selectedNodeId={selectedNodeId}
canvas={canvasState}
/>
</div>
</div>
</div>
Expand Down
1 change: 0 additions & 1 deletion packages/ui/src/components/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default function StackComponent({
style={cssProps}
className={cn("h-full w-full", {
"node.style.className": node.style.className,
"ring-semantic-info ring-2": hoveredStackId === node.id,
})}
>
{children}
Expand Down
56 changes: 36 additions & 20 deletions packages/ui/src/core/EditorNodeWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import clsx from "clsx";
import { useDragStore } from "context/dragContext";
import { useRef, useState } from "react";
import { flushSync } from "react-dom";
import { Rnd } from "react-rnd";
import { WcxNode } from "types";
import { CanvasState, Layer } from "types/rnd";
Expand Down Expand Up @@ -40,6 +41,7 @@ export default function EditorNodeWrapper({
cursor: "move",
};

const rndRef = useRef<Rnd>(null);
const [isTransformActive, setIsTransformActive] = useState(false);
const [dragPosition, setDragPosition] = useState<{
x: number;
Expand All @@ -52,8 +54,9 @@ export default function EditorNodeWrapper({
const { id } = node;
const { width, height, x, y, zIndex } = node.layout;
const selectedNodeGuideClasses = {
handle: "bg-white border-2 rounded-full border-rnd-handle !w-2 !h-2 ",
outline: "ring ring-2 ring-rnd-handle",
// μ‹œκ°μ  핸듀은 SelectionOverlay ν¬νƒˆμ—μ„œ λ Œλ”λ§.
// Rnd의 핸듀은 μΈν„°λž™μ…˜λ§Œ λ‹΄λ‹Ή (투λͺ…ν•˜κ²Œ μœ μ§€)
handle: "opacity-0 !w-3 !h-3 ",
};

// ν•„μš”ν•œ λ°μ΄ν„°λ§Œ ꡬ독
Expand Down Expand Up @@ -95,31 +98,42 @@ export default function EditorNodeWrapper({

return (
<Rnd

ref={rndRef}
className={clsx(
"group cursor-pointer",
hasRelativePosition && !isTransformActive && "!transform-none", // relative인 κ²½μš°μ—λŠ” stack의 정렬을 μ§€ν‚€κΈ° μœ„ν•΄ transform을 κΊΌλ†“λŠ”λ‹€.
)}
size={{ width, height }}
position={{ x, y }}
position={hasRelativePosition ? dragPosition : { x, y }}
style={{
...wrapperStyle,
position: hasRelativePosition ? "relative" : (node.style.position as any),
position:
hasRelativePosition && !isTransformActive
? "relative"
: (node.style.position as any) || "absolute",

top: hasRelativePosition && isTransformActive ? 0 : undefined,
left: hasRelativePosition && isTransformActive ? 0 : undefined,
zIndex,
}}
scale={canvas.scale}
//FIXME-transform이 ν’€λ¦¬λŠ” μˆœκ°„ μƒνƒœλ³€ν™”μ˜ 타이밍 μˆœμ„œ 문제 λ•Œλ¬Έμ— 버그 λ°œμƒν•˜λŠ”λ“―?
onDragStart={(e, d) => {
e.stopPropagation();
setDraggingId(id); //λ“œλž˜κ·Έ μ‹œμž‘ μ•Œλ¦Ό
console.log(d.node.offsetLeft);
if (hasRelativePosition) {
const { offsetLeft, offsetTop } = d.node;
updateNode(id, { x: offsetLeft, y: offsetTop });
setDragPosition({ x: offsetLeft, y: offsetTop });
rndRef.current?.updatePosition({ x: 0, y: 0 });

setIsTransformActive(true);
console.log(
`μ’Œν‘œ 보정 μž‘λ™ offsetLeft - ${offsetLeft} // offsetTop - ${offsetTop} `,
`[dragStart]_ν˜„μž¬ μΆ”μΆœλœ λ…Έλ“œ μ’Œν‘œ offsetLeft - ${offsetLeft} // offsetTop - ${offsetTop} `,
);
}
console.log(
`[dragStart]ν˜„μž¬ λ…Έλ“œμ˜ μ‹€μ œ λ Œλ”λ§position - x:${x}, y:${y}`,
);
}}
//TODO-이동쀑에 둜직 μ‹€ν–‰ν•˜λ©΄ μ„±λŠ₯상 뢀담이 될 수 μžˆλ‹€... μ΅œμ ν™” κ³ λ―Ό 해보기
onDrag={(e, d) => {
Expand All @@ -131,18 +145,15 @@ export default function EditorNodeWrapper({
}}
onDragStop={(e, d) => {
setIsTransformActive(false);
console.log(`ν˜„μž¬ λ…Έλ“œ ${id}- ν¬μ§€μ…˜ ${node.style.position}`);
console.log(`[dragStop]ν˜„μž¬ λ…Έλ“œ ${id}- ν¬μ§€μ…˜ ${node.style.position}`);
const stackId = findStackId(e);

setDraggingId(null);
setHoveredStackId(null);

console.log(
`λ“œλž˜κ·Έ μ’…λ£Œμ‹œ λ…Έλ“œμ˜ μ’Œν‘œ x:${d.node.offsetLeft}, y:${d.node.offsetTop}`,
);
console.log(`[dragStop]λ…Έλ“œμ˜ μ’Œν‘œ x:${x}, y:${y}`);

if (hasRelativePosition) {
console.log("relative position");
return;
}

Expand All @@ -168,13 +179,21 @@ export default function EditorNodeWrapper({
})
}
*/
onResizeStop={(e, dir, ref, delta, pos) =>
onResizeStop={(e, dir, ref, delta, pos) => {
updateNode(id, {
width: parseInt(ref.style.width),
height: parseInt(ref.style.height),
...(hasRelativePosition ? {} : pos),
})
}
});
// [버그 μˆ˜μ •] react-rndλŠ” λ¦¬μ‚¬μ΄μ¦ˆ 쀑 top/left λ°©ν–₯ ν•Έλ“€ μ‚¬μš© μ‹œ
// λ‚΄λΆ€ position을 λˆ„μ  λ³€κ²½ν•œλ‹€. !transform-none으둜 DOM에선 보이지 μ•Šμ§€λ§Œ
// λ“œλž˜κ·Έ μ‹œμž‘ μ‹œ isTransformActiveκ°€ true둜 λ°”λ€Œλ©΄μ„œ λˆ„μ λœ 값이 ν•œκΊΌλ²ˆμ— λ°˜μ˜λ˜μ–΄
// λ…Έλ“œκ°€ νŠ€λŠ” 버그가 λ°œμƒν•œλ‹€. β†’ λ¦¬μ‚¬μ΄μ¦ˆ μ’…λ£Œ μ‹œ λ‚΄λΆ€ position을 {x:0, y:0}으둜 κ°•μ œ 동기화.
// if (hasRelativePosition) {
// setDragPosition({ x: 0, y: 0 });
// rndRef.current?.updatePosition({ x: 0, y: 0 });
// }
}}
enableResizing={isGroup ? undefined : isSelected ? undefined : false}
disableDragging={!isSelected}
resizeHandleClasses={{
Expand All @@ -198,10 +217,7 @@ export default function EditorNodeWrapper({
selectNode(id);
}}
style={wrapperStyle}
className={clsx(
"relative h-full w-full transition-shadow duration-200",
isSelected && selectedNodeGuideClasses.outline,
)}
className="relative h-full w-full"
>
{children}
</div>
Expand Down
Loading