Layout
PanelSplitter
The draggable seam between two side-by-side panels; every split view in Max uses it, so one gesture works everywhere.
Guidelines#
Use it for list/detail layouts: Unibox list and thread, pipeline board and people board, conversation and details.
What you provide: label ("Resize conversation list"), valueText for screen readers ("260 pixels for the list"), min/max/now in pixels, isResizing, pointer handlers (onPointerDown/Move/Up), onKeyDown (arrow keys step the width) and onReset (double-click restores the default).
Anatomy: a 12px hit strip with a 1px border line that turns primary at 50% on hover and full primary on focus or drag, and a 12 × 48px grip pill in the middle.
Facts#
| Group | Layout |
|---|---|
| Product | Max |
| Global | window.DigitalCrew.PanelSplitter |
| Source | max-agent: src/components/ui/panel-splitter.tsx |
| Import in the app | import { PanelSplitter } from "@/components/ui/panel-splitter"; |
API#
TypeScript declarations emitted from src/components/ui/panel-splitter.tsx (the module also exports its other parts).
interface PanelSplitterProps {
label: string;
/** Human-readable state for screen readers, e.g. "320 pixels for the list". */
valueText: string;
min: number;
max: number;
now: number;
isResizing: boolean;
onPointerDown: (event: PointerEvent<HTMLElement>) => void;
onPointerMove: (event: PointerEvent<HTMLElement>) => void;
onPointerUp: (event: PointerEvent<HTMLElement>) => void;
onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
onReset: () => void;
className?: string;
}
export declare function PanelSplitter({ label, valueText, min, max, now, isResizing, onPointerDown, onPointerMove, onPointerUp, onKeyDown, onReset, className, }: PanelSplitterProps): JSX.Element;Example#
The code of the preview above: plain React.createElement against window.DigitalCrew, with realistic data.
<div id="root" class="p-6"></div>
<script>
(function () {
var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
function App() {
var w = React.useState(260), drag = React.useState(false), start = React.useRef(null);
function set(v) { w[1](Math.max(200, Math.min(420, v))); }
return h("div", { className: "flex h-52 overflow-hidden rounded-lg border" },
h("div", { style: { width: w[0] }, className: "shrink-0 p-4" },
h("p", { className: "text-sm font-medium" }, "Conversations"),
h("p", { className: "text-sm text-muted-foreground" }, "Drag the seam, use the arrow keys, or double-click to reset.")),
h(DC.PanelSplitter, {
label: "Resize conversation list", valueText: w[0] + " pixels for the list", min: 200, max: 420, now: w[0], isResizing: drag[0],
onPointerDown: function (e) { start.current = { x: e.clientX, w: w[0] }; drag[1](true); e.currentTarget.setPointerCapture(e.pointerId); },
onPointerMove: function (e) { if (start.current) set(start.current.w + e.clientX - start.current.x); },
onPointerUp: function () { start.current = null; drag[1](false); },
onKeyDown: function (e) { if (e.key === "ArrowLeft") set(w[0] - 16); if (e.key === "ArrowRight") set(w[0] + 16); },
onReset: function () { w[1](260); } }),
h("div", { className: "flex-1 p-4 bg-muted/40" }, h("p", { className: "text-sm font-medium" }, "Thread"), h("p", { className: "text-sm text-muted-foreground" }, "Claire Dubois · Re: annual plan")));
}
ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>