Resizable
StabilnyPanele, których rozmiar można zmieniać przeciąganiem lub z klawiatury — do układów, których proporcje każdy ustawia po swojemu.
Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/resizable.jsonUż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.
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ą.
select * from deployments where status = 'error'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.
Układ zagnieżdżony
Grupy można zagnieżdżać, budując prawdziwe układy aplikacji. Tutaj eksplorator plików ma collapsible oraz minSize i maxSize, 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.
1import { cva } from "class-variance-authority"23export const buttonVariants = cva([4 "inline-flex items-center",5])
$ 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.
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.
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} />
}"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
mdzamiast 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.
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-valuemin i aria-valuemax opisują rozmiar panelu przed nim, natomiast aria-orientation wskazuje kierunek prostopadły do grupy (grupa pozioma ma separatory pionowe).
| Klawisz | Dział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.
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.
ResizableHandle
Separator między dwoma panelami. Opakowuje Separator.
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.