Przejdź do treści

Drawer

Stabilny

Panel projektowany z myślą o dotyku, który wysuwa się od krawędzi, podąża za palcem, zatrzymuje się w punktach przyciągania i zamyka gestem przesunięcia.

Instalacja

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

Użycie

import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"
<Drawer showSwipeHandle>
  <DrawerTrigger render={<Button variant="outline" />}>Set daily goal</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Daily move goal</DrawerTitle>
      <DrawerDescription>Set your daily activity goal.</DrawerDescription>
    </DrawerHeader>
    {/* … */}
    <DrawerFooter>
      <DrawerClose render={<Button size="lg" />}>Save goal</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Szuflada opiera się na komponencie Drawer z Base UI: panel podąża za wskaźnikiem podczas przeciągania, zamyka się, gdy gest jest wystarczająco szybki lub długi, i osiada w najbliższym punkcie zatrzymania, zamiast stanąć tam, gdzie puścił go palec.

Przykłady

Panel dolny

Wariant domyślny: szuflada, która wysuwa się od dołu i zamyka przesunięciem w dół. showSwipeHandle renderuje uchwyt, który sygnalizuje, że panel można przeciągać. Wyśrodkuj treść w kontenerze z klasą max-w-*, żeby pozostała czytelna na tabletach — wzorcowym przykładem jest panel udostępniania.

Punkty przyciągania

snapPoints pozwala szufladzie zatrzymywać się na kilku wysokościach. Ułamki od 0 do 1 oznaczają część wysokości obszaru widoku, liczby większe od 1 — piksele, a ciągi znaków przyjmują pxrem. Szuflada otwiera się w pierwszym punkcie; przeciągnij ją w górę, żeby dotrzeć do następnego.

<Drawer showSwipeHandle snapPoints={[0.45, 1]}>

Kierunki

swipeDirection określa jednocześnie krawędź i gest zamykania: down (domyślnie), up, left albo right. Szuflady boczne zajmują na telefonach 75% szerokości, a od sm wzwyż mają 24 rem.

Zagnieżdżanie

Gdy otworzysz szufladę z wnętrza innej, szuflada nadrzędna cofa się w głąb stosu — nieco mniejsza i przygaszona — żeby było widać, dokąd się wróci. Gdy zamkniesz gestem szufladę podrzędną, nadrzędna znów wysunie się na pierwszy plan.

Responsywność

Na komputerze do wyśrodkowanego okna Dialog łatwiej sięgnąć kursorem; na telefonie dolna szuflada jest w zasięgu kciuka. Zachowaj wspólną treść i przełączaj kontener za pomocą useIsMobile.

const isMobile = useIsMobile()

return isMobile ? (
  <Drawer showSwipeHandle>{/* … */}</Drawer>
) : (
  <Dialog>{/* … */}</Dialog>
)

Wytyczne

Kiedy używać

  • Do zadań kontekstowych na urządzeniach dotykowych: wyboru wartości, krótkich formularzy, udostępniania, filtrów.
  • Gdy treść zyskuje na tym, że panel może zatrzymać się na kilku wysokościach — np. mapa z listą miejsc.

Kiedy nie używać

  • W desktopowych układach obsługiwanych głównie myszą — użyj Dialog albo Sheet.
  • Do potwierdzania akcji nieodwracalnych — użyj Alert Dialog, którego nie da się przypadkiem zamknąć gestem.
  • Do długich, złożonych formularzy — daj im osobną stronę.

Gest to skrót, nigdy jedyna droga

Wszystko, co robi gest, musi być możliwe także stuknięciem lub klawiszem. Zostaw w stopce widoczną akcję zamknięcia lub anulowania i nie polegaj na przesuwaniu między punktami przyciągania, żeby odsłonić kluczową treść.

Udostępnij album24 zdjęcia
Dobrze.Szufladę zamyka widoczna akcja; gest przesunięcia to tylko skrót.
Udostępnij albumPrzesuń w dół, aby zamknąć
Źle.Ukryte gesty są niewidoczne dla osób korzystających z czytnika ekranu i klawiatury.

W zasięgu kciuka

