8.4 KiB
Resizable
(no submodules)
- resizable.variants — `resizablePanelGroupVariants`,…-
resizable.variants —
resizablePanelGroupVariants,resizablePanelVariants,resizableHandleVariants, andresizableHandleGripVariantsa… -
resizable.variants —
resizablePanelGroupVariants,… -
resizable.variants —
resizablePanelGroupVariants,resizablePanelVariants,resizableHandleVariants, andresizableHandleGripVariantsa… -
resizable.variants —
resizablePanelGroupVariants,… -
resizable.variants —
resizablePanelGroupVariants,resizablePanelVariants,resizableHandleVariants, andresizableHandleGripVariantsa… -
resizable.variants —
resizablePanelGroupVariants,… -
resizable.variants —
resizablePanelGroupVariants,resizablePanelVariants,resizableHandleVariants, andresizableHandleGripVariantsa… -
resizable.variants —
resizablePanelGroupVariants,resizablePanelVariants,resizableHandleVariants, andresizableHandleGripVariantsa…
Purpose
<ResizablePanelGroup>, <ResizablePanel>, and <ResizableHandle> are the chassis split-layout primitives built on react-resizable-panels v4 (Group / Panel / Separator). The library renders a flex container with orientation driving flex-direction (horizontal → row, vertical → column) and exposes pointer + keyboard resize gestures (Arrow keys when the handle is focused). The chassis layers cva slots and an optional withHandle indicator on the separator.
Use this primitive for split-layout surfaces (sidebar / main / preview, master-detail, code-editor + preview). Each panel sizes itself via flex-grow / flex-shrink driven by the library; consumers persist sizes via the defaultLayout / onLayoutChanged props that pass through to the library.
Features
- Built on
react-resizable-panelsv4 (Group/Panel/Separator). orientationprop on the group:"horizontal"(default; row layout) or"vertical"(column layout).- Pointer drag + keyboard resize (ArrowLeft/Right or ArrowUp/Down when the handle is focused) supplied by the library.
defaultSize,minSize,maxSize,collapsibleper panel.withHandleopt-in grip indicator on<ResizableHandle>so coarse pointers (touch) get a visible hit indicator.defaultLayout+onLayoutChangedfor persisting sizes between sessions.- Theme-driven cva overrides for
ResizablePanelGroup,ResizablePanel,ResizableHandle,ResizableHandleGrip. data-disabledon the handle when the group'sdisabledprop is true; the handle still mounts but the cursor and opacity collapse so users see the disabled affordance.
Usage: Basic Example
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@suaveplan/react-ui";
export function TwoPaneSplit() {
return (
<ResizablePanelGroup id="main-split" className="rounded border">
<ResizablePanel id="sidebar" defaultSize={25} minSize={15}>
<div className="p-3">Sidebar</div>
</ResizablePanel>
<ResizableHandle id="handle-1" withHandle />
<ResizablePanel id="main" defaultSize={75} minSize={40}>
<div className="p-3">Main content</div>
</ResizablePanel>
</ResizablePanelGroup>
);
}
Usage: Advanced Example
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@suaveplan/react-ui";
import type { Layout } from "react-resizable-panels";
const STORAGE_KEY = "editor.layout";
function loadLayout(): Layout | undefined {
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? (JSON.parse(raw) as Layout) : undefined;
} catch {
return undefined;
}
}
function saveLayout(layout: Layout): void {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(layout));
} catch {
/* ignore */
}
}
export function EditorSplit() {
return (
<ResizablePanelGroup
id="editor-split"
defaultLayout={loadLayout()}
onLayoutChanged={saveLayout}
className="h-screen"
>
<ResizablePanel id="files" defaultSize={20} minSize={15} collapsible>
<FileTree />
</ResizablePanel>
<ResizableHandle id="h1" withHandle />
<ResizablePanel id="editor" defaultSize={50} minSize={30}>
<CodeEditor />
</ResizablePanel>
<ResizableHandle id="h2" withHandle />
<ResizablePanel id="preview" defaultSize={30} minSize={20}>
<LivePreview />
</ResizablePanel>
</ResizablePanelGroup>
);
}
API Reference
<ResizablePanelGroup>
Wraps Group from react-resizable-panels.
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | number |
useId() |
Unique identifier within the application. Falls back to React's useId() when omitted. |
orientation |
"horizontal" | "vertical" |
"horizontal" |
Layout axis. |
defaultLayout |
Layout ({ [panelId]: number }) |
none | Initial layout (percentages 0..100 keyed by panel id). |
onLayoutChange |
(layout: Layout) => void |
none | Fires on every pointer-move during drag — debounce before persisting. |
onLayoutChanged |
(layout: Layout) => void |
none | Fires once after the pointer is released — recommended for persistence. |
disabled |
boolean |
false |
Disable resize across the entire group; the handle gets data-disabled and the cursor stays default. |
disableCursor |
boolean |
false |
Disable the library's drag-cursor styling. |
<ResizablePanel>
Wraps Panel.
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | number |
useId() |
Unique identifier within the group. |
defaultSize |
number | string |
auto | Initial size as a percentage (0..100) or a CSS value. |
minSize |
number | string |
none | Minimum size during resize. |
maxSize |
number | string |
none | Maximum size during resize. |
collapsible |
boolean |
false |
Allow the panel to collapse below minSize and snap to zero. |
onResize |
(size, id, prev) => void |
none | Fires when the panel's size changes. |
<ResizableHandle>
Wraps Separator.
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | number |
useId() |
Unique identifier within the group. |
withHandle |
boolean |
false |
Render a small grip square (using lucide's <GripVerticalIcon>) centred on the handle. |
disabled |
boolean |
false |
Disable this individual handle. |
disableDoubleClick |
boolean |
false |
Disable double-click-to-collapse on this handle. |
Implementation Notes
- The library v4 names are
Group/Panel/Separator; the chassis re-exports them as<ResizablePanelGroup>/<ResizablePanel>/<ResizableHandle>for naming parity with the upstream shadcn registry name and so the chassis surface reads naturally. - The library applies
display,flex-direction,flex-wrap, andoverflowstyles inline on the group root and CANNOT be overridden via className — the chassis cva sticks to layout-neutral recipes (h-full w-full). - The chassis NEVER assigns a numeric
z-index(per the shadcn skill rule). Stacking is implicit — the handle sits between two panels in document order. - Each panel renders as an OUTER
<div data-panel>wrapping a chassis-className-styled INNER<div>. Tests inspect the inner element when asserting cva classes. - The handle exposes
aria-orientationperpendicular to the resize axis (a horizontal group has a vertical separator), matching the WAI-ARIA separator pattern. - The
withHandlegrip uses lucide's<GripVerticalIcon>; consumers can render a custom indicator by composing their own children inside<ResizableHandle>instead of opting intowithHandle. - The
disabledgroup prop setsdata-disabledon the handle; the cva collapses the cursor and reduces opacity so users see the disabled state without losing the focus ring. - Per the WAI-ARIA separator pattern, the handle is keyboard-focusable (
tabindex="0"); ArrowLeft/Right or ArrowUp/Down resize the adjacent panels by 5% per keystroke (library default).