HextaUI

Resizable

ドラッグで分割できるパネルです。ホバーで目覚める控えめな区切り線、リセットや折りたたみで滑らかに動くサイズ、永続化されるレイアウトに対応します。

ExplorerDrag the divider, or double-click it to reset.
EditorFocus a divider and use the arrow keys.
Terminal
import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"

function Pane({ title, children }: { title: string; children?: string }) {
  return (
    <div className="flex h-full flex-col gap-1 p-4">
      <span className="text-sm font-medium">{title}</span>
      <span className="text-sm text-muted-foreground">{children}</span>
    </div>
  )
}

export function ResizableDemo() {
  return (
    <div className="h-80 w-full max-w-2xl overflow-hidden rounded-xl border">
      <ResizablePanelGroup>
        <ResizablePanel defaultSize="28%" minSize="18%" maxSize="45%">
          <Pane title="Explorer">
            Drag the divider, or double-click it to reset.
          </Pane>
        </ResizablePanel>
        <ResizableHandle />
        <ResizablePanel>
          <ResizablePanelGroup orientation="vertical">
            <ResizablePanel defaultSize="65%" minSize="25%">
              <Pane title="Editor">
                Focus a divider and use the arrow keys.
              </Pane>
            </ResizablePanel>
            <ResizableHandle />
            <ResizablePanel minSize="15%">
              <Pane title="Terminal" />
            </ResizablePanel>
          </ResizablePanelGroup>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/resizable.json

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"
<ResizablePanelGroup>
  <ResizablePanel defaultSize="30%" minSize="20%">Sidebar</ResizablePanel>
  <ResizableHandle />
  <ResizablePanel>Content</ResizablePanel>
</ResizablePanelGroup>

数値はピクセル、文字列はパーセンテージなので、defaultSize="30%" と minSize={240} のどちらも使えます。区切りは触れるまで細い線のままです。ドラッグ中はパネルがポインターにぴったり追従し、それ以外の変更(ダブルクリックでのリセット、キーボード操作、コードからの折りたたみ)はなめらかに動きます。

ResizablePanelGroup
├── ResizablePanel
├── ResizableHandle
└── ResizablePanel

Collapsible

collapsible なパネルは、minSize を下回るまでドラッグすると閉じます。ボタンから usePanelRef で切り替えられ、折りたたみはアニメーションします。

"use client"

import * as React from "react"
import { IconLayoutSidebar } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
  usePanelRef,
} from "@/components/ui/resizable"

export function ResizableCollapsible() {
  const sidebar = usePanelRef()
  const [collapsed, setCollapsed] = React.useState(false)

  return (
    <div className="flex h-72 w-full max-w-2xl flex-col overflow-hidden rounded-xl border">
      <div className="flex items-center gap-2 border-b p-2">
        <Button
          variant="ghost"
          size="icon-sm"
          aria-label={collapsed ? "Show sidebar" : "Hide sidebar"}
          aria-pressed={!collapsed}
          onClick={() => {
            if (sidebar.current?.isCollapsed()) {
              sidebar.current.expand()
            } else {
              sidebar.current?.collapse()
            }
          }}
        >
          <IconLayoutSidebar />
        </Button>
        <span className="text-sm text-muted-foreground">
          Drag the sidebar narrow to collapse it, or press Enter on the divider.
        </span>
      </div>
      <div className="min-h-0 flex-1">
        <ResizablePanelGroup>
          <ResizablePanel
            panelRef={sidebar}
            collapsible
            defaultSize="30%"
            minSize="20%"
            collapsedSize="0%"
            onResize={(size) => setCollapsed(size.asPercentage === 0)}
          >
            <div className="flex h-full flex-col gap-1 p-4 text-sm">
              <span className="font-medium">Inbox</span>
              <span className="text-muted-foreground">Drafts</span>
              <span className="text-muted-foreground">Archive</span>
            </div>
          </ResizablePanel>
          <ResizableHandle />
          <ResizablePanel minSize="40%">
            <div className="flex h-full items-center justify-center text-sm font-medium">
              Message
            </div>
          </ResizablePanel>
        </ResizablePanelGroup>
      </div>
    </div>
  )
}

サイズ表示

showSize は、ドラッグまたはキーボード操作中に、区切りの前のパネルのサイズを "percent" または "pixels" で表示します。

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"

export function ResizableSize() {
  return (
    <div className="h-56 w-full max-w-xl overflow-hidden rounded-xl border">
      <ResizablePanelGroup>
        <ResizablePanel defaultSize="320px" minSize="160px" maxSize="70%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            Canvas
          </div>
        </ResizablePanel>
        <ResizableHandle showSize="pixels" />
        <ResizablePanel minSize="20%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            Inspector
          </div>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  )
}

レイアウトを記憶する

useDefaultLayout はサイズを localStorage に保存し、次回の訪問時にアニメーションなしで復元します。各パネルに id を付けてください。

"use client"

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
  useDefaultLayout,
} from "@/components/ui/resizable"

