PG07▸📐 Layout · 版型▸分割
Resizable Panels
可拖曳分割面板
難度 ★★★★★標籤 resize · split · drag3 min read
模式簡述
Resizable Panels:可拖曳調整大小的分割面板。VS Code、設計工具的標配。要記住使用者偏好。
// editor.tsx
export function Editor() {
return <Pane />;
}
Preview
Hello, Pane.
左 50% / 右 50%
Claude Code Prompt
參考:PG07 resizable-panels 組件:ResizablePanels.tsx Resizable Panels:可拖曳調整大小的分割面板。VS Code、設計工具的標配。要記住使用者偏好。 限制: - 沒做最小寬度 - 沒記住寬度 - 手把太細
完整程式碼
"use client";
import { useEffect, useRef, useState } from "react";
type State = "balanced" | "left-wide" | "right-wide";
export function ResizablePanels({ state = "balanced" }: { state?: State }) {
const pct = state === "left-wide" ? 72 : state === "right-wide" ? 28 : 50;
const [split, setSplit] = useState(pct);
const ref = useRef<HTMLDivElement>(null);
const [drag, setDrag] = useState(false);
useEffect(() => setSplit(pct), [pct]);
useEffect(() => {
function onMove(e: PointerEvent) {
if (!drag || !ref.current) return;
const r = ref.current.getBoundingClientRect();
const x = ((e.clientX - r.left) / r.width) * 100;
setSplit(Math.max(15, Math.min(85, x)));
}
function onUp() {
setDrag(false);
}
if (drag) {
window.addEventListener("pointermove", onMove);
window.addEventListener("pointerup", onUp);
return () => {
window.removeEventListener("pointermove", onMove);
window.removeEventListener("pointerup", onUp);
};
}
}, [drag]);
return (
<div className="w-full max-w-2xl">
<div
ref={ref}
className="relative flex h-56 overflow-hidden rounded-xl border border-border select-none"
>
<div
className="overflow-hidden bg-zinc-800 p-3 font-mono text-[10px] text-zinc-400"
style={{ width: `${split}%` }}
>
<p className="text-zinc-500">// editor.tsx</p>
<p>export function Editor() {`{`}</p>
<p> return <Pane />;</p>
<p>{`}`}</p>
</div>
<div
onPointerDown={() => setDrag(true)}
className="relative w-1.5 shrink-0 cursor-ew-resize bg-border hover:bg-lab-accent/50"
>
<div className="absolute left-1/2 top-1/2 h-10 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full bg-foreground/40" />
</div>
<div
className="flex-1 bg-background p-3 font-mono text-[10px] text-muted-foreground"
style={{ width: `${100 - split}%` }}
>
<p className="text-foreground">Preview</p>
<div className="mt-2 grid h-32 place-items-center rounded-md border border-border bg-muted/30">
<span className="text-[11px] text-foreground">Hello, Pane.</span>
</div>
</div>
</div>
<p className="mt-2 text-center font-mono text-[10px] text-muted-foreground">
左 {Math.round(split)}% / 右 {Math.round(100 - split)}%
</p>
</div>
);
}何時用
- ✅ 編輯器與預覽並排
- ✅ Three column 工具型介面
- ✅ 需要使用者自訂版面
何時別用
- ❌ 手機端(沒空間 resize)
- ❌ 純展示內容(沒必要 resize)
- ❌ 極簡介面(增加複雜度)
我踩過的坑
-
沒做最小寬度:拖到 0 panel 消失但不知道怎麼救回。設 min-width: 200px。
-
沒記住寬度:每次刷新都重置。存 localStorage。
-
手把太細:1px 拖曳區手指找不到。要 8-12px 的 hit area + 視覺 indicator。
相關模式
- PC03