Przejdź do treści

Resizable

Stabilny

Panele, których rozmiar można zmieniać przeciąganiem lub z klawiatury — do układów, których proporcje każdy ustawia po swojemu.

Pasek boczny
Nagłówek
Treść

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/resizable.json

Użycie

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable"
<div className="h-80">
  <ResizablePanelGroup orientation="horizontal">
    <ResizablePanel defaultSize="30%" minSize="20%">Sidebar</ResizablePanel>
    <ResizableHandle />
    <ResizablePanel defaultSize="70%">Content</ResizablePanel>
  </ResizablePanelGroup>
</div>

Grupa zawsze wypełnia element nadrzędny — rozmiar ustalaj na nim, a nie na grupie. Rozmiary przyjmują ciągi znaków z jednostkami ("30%", "240px", "16rem"); sama liczba oznacza piksele, a ciąg znaków bez jednostki — procenty.

Przechodzisz z react-resizable-panels v2?
prfct korzysta z v4. direction to teraz orientation, PanelResizeHandle to Separator (opakowany jako ResizableHandle), a defaultSize={30} oznacza 30 pikseli — pisz defaultSize="30%".

Przykłady

Układ pionowy

Ułóż panele jeden nad drugim przez orientation="vertical" — zapytanie nad wynikami, edytor nad konsolą.

Zapytanieselect * from deployments where status = 'error'
3 wiersze · 12 ms

Z uchwytem

withHandle rysuje na separatorze mały uchwyt. Używaj go, gdy separator łatwo przeoczyć — przy wąskich panelach, na powierzchniach o niskim kontraście, na urządzeniach dotykowych.

Przed
Po

Układ zagnieżdżony

Grupy można zagnieżdżać, budując prawdziwe układy aplikacji. Tutaj eksplorator plików ma collapsible oraz minSizemaxSize, więc po przeciągnięciu poniżej minimum od razu się zwija; terminal też da się zwinąć. Ustaw fokus na separatorze i naciśnij Enter, aby zwinąć lub przywrócić panel przed nim.

componentsbutton.tsxdialog.tsxtokens.cssutils.ts
button.tsx
1import { cva } from "class-variance-authority"
2
3export const buttonVariants = cva([
4 "inline-flex items-center",
5])
Terminal
$ pnpm dev
 Ready in 594ms

Zapamiętywanie układu

useDefaultLayout przywraca zapisany układ przy montowaniu i zapisuje go po każdej zmianie rozmiaru. Nadaj każdemu panelowi id, aby zapisane rozmiary trafiały do właściwych paneli. localStorage istnieje tylko w przeglądarce, dlatego ten podgląd renderuje na serwerze układ domyślny, a po hydratacji przełącza się na zapisany.

Lista
SzczegółyZmień rozmiar, a potem odśwież stronę.

W aplikacji Next.js renderowanej na serwerze lepiej zapisywać układ w pliku cookie: serwer może go odczytać, więc już pierwsze wyrenderowanie strony uwzględnia zapisane rozmiary i nic się nie przesuwa.

app/inbox/layout.tsx
import { cookies } from "next/headers"

import { InboxPanels } from "./inbox-panels"

export default async function InboxLayout() {
  const saved = (await cookies()).get("layout:inbox")?.value
  return <InboxPanels defaultLayout={saved ? JSON.parse(saved) : undefined} />
}
app/inbox/inbox-panels.tsx
"use client"

import type { Layout } from "react-resizable-panels"

export function InboxPanels({ defaultLayout }: { defaultLayout?: Layout }) {
  return (
    <ResizablePanelGroup
      defaultLayout={defaultLayout}
      onLayoutChanged={(layout) => {
        document.cookie = `layout:inbox=${JSON.stringify(layout)}; path=/; max-age=31536000`
      }}
    >
      <ResizablePanel id="list" defaultSize="40%" minSize="25%"></ResizablePanel>
      <ResizableHandle />
      <ResizablePanel id="detail" defaultSize="60%"></ResizablePanel>
    </ResizablePanelGroup>
  )
}

Wytyczne

Kiedy używać

  • W narzędziach, w których spędza się długie sesje i ma się wyraźne preferencje co do przestrzeni: w edytorach, poczcie, menedżerach plików, pulpitach z panelami bocznymi.
  • Gdy dwa widoki rywalizują o miejsce, a właściwy podział zależy od zadania.

Kiedy nie używać

  • Na stronach marketingowych i treściowych, gdzie układ powinien być skomponowany z myślą o czytelniku.
  • Na małych ekranach. Poniżej breakpointu md zamiast dzielonych paneli przejdź na jedną kolumnę, zakładki lub panel Sheet.
  • Do pokazywania i ukrywania całego panelu — przycisk przełączający jest czytelniejszy niż przeciąganie do zera.

Ustaw limity

Zawsze nadawaj panelom minSize, aby treści nie dało się zgnieść do bezużyteczności, oraz maxSize, gdy jedna strona musi pozostać widoczna. Oznacz panel jako collapsible, gdy całkowite ukrycie go jest uzasadnionym wyborem.