Główne akcje umieszczaj na dole szuflady, na całą szerokość, w rozmiarze lg (40 px wysokości). Tam są kciuki, a taki przycisk z zapasem spełnia wymóg minimalnego celu dotyku 24 px.

Dostępność

Szuflada jest modalnym oknem dialogowym: zatrzymuje fokus w środku, blokuje przewijanie strony, a nazwę i opis otrzymuje z DrawerTitleDrawerDescription.

KlawiszDziałanie
EnterSpacja
Na wyzwalaczu: otwiera szufladę i przenosi do niej fokus.
Tab
Przechodzi między kontrolkami szuflady. Dopóki jest otwarta, fokus nie wychodzi poza nią.
ShiftTab
Przenosi fokus wstecz.
Esc
Zamyka szufladę i przywraca fokus do wyzwalacza.
  • Zawsze dodawaj tytuł. Nadaje szufladzie nazwę dla technologii wspomagających; jeśli projekt go nie przewiduje, ukryj go klasą sr-only.
  • Uchwyt do przeciągania jest dekoracyjny i ukryty przed technologiami wspomagającymi — zapewnij prawdziwy przycisk zamykania.
  • Ruch. Podczas przeciągania szuflada podąża za wskaźnikiem 1:1, a potem osiada w ciągu 450 ms. Przy ograniczonym ruchu animacja osiadania znika; przeciąganie nadal podąża za palcem, bo ten ruch wywołuje człowiek, a nie interfejs.

Dokumentacja API

Drawer

Komponent główny. Nie renderuje żadnego elementu. Przyjmuje wszystkie propsy Drawer.Root z Base UI.

PropTypDomyślnie
swipeDirection

Krawędź, do której przylega szuflada, i kierunek gestu, który ją zamyka.

"down" | "up" | "left" | "right""down"
snapPoints

Pozycje, w których szuflada może się zatrzymać. 0–1 to ułamek wysokości obszaru widoku, > 1 to piksele, ciągi znaków przyjmują px i rem.

(number | string)[]Brak wartości domyślnej
snapPoint

Aktywny punkt przyciągania. Używaj razem z onSnapPointChange, żeby nim sterować.

number | string | nullBrak wartości domyślnej
defaultSnapPoint

Początkowy punkt przyciągania, w trybie niekontrolowanym.

number | string | nullBrak wartości domyślnej
onSnapPointChange

Wywoływana, gdy szuflada osiądzie w innym punkcie przyciągania.

(snapPoint, details) => voidBrak wartości domyślnej
snapToSequentialPoints

Przesuwa szufladę o jeden punkt przyciągania naraz, zamiast przeskakiwać punkty przy szybkich gestach.

booleanfalse
showSwipeHandle

Renderuje uchwyt do przeciągania. Tylko w prfct.

booleanfalse
open

Czy szuflada jest otwarta. Używaj razem z onOpenChange, żeby nią sterować.

booleanBrak wartości domyślnej
defaultOpen

Czy szuflada jest początkowo otwarta, w trybie niekontrolowanym.

booleanfalse
onOpenChange

Wywoływana, gdy szuflada się otwiera lub zamyka. Przy gestach details.reason ma wartość "swipe".

(open: boolean, details) => voidBrak wartości domyślnej
modal

true blokuje przewijanie i resztę strony; false pozostawia stronę interaktywną.

boolean | "trap-focus"true
disablePointerDismissal

Zapobiega zamknięciu po kliknięciu przyciemnionego tła.

booleanfalse

DrawerContent

Renderuje portal, przyciemnione tło, viewport i panel. Przyjmuje wszystkie propsy Drawer.Popup z Base UI, w tym initialFocusfinalFocus.

DrawerHeader, DrawerFooter

DrawerHeader zawiera tytuł i opis — w szufladach wysuwanych w pionie są one poniżej md wyśrodkowane, a powyżej wyrównane do lewej. DrawerFooter układa akcje jedna pod drugą, na całą szerokość, na dole panelu.

DrawerTitle, DrawerDescription

DrawerTitle renderuje <h2> i nadaje szufladzie nazwę. DrawerDescription renderuje <p> i ją opisuje.

DrawerTrigger, DrawerClose

Otwierają i zamykają szufladę. Oba renderują <button>; użyj render, żeby wyrenderować Button z prfct.