export function ResizablePersist() {
  const { defaultLayout, onLayoutChanged } = useDefaultLayout({
    id: "hextaui-resizable-persist",
  })

  return (
    <div className="h-56 w-full max-w-md overflow-hidden rounded-xl border">
      <ResizablePanelGroup
        defaultLayout={defaultLayout}
        onLayoutChanged={onLayoutChanged}
      >
        <ResizablePanel id="list" defaultSize="40%" minSize="20%">
          <div className="flex h-full items-center justify-center p-4 text-center text-sm font-medium">
            Resize me, then reload
          </div>
        </ResizablePanel>
        <ResizableHandle />
        <ResizablePanel id="detail" minSize="20%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            Detail
          </div>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  )
}

垂直

orientation="vertical" はパネルを縦に積みます。グループは上のデモのようにネストできます。

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"

export function ResizableVertical() {
  return (
    <div className="h-72 w-full max-w-md overflow-hidden rounded-xl border">
      <ResizablePanelGroup orientation="vertical">
        <ResizablePanel defaultSize="30%" minSize="20%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            Header
          </div>
        </ResizablePanel>
        <ResizableHandle />
        <ResizablePanel minSize="30%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            Content
          </div>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  )
}

常に見えるグリップ

withHandle は、ホバー時に表示する代わりにグリップを表示したままにします。

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"

export function ResizableWithHandle() {
  return (
    <div className="h-56 w-full max-w-md overflow-hidden rounded-xl border">
      <ResizablePanelGroup>
        <ResizablePanel defaultSize="40%" minSize="20%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            Preview
          </div>
        </ResizablePanel>
        <ResizableHandle withHandle />
        <ResizablePanel minSize="20%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            Code
          </div>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  )
}

右から左

パネルと矢印キーは文字の読み方向に従います。

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"

export function ResizableRtl() {
  return (
    <div
      dir="rtl"
      className="h-56 w-full max-w-md overflow-hidden rounded-xl border"
    >
      <ResizablePanelGroup>
        <ResizablePanel defaultSize="35%" minSize="20%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            القائمة
          </div>
        </ResizablePanel>
        <ResizableHandle showSize />
        <ResizablePanel minSize="20%">
          <div className="flex h-full items-center justify-center text-sm font-medium">
            المحتوى
          </div>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  )
}
キーアクション
←→↑↓フォーカスされている区切りを、そのグループの方向に5%動かします。
HomeEndパネルが許す限り区切りを動かします。
Enter折りたたみ可能なパネルを折りたたむか展開します。
F6グループ内の次の区切りに移動します。Shift で戻ります。
  • 各区切りはフォーカス可能な separator で、その前のパネルのサイズを報告します。
  • サイズ表示は装飾です。スクリーンリーダーには separator 自身の値が伝えられます。
  • 視差効果の軽減が有効な場合、サイズは即座に変化し、区切りはアニメーションしません。
プロパティ型デフォルト
orientation
"horizontal" | "vertical""horizontal"
defaultLayout
Layout–
onLayoutChangeドラッグ中を含むすべての変更。
(layout) => void–
onLayoutChanged変更が落ち着いたとき。
(layout, meta) => void–
disabled
booleanfalse
groupRef
Ref<GroupImperativeHandle>–
属性説明
data-slot="resizable-panel-group"グループ。
data-readyマウント後に設定され、以降サイズはアニメーションします。
プロパティ型デフォルト
defaultSize
number | string–
minSize
number | string–
maxSize
number | string–
collapsible
booleanfalse
collapsedSize
number | string0
onResize
(size, id, previous) => void–
panelRef
Ref<PanelImperativeHandle>–
idレイアウトを記憶するために必要です。
string–
プロパティ型デフォルト
withHandleグリップを常に表示します。
booleanfalse
showSize
boolean | "percent" | "pixels"false
disableDoubleClickダブルクリックでパネルがリセットされないようにします。
booleanfalse
disabled
booleanfalse
属性説明
data-slot="resizable-handle"区切り。
data-separator"inactive"、"hover"、"active"、"focus"、"disabled"。
data-slot="resizable-handle-grip"グリップのピル。

usePanelRef と useGroupRef は collapse、expand、resize、setLayout を提供します。useDefaultLayout はグループのレイアウトを永続化します。

使用しているブロック

Resizable の上に構築されるブロック。