min. 25%
min. 40%
Dobrze.Rozsądne minima sprawiają, że każdy panel pozostaje użyteczny, niezależnie od przeciągania.
Bez limitów
Źle.Bez limitów panel można zwęzić do bezużytecznego skrawka.

Zapamiętuj wybór użytkownika

Układ dopasowany przez użytkownika to jego preferencja. Zapisz go przez useDefaultLayout (albo we własnym magazynie danych przez onLayoutChanged), aby przetrwał przeładowanie strony.

Dostępność

Każdy separator to element role="separator", który może otrzymać fokus. Atrybuty aria-valuenow, aria-valueminaria-valuemax opisują rozmiar panelu przed nim, natomiast aria-orientation wskazuje kierunek prostopadły do grupy (grupa pozioma ma separatory pionowe).

KlawiszDziałanie
Tab
Przenosi fokus na następny separator. Separator z fokusem przyjmuje kolor marki.
Zmienia rozmiar w grupie poziomej o 5%.
Zmienia rozmiar w grupie pionowej o 5%.
Home
Zmniejsza panel przed separatorem do minimum.
End
Powiększa panel przed separatorem do maksimum.
Enter
Zwija panel przed separatorem lub go przywraca — o ile ten panel da się zwinąć.
F6ShiftF6
Przenosi fokus na następny lub poprzedni separator w tej samej grupie.
  • Dwukrotne kliknięcie separatora przywraca domyślne rozmiary sąsiednich paneli (wyłączysz to przez disableDoubleClick).
  • Obszar kliknięcia. Separatory mają 1 px szerokości, ale ich obszar kliknięcia jest większy — biblioteka wymusza minimalny rozmiar celu, większy przy mniej precyzyjnych wskaźnikach, np. przy dotyku.
  • Nazwij panele. Nadaj panelom nagłówki lub aria-label, aby użytkownik czytnika ekranu wiedział, jakiemu panelowi zmienia rozmiar.

Dokumentacja API

ResizablePanelGroup

Kontener, który rozmieszcza panele i separatory. Renderuje <div> wypełniający element nadrzędny. Opakowuje Group z react-resizable-panels v4.

PropTypDomyślnie
orientation

Kierunek, w którym panele są układane i zmieniają rozmiar.

"horizontal" | "vertical""horizontal"
defaultLayout

Rozmiary paneli (według id panelu, w procentach) przywracane przy montowaniu.

Record<string, number>Brak wartości domyślnej
onLayoutChanged

Wywoływana po zakończeniu zmiany rozmiaru. meta.isUserInteraction ma wartość true, gdy zmiana nastąpiła wskaźnikiem lub z klawiatury.

(layout, meta) => voidBrak wartości domyślnej
onLayoutChange

Wywoływana nieprzerwanie w trakcie zmiany rozmiaru. Do zapisywania wybieraj onLayoutChanged.

(layout) => voidBrak wartości domyślnej
disabled

Wyłącza zmianę rozmiaru w całej grupie.

booleanfalse
resizePreviewMode

Zmienia rozmiar paneli na żywo albo pokazuje podgląd separatora i stosuje zmianę po puszczeniu.

"panel" | "separator""panel"
groupRef

Imperatywne API: getLayout() i setLayout().

Ref<GroupImperativeHandle>Brak wartości domyślnej

ResizablePanel

Obszar o zmiennym rozmiarze. className trafia do wewnętrznego <div>, więc klasy układu nie kolidują z rozmiarem panelu wyznaczanym przez flex. Opakowuje Panel.

PropTypDomyślnie
id

Stały identyfikator. Wymagany do zapisywania i przywracania układów.

string | numberBrak wartości domyślnej
defaultSize

Rozmiar początkowy. Liczby oznaczają piksele; ciągi znaków mogą używać %, px, rem, em, vh lub vw.

number | stringBrak wartości domyślnej
minSize

Najmniejszy rozmiar, do jakiego można zmniejszyć panel.

number | stringBrak wartości domyślnej
maxSize

Największy rozmiar, do jakiego można powiększyć panel.

number | stringBrak wartości domyślnej
collapsible

Pozwala panelowi zwinąć się do collapsedSize po przeciągnięciu poniżej minSize.

booleanfalse
collapsedSize

Rozmiar zwiniętego panelu.

number | string"0%"
onResize

Wywoływana, gdy zmienia się rozmiar tego panelu.

(size, id, previousSize) => voidBrak wartości domyślnej
panelRef

Imperatywne API: collapse(), expand(), isCollapsed(), getSize() i resize().

Ref<PanelImperativeHandle>Brak wartości domyślnej

ResizableHandle

Separator między dwoma panelami. Opakowuje Separator.

PropTypDomyślnie
withHandle

Pokazuje uchwyt na separatorze.

booleanfalse
disabled

Sprawia, że ten separator nie zmienia rozmiaru sąsiednich paneli.

booleanfalse
disableDoubleClick

Wyłącza przywracanie domyślnych rozmiarów paneli dwukrotnym kliknięciem.

booleanfalse

Separator udostępnia swój stan w atrybucie data-separator: inactive, hover, active (przeciąganie), focus lub disabled — prfct koloruje go skalą marki po najechaniu, podczas przeciągania i przy fokusie.