summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/components
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-08 10:24:23 -0600
committerAdam <[email protected]>2025-12-08 10:24:26 -0600
commit5e3a59d5a297a8fc5589a9935ddf08db5c7e512c (patch)
treecbf7f8330c53f06c006a5b63dee11e75a89ec534 /packages/ui/src/components
parent9f23d85e20af5ae8e81bdff66a0b5927f0597edc (diff)
downloadopencode-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.css20
-rw-r--r--packages/ui/src/components/resize-handle.tsx71
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}
+ />
+ )
+}