@aiquants/resize-panels
v1.5.1
Published
Reusable resizable panel components and utilities for React
Readme
@aiquants/resize-panels
React 向けのリサイズ可能なパネルレイアウトコンポーネント集です。~/components/elements/ResizablePanels で培ったロジックをパッケージ化し、任意のアプリケーションから再利用できるようにしました。
主な特徴
PanelGroupとPanelとPanelResizeHandleを中核に、リデューサーでレイアウトと DOM 計測を同期calculateSnapThresholdとノイズ除去ロジックでリサイズ時の揺らぎを抑え、意図しない折りたたみを防止- 方向指定付き
collapsible設定とusePanelControlsにより、ドラッグ操作と UI 操作の両方で折りたたみ / 展開を制御 autoSaveIdを指定するとローカルストレージにパネルサイズを自動保存し、再読み込み時に復元showDebugInfoを使えばPanelDebugInfoとグローバルオーバーレイで計測結果や制約違反を可視化
インストール
ワークスペース内で利用する場合は pnpm を利用します。
pnpm add @aiquants/resize-panelsモノレポ内から参照する場合は package.json に次のように記載します。
{
"dependencies": {
"@aiquants/resize-panels": "workspace:*"
}
}クイックスタート
import { useId } from "react"
import { PanelGroup, Panel, PanelResizeHandle } from "@aiquants/resize-panels"
export const Example = () => {
const baseId = useId()
return (
<PanelGroup id={`${baseId}-group`} direction="horizontal" className="h-96">
<Panel id={`${baseId}-left`} defaultSize={{ value: 200, unit: "pixels" }} className="bg-slate-100">
左パネル
</Panel>
<PanelResizeHandle id={`${baseId}-handle`} />
<Panel id={`${baseId}-right`} defaultSize={{ value: 60, unit: "percentage" }} className="bg-slate-50">
右パネル
</Panel>
</PanelGroup>
)
}折りたたみを有効にする
Panel に collapsible={{ from: "start" }} や collapsible={{ from: "end" }} を指定すると、対応する PanelResizeHandle を端までドラッグしたタイミングで自動的に折りたたみ・展開が行われます。defaultCollapsed を併用すると初期状態を折りたたみ済みに設定できます。方向指定は必須で、各パネルがどちら側から折りたためるかを宣言します。両方向に対応したい場合は collapsible={{ from: "both" }} を使用します。
<PanelGroup direction="horizontal" className="h-96">
<Panel id="sidebar" defaultSize={{ value: 240, unit: "pixels" }} collapsible={{ from: "end" }}>
サイドバー
</Panel>
<PanelResizeHandle id="handle" />
<Panel id="main" defaultSize={{ value: 60, unit: "percentage" }}>
メインコンテンツ
</Panel>
</PanelGroup>UI から折りたたみ / 展開を切り替える
usePanelControls(panelId) フックを使うと、任意の UI から対象パネルを折りたたみ・展開・トグルできます。ボタンを配置するコンポーネントは PanelGroup の配下(コンテキスト内)に置いてください。折りたたみ後もボタンを表示したい場合は、別パネルやヘッダーなど常に可視な場所に設置します。API は方向を必須パラメーターとして受け取り、パネル構成に応じて呼び出し側で指定します。
import { PanelGroup, Panel, PanelResizeHandle, usePanelControls } from "@aiquants/resize-panels"
const PanelToggleButtons = ({ panelId, label }: { panelId: string; label: string }) => {
const { panel, isCollapsed, collapse, expand, toggle, canCollapseFromStart, canCollapseFromEnd } = usePanelControls(panelId)
const direction = panel?.collapsedByDirection ?? (canCollapseFromStart ? "start" : "end")
return (
<div className="flex items-center gap-2">
<span>{label}</span>
<button onClick={() => collapse(direction)} disabled={(!canCollapseFromStart && !canCollapseFromEnd) || isCollapsed}>
折りたたむ
</button>
<button onClick={() => expand(direction)} disabled={!isCollapsed || (!canCollapseFromStart && !canCollapseFromEnd)}>
展開する
</button>
<button onClick={() => toggle(direction)} disabled={!canCollapseFromStart && !canCollapseFromEnd}>
{isCollapsed ? "展開に切り替え" : "折りたたみに切り替え"}
</button>
</div>
)
}
export const Example = () => (
<PanelGroup direction="horizontal" className="h-96">
<Panel id="sidebar" defaultSize={{ value: 240, unit: "pixels" }} collapsible={{ from: "end" }}>
<PanelToggleButtons panelId="sidebar" label="サイドバー" />
</Panel>
<PanelResizeHandle id="split" />
<Panel id="main" defaultSize={{ value: 60, unit: "percentage" }}>
<PanelToggleButtons panelId="details" label="詳細パネル" />
</Panel>
<PanelResizeHandle id="split-right" />
<Panel id="details" defaultSize={{ value: 40, unit: "percentage" }} collapsible={{ from: "start" }}>
詳細
</Panel>
</PanelGroup>
)collapse(direction) は指定方向からパネルを非表示にし、expand(direction) はドラッグ操作と同じロジックで直前のサイズや推奨サイズを復元します。toggle(direction) を使うと現在の状態に応じて自動的に切り替わります。
コンポーネント API
PanelGroup の主なプロパティ
| プロパティ | 型 | 説明 |
|-------------|----|------|
| id | string | data-panel-group-id にも反映される識別子。複数グループを並べる場合は指定を推奨 |
| direction | 'horizontal' \| 'vertical' | パネルの配置方向 |
| className / style | string / React.CSSProperties | コンテナの見た目を調整するための追加スタイル |
| children | React.ReactNode | グループ内の Panel と PanelResizeHandle を並べるための子要素 |
| showDebugInfo | boolean | PanelDebugInfo とグローバルオーバーレイを表示して計測結果と制約違反を可視化 |
| onLayout | (sizes: number[]) => void | リサイズや初期化のたびに最新のピクセルサイズ配列を通知 |
| autoSaveId | string | 指定すると localStorage にパネルサイズを保存・復元 |
Panel の主なプロパティ
| プロパティ | 型 | 説明 |
|-------------|----|------|
| id | string | パネル識別子。省略時はランダム生成だが、SSR との整合のため明示指定を推奨 |
| defaultSize | { value: number; unit: 'pixels' \| 'percentage' } \| number | 初期サイズ。数値のみの場合はパーセンテージとして扱う |
| minSize / maxSize | FlexibleSize | パネルの最小・最大サイズ制約。ピクセル・割合で指定可能 |
| collapsible | { from: 'start' \| 'end' \| 'both' } | 折りたたみ方向の宣言。ドラッグや API 呼び出しで利用される必須設定 |
| defaultCollapsed | boolean | 初期状態を折りたたみ済みにするかどうか |
| pixelAdjustPriority | number | 余白調整時にピクセル基準パネルへ割り当てる優先度 |
| className / style | string / React.CSSProperties | パネル本体の見た目をカスタマイズ |
PanelResizeHandle の主なプロパティ
| プロパティ | 型 | 説明 |
|-------------|----|------|
| id | string | ハンドル識別子。省略時はランダム生成 |
| disabled | boolean | true にするとドラッグ操作を無効化 |
| onDragging | (isDragging: boolean) => void | ドラッグ開始 / 終了ごとに呼び出されるコールバック |
| className / style | string / React.CSSProperties | ハンドル見た目を追加カスタマイズ |
| children | React.ReactNode | 独自のハンドルインジケーターを描画する場合に使用 |
フックとユーティリティ
useResizablePanels:PanelGroup内部実装でも利用しているフック。カスタムコンテナを作る場合に役立ちます。usePanelGroup: コンテキストを直接参照し、パネルリストやレイアウト情報を取得できます。usePanelControls: 特定パネルに対する折りたたみ / 展開操作を提供します。saveLayoutとloadLayout: 任意のタイミングでレイアウトを永続化 / 復元できます。getPanelElement,getPanelGroupElement,getResizeHandleElement:data-*属性をもとに DOM 要素を取得します。
レイアウト保存と復元
PanelGroup に autoSaveId を指定すると、ResizeObserver が安定した後のパネルサイズを 200ms デバウンス付きで localStorage に保存します。ページを再読み込みすると loadLayout(autoSaveId) の結果が自動的に反映され、ピクセル基準パネルと割合基準パネルの両方が復元されます。手動で制御したい場合は公開ユーティリティの saveLayout / loadLayout を直接呼び出してください。
デバッグと可視化
PanelGroup の showDebugInfo を有効にすると、各 Panel に PanelDebugInfo が表示され、折りたたみ状態・計測サイズ・保持中の割合が即座に確認できます。同時にグローバルオーバーレイが開き、コンテナの実測サイズ、制約違反の有無、ハンドル厚み(PANEL_HANDLE_THICKNESS で定義された 8px)なども一覧できます。
スナップとノイズ対策の挙動
PanelResizeHandle と Panel の両方で calculateSnapThreshold を利用し、コンテナサイズに応じたゼロスナップ閾値を共有しています。Panel コンポーネントでは ResizeObserver が一時的に極端な値を返した場合でも、noiseThreshold を用いたフィルタでチラつきを防ぎ、リデューサーが保持する状態と DOM 計測値の整合を保ちます。
スタイル
src/styles.css で Tailwind CSS レイヤーを読み込んでいます。グローバルに取り込み済みの環境では追加設定不要ですが、個別プロジェクトでユーティリティクラスを無効化したい場合はローカルの Tailwind 設定でオーバーライドしてください。
デモ
モノレポ内に簡易デモアプリを用意しています。
pnpm --filter '@aiquants/resize-panels-demo' devhttp://localhost:5175 にアクセスすると複数レイアウトを含むデモを確認できます。
ビルド
pnpm --filter '@aiquants/resize-panels' build生成物は dist に出力されます。
