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 /packages/ui/src/components | |
| parent | 9f23d85e20af5ae8e81bdff66a0b5927f0597edc (diff) | |
| download | opencode-5e3a59d5a297a8fc5589a9935ddf08db5c7e512c.tar.gz opencode-5e3a59d5a297a8fc5589a9935ddf08db5c7e512c.zip | |
feat: resize handle
Diffstat (limited to 'packages/ui/src/components')
| -rw-r--r-- | packages/ui/src/components/resize-handle.css | 20 | ||||
| -rw-r--r-- | packages/ui/src/components/resize-handle.tsx | 71 |
2 files changed, 91 insertions, 0 deletions
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} + /> + ) +} |
