Compare commits

...

21 Commits

Author SHA1 Message Date
openhands
ebea3d34b5 Merge remote changes and keep descriptive comments 2025-01-08 15:38:18 +00:00
openhands
ffcaaf705b Simplified panel resizing: Add responsive initial sizes and reasonable constraints 2025-01-08 15:36:53 +00:00
Xingyao Wang
b0f5545fc4 Merge branch 'main' into openhands-fix-issue-5965 2025-01-08 10:26:16 -05:00
Xingyao Wang
57285492d6 sync 2025-01-07 13:39:54 -05:00
Xingyao Wang
2447015197 remove unintended changes 2025-01-07 13:38:41 -05:00
Xingyao Wang
614345783c Merge branch 'main' into openhands-fix-issue-5965 2025-01-07 13:36:07 -05:00
openhands
695266c644 merge: resolve conflicts in confirm-delete-modal.tsx
- Keep responsive layout with flex-col/flex-row
- Keep hover states for better UX
- Keep test IDs for better test stability
- Keep Delete text for clearer action
- Keep font-bold from main branch
2025-01-07 17:27:48 +00:00
openhands
dcb24d3ddd fix: simplify panel sizing logic
- Remove window resize handling since panels use flex layout
- Use fixed min/max dimensions instead of window-based constraints
- Keep proportional sizing for second panel (30-70%)
2025-01-07 17:22:59 +00:00
openhands
ccdc1c3f10 fix: make right panel width proportional to prevent overflow 2025-01-06 23:47:36 +00:00
openhands
9e5af03b35 Simplified resizable panel code while maintaining functionality 2025-01-06 21:53:01 +00:00
Xingyao Wang
ac51a4cc53 Merge branch 'main' into openhands-fix-issue-5965 2025-01-06 16:40:15 -05:00
openhands
882d788661 fix: Improve panel resizing behavior
- Fix right panel extending beyond browser window
- Fix terminal panel not filling available space
- Adjust chat panel max width to 50%
- Simplify resize logic using React state
- Add proper flex properties for responsive behavior
2025-01-04 22:20:24 +00:00
Xingyao Wang
daedc72a21 Merge commit '5ca0beadfbec3bddf33a21623899e0118543cc14' into openhands-fix-issue-5965 2025-01-04 16:54:17 -05:00
openhands
4156bc2e25 Fix pr #5966: Fix issue #5965: [Bug]: Misc UI issues due to resizable and collapsible panel 2025-01-03 04:53:14 +00:00
Xingyao Wang
eef4b1fcd9 lint 2025-01-02 23:41:18 -05:00
openhands
e923551ba6 Fix pr #5966: Fix issue #5965: [Bug]: Misc UI issues due to resizable and collapsible panel 2025-01-03 04:37:29 +00:00
openhands
eef222e97e Fix pr #5966: Fix issue #5965: [Bug]: Misc UI issues due to resizable and collapsible panel 2025-01-03 02:44:02 +00:00
openhands
6a1f77a0fa Fix pr #5966: Fix issue #5965: [Bug]: Misc UI issues due to resizable and collapsible panel 2025-01-03 01:00:49 +00:00
openhands
ab44ad79e8 Fix pr #5966: Fix issue #5965: [Bug]: Misc UI issues due to resizable and collapsible panel 2025-01-02 06:31:13 +00:00
openhands
c7c1d08295 Fix pr #5966: Fix issue #5965: [Bug]: Misc UI issues due to resizable and collapsible panel 2025-01-02 06:14:10 +00:00
openhands
cdb2784a1f Fix issue #5965: [Bug]: Misc UI issues due to resizable and collapsible panel 2025-01-02 05:54:56 +00:00
2 changed files with 97 additions and 96 deletions

View File

