diff options
| author | Adam <[email protected]> | 2025-12-08 10:24:23 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-08 10:24:26 -0600 |
| commit | 5e3a59d5a297a8fc5589a9935ddf08db5c7e512c (patch) | |
| tree | cbf7f8330c53f06c006a5b63dee11e75a89ec534 | |
| parent | 9f23d85e20af5ae8e81bdff66a0b5927f0597edc (diff) | |
| download | opencode-5e3a59d5a297a8fc5589a9935ddf08db5c7e512c.tar.gz opencode-5e3a59d5a297a8fc5589a9935ddf08db5c7e512c.zip | |
feat: resize handle
| -rw-r--r-- | packages/desktop/src/pages/layout.tsx | 44 | ||||
| -rw-r--r-- | packages/desktop/src/pages/session.tsx | 44 | ||||
| -rw-r--r-- | packages/ui/src/components/resize-handle.css | 20 | ||||
| -rw-r--r-- | packages/ui/src/components/resize-handle.tsx | 71 | ||||
| -rw-r--r-- | packages/ui/src/styles/index.css | 1 |
5 files changed, 110 insertions, 70 deletions
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 0c0d99147..166ee7beb 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -5,6 +5,7 @@ import { useLayout } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { base64Decode, base64Encode } from "@opencode-ai/util/encode" import { Mark } from "@opencode-ai/ui/logo" +import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" @@ -116,41 +117,14 @@ export default function Layout(props: ParentProps) { style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }} > <Show when={layout.sidebar.opened()}> - <div - class="absolute inset-y-0 right-0 z-10 w-2 translate-x-1/2 cursor-ew-resize" - onMouseDown={(e) => { - e.preventDefault() - const startX = e.clientX - const startWidth = layout.sidebar.width() - const maxWidth = window.innerWidth * 0.3 - const minWidth = 150 - const collapseThreshold = 80 - let currentWidth = startWidth - - document.body.style.userSelect = "none" - document.body.style.overflow = "hidden" - - const onMouseMove = (moveEvent: MouseEvent) => { - const deltaX = moveEvent.clientX - startX - currentWidth = startWidth + deltaX - const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth)) - layout.sidebar.resize(clampedWidth) - } - - const onMouseUp = () => { - document.body.style.userSelect = "" - document.body.style.overflow = "" - document.removeEventListener("mousemove", onMouseMove) - document.removeEventListener("mouseup", onMouseUp) - - if (currentWidth < collapseThreshold) { - layout.sidebar.close() - } - } - - document.addEventListener("mousemove", onMouseMove) - document.addEventListener("mouseup", onMouseUp) - }} + <ResizeHandle + direction="horizontal" + size={layout.sidebar.width()} + min={150} + max={window.innerWidth * 0.3} + collapseThreshold={80} + onResize={layout.sidebar.resize} + onCollapse={layout.sidebar.close} /> </Show> <div class="grow flex flex-col items-start self-stretch gap-4 p-2 min-h-0"> diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index 81f4dc1cb..f9e717674 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -9,6 +9,7 @@ import { Icon } from "@opencode-ai/ui/icon" import { Tooltip } from "@opencode-ai/ui/tooltip" import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { ProgressCircle } from "@opencode-ai/ui/progress-circle" +import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Tabs } from "@opencode-ai/ui/tabs" import { Code } from "@opencode-ai/ui/code" import { SessionTurn } from "@opencode-ai/ui/session-turn" @@ -607,41 +608,14 @@ export default function Page() { class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base" style={{ height: `${layout.terminal.height()}px` }} > - <div - class="absolute inset-x-0 top-0 z-10 h-2 -translate-y-1/2 cursor-ns-resize" - onMouseDown={(e) => { - e.preventDefault() - const startY = e.clientY - const startHeight = layout.terminal.height() - const maxHeight = window.innerHeight * 0.6 - const minHeight = 100 - const collapseThreshold = 50 - let currentHeight = startHeight - - document.body.style.userSelect = "none" - document.body.style.overflow = "hidden" - - const onMouseMove = (moveEvent: MouseEvent) => { - const deltaY = startY - moveEvent.clientY - currentHeight = startHeight + deltaY - const clampedHeight = Math.min(maxHeight, Math.max(minHeight, currentHeight)) - layout.terminal.resize(clampedHeight) - } - - const onMouseUp = () => { - document.body.style.userSelect = "" - document.body.style.overflow = "" - document.removeEventListener("mousemove", onMouseMove) - document.removeEventListener("mouseup", onMouseUp) - - if (currentHeight < collapseThreshold) { - layout.terminal.close() - } - } - - document.addEventListener("mousemove", onMouseMove) - document.addEventListener("mouseup", onMouseUp) - }} + <ResizeHandle + direction="vertical" + size={layout.terminal.height()} + min={100} + max={window.innerHeight * 0.6} + collapseThreshold={50} + onResize={layout.terminal.resize} + onCollapse={layout.terminal.close} /> <Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}> <Tabs.List class="h-10"> diff --git a/packages/ui/src/components/resize-handle.css b/packages/ui/src/components/resize-handle.css new file mode 100644 index 000000000..9344402c6 --- /dev/null +++ b/packages/ui/src/components/resize-handle.css @@ -0,0 +1,20 @@ +[data-component="resize-handle"] { + position: absolute; + z-index: 10; + + &[data-direction="horizontal"] { + inset-block: 0; + inset-inline-end: 0; + width: 8px; + transform: translateX(50%); + cursor: ew-resize; + } + + &[data-direction="vertical"] { + inset-inline: 0; + inset-block-start: 0; + height: 8px; + transform: translateY(-50%); + cursor: ns-resize; + } +} diff --git a/packages/ui/src/components/resize-handle.tsx b/packages/ui/src/components/resize-handle.tsx new file mode 100644 index 000000000..3ad01e27f --- /dev/null +++ b/packages/ui/src/components/resize-handle.tsx @@ -0,0 +1,71 @@ +import { splitProps, type JSX } from "solid-js" + +export interface ResizeHandleProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "onResize"> { + direction: "horizontal" | "vertical" + size: number + min: number + max: number + onResize: (size: number) => void + onCollapse?: () => void + collapseThreshold?: number +} + +export function ResizeHandle(props: ResizeHandleProps) { + const [local, rest] = splitProps(props, [ + "direction", + "size", + "min", + "max", + "onResize", + "onCollapse", + "collapseThreshold", + "class", + "classList", + ]) + + const handleMouseDown = (e: MouseEvent) => { + e.preventDefault() + const start = local.direction === "horizontal" ? e.clientX : e.clientY + const startSize = local.size + let current = startSize + + document.body.style.userSelect = "none" + document.body.style.overflow = "hidden" + + const onMouseMove = (moveEvent: MouseEvent) => { + const pos = local.direction === "horizontal" ? moveEvent.clientX : moveEvent.clientY + const delta = local.direction === "vertical" ? start - pos : pos - start + current = startSize + delta + const clamped = Math.min(local.max, Math.max(local.min, current)) + local.onResize(clamped) + } + + const onMouseUp = () => { + document.body.style.userSelect = "" + document.body.style.overflow = "" + document.removeEventListener("mousemove", onMouseMove) + document.removeEventListener("mouseup", onMouseUp) + + const threshold = local.collapseThreshold ?? 0 + if (local.onCollapse && threshold > 0 && current < threshold) { + local.onCollapse() + } + } + + document.addEventListener("mousemove", onMouseMove) + document.addEventListener("mouseup", onMouseUp) + } + + return ( + <div + {...rest} + data-component="resize-handle" + data-direction={local.direction} + classList={{ + ...(local.classList ?? {}), + [local.class ?? ""]: !!local.class, + }} + onMouseDown={handleMouseDown} + /> + ) +} diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index e29d8e33b..afe005f84 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -25,6 +25,7 @@ @import "../components/message-progress.css" layer(components); @import "../components/message-nav.css" layer(components); @import "../components/progress-circle.css" layer(components); +@import "../components/resize-handle.css" layer(components); @import "../components/select.css" layer(components); @import "../components/select-dialog.css" layer(components); @import "../components/spinner.css" layer(components); |
