Skip to content

Commit 1f79b8f

Browse files
authored
chore: v2: windows are resizeable in any direction (#2189)
## Description Floating windows can now be resized from any edge or corner (previously it was just the bottom right). <!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. --> ## Related Issue and Pull requests Closes Shopify/oasis-frontend#610 <!-- Link to any related issues using the format #<issue-number> --> ## Type of Change - [x] Improvement ## Checklist <!-- Please ensure the following are completed before submitting the PR --> - [ ] I have tested this does not break current pipelines / runs functionality - [ ] I have tested the changes on staging ## Screenshots (if applicable) <!-- Include any screenshots that might help explain the changes or provide visual context --> ## Test Instructions Go to v2 editor create a floating window confirm you can resize it along any edge or corner <!-- Detail steps and prerequisites for testing the changes in this PR --> ## Additional Comments <!-- Add any additional context or information that reviewers might need to know regarding this PR -->
1 parent a39da5f commit 1f79b8f

1 file changed

Lines changed: 93 additions & 47 deletions

File tree

src/routes/v2/shared/windows/components/FloatingWindow.tsx

Lines changed: 93 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { cva } from "class-variance-authority";
22
import { observer } from "mobx-react-lite";
3-
import { type CSSProperties, useState } from "react";
3+
import { type CSSProperties, type MouseEventHandler, useState } from "react";
44
import { createPortal } from "react-dom";
55

66
import { useWindowContext } from "@/routes/v2/shared/windows/ContentWindowStateContext";
@@ -57,24 +57,44 @@ function getContentHeight(model: WindowModel): string | number {
5757
return model.size.height - HEADER_HEIGHT;
5858
}
5959

60-
function ResizeHandle({
61-
onMouseDown,
60+
type ResizeDirection = "n" | "s" | "e" | "w" | "ne" | "nw" | "se" | "sw";
61+
62+
const resizeHandleClasses: Record<ResizeDirection, string> = {
63+
n: "absolute top-0 left-3 right-3 h-1 cursor-n-resize",
64+
s: "absolute bottom-0 left-3 right-3 h-1 cursor-s-resize",
65+
e: "absolute right-0 top-3 bottom-3 w-1 cursor-e-resize",
66+
w: "absolute left-0 top-3 bottom-3 w-1 cursor-w-resize",
67+
ne: "absolute top-0 right-0 w-3 h-3 cursor-ne-resize",
68+
nw: "absolute top-0 left-0 w-3 h-3 cursor-nw-resize",
69+
se: "absolute bottom-0 right-0 w-3 h-3 cursor-se-resize hover:bg-gray-300 rounded-tl-sm transition-colors",
70+
sw: "absolute bottom-0 left-0 w-3 h-3 cursor-sw-resize",
71+
};
72+
73+
function ResizeHandles({
74+
onResizeStart,
6275
}: {
63-
onMouseDown: React.MouseEventHandler;
76+
onResizeStart: (direction: ResizeDirection) => MouseEventHandler;
6477
}) {
6578
return (
66-
<div
67-
className="absolute bottom-0 right-0 w-3 h-3 cursor-se-resize hover:bg-gray-300 rounded-tl-sm transition-colors"
68-
onMouseDown={onMouseDown}
69-
>
70-
<svg
71-
className="w-full h-full text-gray-400"
72-
viewBox="0 0 16 16"
73-
fill="currentColor"
74-
>
75-
<path d="M14 14H12V12H14V14ZM14 10H12V8H14V10ZM10 14H8V12H10V14Z" />
76-
</svg>
77-
</div>
79+
<>
80+
{(Object.keys(resizeHandleClasses) as ResizeDirection[]).map((dir) => (
81+
<div
82+
key={dir}
83+
className={resizeHandleClasses[dir]}
84+
onMouseDown={onResizeStart(dir)}
85+
>
86+
{dir === "se" && (
87+
<svg
88+
className="w-full h-full text-gray-400"
89+
viewBox="0 0 16 16"
90+
fill="currentColor"
91+
>
92+
<path d="M14 14H12V12H14V14ZM14 10H12V8H14V10ZM10 14H8V12H10V14Z" />
93+
</svg>
94+
)}
95+
</div>
96+
))}
97+
</>
7898
);
7999
}
80100

@@ -105,38 +125,64 @@ export const FloatingWindow = observer(function FloatingWindow() {
105125

106126
const [isResizing, setIsResizing] = useState(false);
107127

108-
const handleResizeMouseDown = (e: React.MouseEvent) => {
109-
e.preventDefault();
110-
e.stopPropagation();
111-
setIsResizing(true);
112-
113-
const startX = e.clientX;
114-
const startY = e.clientY;
115-
const startWidth = model.size.width;
116-
const startHeight = model.size.height;
117-
118-
const onMouseMove = (moveE: MouseEvent) => {
119-
const newWidth = Math.max(
120-
model.minSize.width,
121-
startWidth + (moveE.clientX - startX),
122-
);
123-
const newHeight = Math.max(
124-
model.minSize.height,
125-
startHeight + (moveE.clientY - startY),
126-
);
127-
model.updateSize({ width: newWidth, height: newHeight });
128-
};
129-
130-
const onMouseUp = () => {
131-
setIsResizing(false);
132-
document.removeEventListener("mousemove", onMouseMove);
133-
document.removeEventListener("mouseup", onMouseUp);
128+
const handleResizeMouseDown =
129+
(direction: ResizeDirection): MouseEventHandler =>
130+
(e) => {
131+
e.preventDefault();
132+
e.stopPropagation();
133+
setIsResizing(true);
134+
135+
const startX = e.clientX;
136+
const startY = e.clientY;
137+
const startWidth = model.size.width;
138+
const startHeight = model.size.height;
139+
const startPosX = model.position.x;
140+
const startPosY = model.position.y;
141+
142+
const resizeRight = direction.includes("e");
143+
const resizeLeft = direction.includes("w");
144+
const resizeBottom = direction.includes("s");
145+
const resizeTop = direction.includes("n");
146+
147+
const onMouseMove = (moveE: MouseEvent) => {
148+
const dx = moveE.clientX - startX;
149+
const dy = moveE.clientY - startY;
150+
151+
let newWidth = startWidth;
152+
let newHeight = startHeight;
153+
let newX = startPosX;
154+
let newY = startPosY;
155+
156+
if (resizeRight) {
157+
newWidth = Math.max(model.minSize.width, startWidth + dx);
158+
} else if (resizeLeft) {
159+
newWidth = Math.max(model.minSize.width, startWidth - dx);
160+
newX = startPosX + (startWidth - newWidth);
161+
}
162+
163+
if (resizeBottom) {
164+
newHeight = Math.max(model.minSize.height, startHeight + dy);
165+
} else if (resizeTop) {
166+
newHeight = Math.max(model.minSize.height, startHeight - dy);
167+
newY = startPosY + (startHeight - newHeight);
168+
}
169+
170+
model.updateSize({ width: newWidth, height: newHeight });
171+
if (resizeLeft || resizeTop) {
172+
model.updatePosition({ x: newX, y: newY });
173+
}
174+
};
175+
176+
const onMouseUp = () => {
177+
setIsResizing(false);
178+
document.removeEventListener("mousemove", onMouseMove);
179+
document.removeEventListener("mouseup", onMouseUp);
180+
};
181+
182+
document.addEventListener("mousemove", onMouseMove);
183+
document.addEventListener("mouseup", onMouseUp);
134184
};
135185

136-
document.addEventListener("mousemove", onMouseMove);
137-
document.addEventListener("mouseup", onMouseUp);
138-
};
139-
140186
return (
141187
<>
142188
<div
@@ -167,7 +213,7 @@ export const FloatingWindow = observer(function FloatingWindow() {
167213
</div>
168214

169215
{!model.isMaximized && (
170-
<ResizeHandle onMouseDown={handleResizeMouseDown} />
216+
<ResizeHandles onResizeStart={handleResizeMouseDown} />
171217
)}
172218
</div>
173219

0 commit comments

Comments
 (0)