Sheet
Một dialog trượt vào từ một cạnh màn hình và chạy dọc cạnh đó: chi tiết của một hàng, bộ lọc, cài đặt, một form cạnh trang mà nó sửa. Dialog này dùng chung scrim, nền raised và button đóng của Dialog; chỉ cạnh hướng vào nội dung mới có viền và góc trang.
Xem trước
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent handleLabel="Đóng">
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>Cách dùng
Khi nào dùng
- Việc làm cạnh trang: chi tiết của một hàng, bộ lọc, một form ngắn.
- Điều hướng trên màn hình hẹp, từ cạnh đầu, hoặc vài lựa chọn trên điện thoại, từ cạnh dưới.
Khi nào không dùng
- Xác nhận một hành động xóa dữ liệu. Dùng AlertDialog.
- Một việc cần tập trung và đứng riêng, như đổi tên một dự án. Dùng Dialog.
- Một menu các hành động. Dùng DropdownMenu.
Biến thể
side
Đặt trên SheetContent. Cạnh mà sheet trượt vào. right cho chi tiết và form cạnh một danh sách; left cho điều hướng; bottom cho vài lựa chọn ngắn trên điện thoại; top thì hiếm.
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent side="top">
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent side="bottom">
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent side="left">
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>size
Đặt trên SheetContent. Rộng nhất khi ở trái và phải (sm 320px, md 448px, lg 672px), cao nhất khi ở trên và dưới (một phần ba, một nửa, ba phần tư màn hình). full phủ màn hình dọc cạnh đó, không có góc.
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent size="sm">
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent size="lg">
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent size="full">
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>Ví dụ
Sửa chi tiết
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sửa dự án</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Sửa dự án</SheetTitle>
<SheetDescription>Các thay đổi hiển thị với mọi người trong nhóm của bạn.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Tên</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Hủy</Button>
</SheetClose>
<Button>Lưu</Button>
</SheetFooter>
</SheetContent>
</Sheet>Từ cạnh dưới
Vài lựa chọn ngắn trên điện thoại, hành động chính cách một lần chạm.
import { Button } from "@/components/ui/button"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sắp xếp</Button>
</SheetTrigger>
<SheetContent side="bottom" size="sm">
<SheetHeader>
<SheetTitle>Sắp xếp dự án</SheetTitle>
</SheetHeader>
<RadioGroup aria-label="Sắp xếp dự án" defaultValue="recent">
<RadioGroupItem value="recent" label="Chỉnh sửa gần đây" />
<RadioGroupItem value="name" label="Tên" />
</RadioGroup>
</SheetContent>
</Sheet>Nên và không nên
Nên
- Dùng cho việc làm cạnh trang: chi tiết của một hàng, bộ lọc, một form ngắn.
- Cho sheet một title, và đặt hành động chính trong footer.
- Giữ sheet mở khi người dùng làm việc trong đó; đóng khi hành động xong.
Không nên
- Đừng dùng cho một xác nhận ngắn; dùng AlertDialog hoặc Dialog.
- Đừng mở một sheet từ một sheet.
- Đừng dùng cho một menu các hành động; dùng DropdownMenu.
Props
| Tên | Kiểu | Giá trị | Mặc định | Mô tả |
|---|---|---|---|---|
| open | boolean | - | - | Đặt trên Sheet. Sheet đang mở hay không, khi bạn kiểm soát nó. Đi cùng onOpenChange. Chỉ trên web. |
| defaultOpen | boolean | - | false | Đặt trên Sheet. Mở ở lần render đầu, khi bạn không kiểm soát nó. Chỉ trên web. |
| onOpenChange | function | - | - | Đặt trên Sheet. Được gọi với true hoặc false khi mở hoặc đóng (trigger, button đóng, Escape, scrim). Chỉ trên web. |
| side | enum | top | right | bottom | left | right | Đặt trên SheetContent. Cạnh mà sheet trượt vào. right cho chi tiết và form cạnh một danh sách; left cho điều hướng; bottom cho vài lựa chọn ngắn trên điện thoại; top thì hiếm. |
| size | enum | sm | md | lg | full | md | Đặt trên SheetContent. Rộng nhất khi ở trái và phải (sm 320px, md 448px, lg 672px), cao nhất khi ở trên và dưới (một phần ba, một nửa, ba phần tư màn hình). full phủ màn hình dọc cạnh đó, không có góc. |
| showCloseButton | boolean | - | true | Đặt trên SheetContent. Button đóng ở góc. Chỉ giấu khi footer có Hủy hoặc Đóng. Chỉ trên web. |
| swipeToDismiss | boolean | - | true | Chỉ side bottom (bị bỏ qua ở các side khác): kéo sheet xuống để đóng. Luôn bắt đầu trên handle hoặc header, còn trên body chỉ khi đã cuộn tới đỉnh. Kéo lên thì có lực cản và không bao giờ làm sheet cao thêm. Thả thì đóng khi cú kéo vượt 25 phần trăm chiều cao của sheet hoặc 80px, hoặc tốc độ thả ít nhất 0.5 px/ms hướng xuống; nếu không thì sheet bật lại với duration-medium và ease-standard. Scrim mờ dần theo độ lệch. Lúc đóng kết thúc bằng animate-exit-slide-bottom và gọi onOpenChange(false) đúng một lần, nên focus trở về trigger như Escape. Khi reduced motion, sheet vẫn theo ngón tay, nhưng cú bật lại và lúc thoát thì fade. Escape, button đóng và scrim vẫn hoạt động. Chỉ trong spec: code registry chưa làm phần này. |
| handleLabel | string | - | Close | Đặt trên SheetContent, chỉ side bottom. Tên accessible của handle kéo; kích hoạt thì đóng sheet. Mặc định EN `Close`, VI `Đóng`. Chỉ trong spec: code registry chưa làm phần này. |
Slot
| Tên | Bắt buộc | Mô tả |
|---|---|---|
| trigger | không | SheetTrigger với asChild bọc một Button. |
| title | có | SheetTitle, bắt buộc cho a11y. |
| description | không | SheetDescription tùy chọn. |
| content | không | Phần body: các field, chi tiết, một danh sách. Phần này cuộn cùng panel. |
| footer | không | SheetFooter với các hành động. |
Cấu tạo
| Phần | Mô tả |
|---|---|
| root | Sheet. Giữ trạng thái mở. |
| overlay | Scrim phía sau sheet, giống của Dialog. |
| content | Panel: nền raised, shadow-high, p-xl, gap-lg. Panel phủ hết chiều của cạnh; viền và góc trang chỉ ở cạnh hướng vào nội dung. |
| header | Khối trên cho title và description, gap-sm. |
| title | Tên của sheet: 16px medium. |
| description | Chữ hỗ trợ tùy chọn: 14px secondary. |
| footer | Các hành động ở cuối panel, dù nội dung có ngắn. |
| close | Icon button ở góc dùng để đóng sheet, giống của Dialog. |
| icon | Tabler X trong button đóng, 16px. |
| hitArea | ::before trong suốt trên button đóng, không nhỏ hơn size-target-min. |
Khả năng truy cập
Role: dialog
| Phím | Hành động |
|---|---|
| Escape | Đóng sheet và trả focus về trigger. |
| Tab | Luân chuyển focus bên trong sheet (focus trap). |
- Một dialog modal: nó bẫy focus, giấu phần còn lại của trang khỏi công nghệ hỗ trợ và đóng khi bấm Escape hoặc nhấn scrim.
- Luôn có SheetTitle (nhìn thấy hoặc sr-only), để sheet có tên.
- Button đóng ở góc có nhãn Đóng giấu khỏi mắt và một Tabler X. Với showCloseButton={false}, footer phải có Hủy hoặc Đóng.
- Sheet chỉ trượt vào khi người dùng cho phép chuyển động; dưới prefers-reduced-motion thì fade vào. Khi đóng, sheet trượt ra lại đúng cạnh đó, nhanh hơn (duration-medium, ease-exit), hoặc fade ra.
- Đừng mở một sheet từ một sheet.
- Sheet bottom đệm cạnh dưới bằng env(safe-area-inset-bottom), cần viewport-fit=cover trong meta viewport và lùi về 0px. Vuốt để đóng là một lối thêm; người dùng bàn phím và trình đọc màn hình đóng bằng Escape hoặc button đóng.
Cài đặt
npx werewolf-ui add sheetChạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.