@@ -1,4 +1,11 @@
import React, { CSSProperties, JSX, useEffect, useRef, useState } from "react";
import React, {
CSSProperties,
JSX,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import {
VscChevronDown,
VscChevronLeft,
@@ -38,129 +45,123 @@ export function ResizablePanel({
orientation,
initialSize,
}: ResizablePanelProps): JSX.Element {
const [firstSize, setFirstSize] = useState<number>(initialSize);
const isHorizontal = orientation === Orientation.HORIZONTAL;
const [firstSize, setFirstSize] = useState(initialSize);
const [dividerPosition, setDividerPosition] = useState<number | null>(null);
const [collapse, setCollapse] = useState(Collapse.SPLIT);
const firstRef = useRef<HTMLDivElement>(null);
const secondRef = useRef<HTMLDivElement>(null);
const [collapse, setCollapse] = useState<Collapse>(Collapse.SPLIT);
const isHorizontal = orientation === Orientation.HORIZONTAL;
useEffect(() => {
if (dividerPosition == null || !firstRef.current) {
return undefined;
}
const getFirstSizeFromEvent = (e: MouseEvent) => {
const position = isHorizontal ? e.clientX : e.clientY;
return firstSize + position - dividerPosition;
};
if (!dividerPosition) return undefined;
const onMouseMove = (e: MouseEvent) => {
e.preventDefault();
const newFirstSize = `${getFirstSizeFromEvent(e)}px`;
const { current } = firstRef;
if (current) {
if (isHorizontal) {
current.style.width = newFirstSize;
current.style.minWidth = newFirstSize;
} else {
current.style.height = newFirstSize;
current.style.minHeight = newFirstSize;
}
}
const delta = (isHorizontal ? e.clientX : e.clientY) - dividerPosition;
setFirstSize(firstSize + delta);
setDividerPosition(isHorizontal ? e.clientX : e.clientY);
};
const onMouseUp = (e: MouseEvent) => {
e.preventDefault();
if (firstRef.current) {
firstRef.current.style.transition = "";
}
if (secondRef.current) {
secondRef.current.style.transition = "";
}
setFirstSize(getFirstSizeFromEvent(e));
if (firstRef.current) firstRef.current.style.transition = "";
if (secondRef.current) secondRef.current.style.transition = "";
setFirstSize(
firstSize + ((isHorizontal ? e.clientX : e.clientY) - dividerPosition),
);
setDividerPosition(null);
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
}, [dividerPosition, firstSize, orientation]);
}, [dividerPosition, firstSize, isHorizontal]);
const onMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
if (firstRef.current) {
firstRef.current.style.transition = "none";
}
if (secondRef.current) {
secondRef.current.style.transition = "none";
}
const position = isHorizontal ? e.clientX : e.clientY;
setDividerPosition(position);
if (firstRef.current) firstRef.current.style.transition = "none";
if (secondRef.current) secondRef.current.style.transition = "none";
setDividerPosition(isHorizontal ? e.clientX : e.clientY);
};
const getStyleForFirst = () => {
const style: CSSProperties = { overflow: "hidden" };
if (collapse === Collapse.COLLAPSED) {
style.opacity = 0;
style.width = 0;
style.minWidth = 0;
style.height = 0;
style.minHeight = 0;
} else if (collapse === Collapse.SPLIT) {
const firstSizePx = `${firstSize}px`;
if (isHorizontal) {
style.width = firstSizePx;
style.minWidth = firstSizePx;
} else {
style.height = firstSizePx;
style.minHeight = firstSizePx;
const getPanelStyle = useCallback(
(isFirst: boolean): CSSProperties => {
const style: CSSProperties = { overflow: "hidden" };
const isHidden =
(isFirst && collapse === Collapse.COLLAPSED) ||
(!isFirst && collapse === Collapse.FILLED);
const hiddenStyle: CSSProperties = {
...style,
opacity: 0,
width: 0,
minWidth: 0,
height: 0,
minHeight: 0,
};
const expandedStyle: CSSProperties = { ...style, flexGrow: 1 };
if (isHidden) {
return hiddenStyle;
}
} else {
style.flexGrow = 1;
}
return style;
};
const getStyleForSecond = () => {
const style: CSSProperties = { overflow: "hidden" };
if (collapse === Collapse.FILLED) {
style.opacity = 0;
style.width = 0;
style.minWidth = 0;
style.height = 0;
style.minHeight = 0;
} else if (collapse === Collapse.SPLIT) {
style.flexGrow = 1;
} else {
style.flexGrow = 1;
}
return style;
};
if (collapse !== Collapse.SPLIT) {
return expandedStyle;
}
const onCollapse = () => {
if (collapse === Collapse.SPLIT) {
setCollapse(Collapse.COLLAPSED);
} else {
setCollapse(Collapse.SPLIT);
}
};
if (isFirst) {
const dimension = isHorizontal ? "width" : "height";
const minDimension = isHorizontal ? "minWidth" : "minHeight";
const maxDimension = isHorizontal ? "maxWidth" : "maxHeight";
const onExpand = () => {
if (collapse === Collapse.SPLIT) {
setCollapse(Collapse.FILLED);
} else {
setCollapse(Collapse.SPLIT);
}
};
const firstPanelStyle: CSSProperties = {
...style,
[dimension]: `${firstSize}px`,
[minDimension]: isHorizontal ? "350px" : "300px",
[maxDimension]: isHorizontal ? "50%" : "70%",
flexShrink: 0,
};
return firstPanelStyle;
}
const secondPanelStyle: CSSProperties = {
...style,
flexGrow: 1,
flexShrink: 1,
...(isHorizontal
? {
minWidth: "30%",
maxWidth: "70%",
}
: {
minHeight: "300px",
display: "flex",
flexDirection: "column",
}),
};
return secondPanelStyle;
},
[collapse, firstSize, isHorizontal],
);
const toggleCollapse = () =>
setCollapse(
collapse === Collapse.SPLIT ? Collapse.COLLAPSED : Collapse.SPLIT,
);
const toggleExpand = () =>
setCollapse(collapse === Collapse.SPLIT ? Collapse.FILLED : Collapse.SPLIT);
return (
<div className={twMerge("flex", !isHorizontal && "flex-col", className)}>
<div
ref={firstRef}
className={twMerge(firstClassName, "transition-all ease-soft-spring")}
style={getStyleForFirst()}
style={getPanelStyle(true)}
>
{firstChild}
</div>
@@ -171,18 +172,18 @@ export function ResizablePanel({
<IconButton
icon={isHorizontal ? <VscChevronLeft /> : <VscChevronUp />}
ariaLabel="Collapse"
onClick={onCollapse}
onClick={toggleCollapse}
/>
<IconButton
icon={isHorizontal ? <VscChevronRight /> : <VscChevronDown />}
ariaLabel="Expand"
onClick={onExpand}
onClick={toggleExpand}
/>
</div>
<div
ref={secondRef}
className={twMerge(secondClassName, "transition-all ease-soft-spring")}
style={getStyleForSecond()}
style={getPanelStyle(false)}
>
{secondChild}
</div>

View File

@@ -122,7 +122,7 @@ function AppContent() {
<ResizablePanel
orientation={Orientation.HORIZONTAL}
className="grow h-full min-h-0 min-w-0"
initialSize={500}
initialSize={window.innerWidth * 0.3} // 30% of window width
firstClassName="rounded-xl overflow-hidden border border-neutral-600 bg-neutral-800"
secondClassName="flex flex-col overflow-hidden"
firstChild={<ChatInterface />}
@@ -130,7 +130,7 @@ function AppContent() {
<ResizablePanel
orientation={Orientation.VERTICAL}
className="grow h-full min-h-0 min-w-0"
initialSize={500}
initialSize={window.innerHeight * 0.7} // 70% of window height for workspace, 30% for terminal
firstClassName="rounded-xl overflow-hidden border border-neutral-600"
secondClassName="flex flex-col overflow-hidden"
firstChild={