first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
# Drawer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - drawer.variants — `drawerOverlayVariants`, `drawerC…
- [drawer.variants](./drawer.variants.md) — `drawerOverlayVariants`, `drawerContentVariants`, `drawerHandleVariants`, `drawerHeaderVariants`, `drawerFooterVariants…
- drawer.variants — `drawerOverlayVariants`, `drawerC…
- [drawer.variants](./drawer.variants.md) — `drawerOverlayVariants`, `drawerContentVariants`, `drawerHandleVariants`, `drawerHeaderVariants`, `drawerFooterVariants…
- drawer.variants — `drawerOverlayVariants`, `drawerC…
- [drawer.variants](./drawer.variants.md) — `drawerOverlayVariants`, `drawerContentVariants`, `drawerHandleVariants`, `drawerHeaderVariants`, `drawerFooterVariants…
- drawer.variants — `drawerOverlayVariants`, `drawerC…
- [drawer.variants](./drawer.variants.md) — `drawerOverlayVariants`, `drawerContentVariants`, `drawerHandleVariants`, `drawerHeaderVariants`, `drawerFooterVariants…
- [drawer.variants](./drawer.variants.md) — `drawerOverlayVariants`, `drawerContentVariants`, `drawerHandleVariants`, `drawerHeaderVariants`, `drawerFooterVariants…
## Purpose
`<Drawer>` and its sub-components (`<DrawerTrigger>`, `<DrawerPortal>`, `<DrawerOverlay>`, `<DrawerContent>`, `<DrawerHandle>`, `<DrawerHeader>`, `<DrawerFooter>`, `<DrawerTitle>`, `<DrawerDescription>`, `<DrawerClose>`) are the chassis bottom-anchored modal panel primitives built on `vaul`. Vaul wraps `@radix-ui/react-dialog` internally so the WAI-ARIA dialog semantics (focus-trap, restore-focus, Escape closes, click-outside closes) are inherited; vaul layers on the swipe-down dismiss gesture, snap-points, and background-scale animation that distinguish a Drawer from a Sheet.
Per the WAI-ARIA dialog pattern, every drawer MUST contain a `<DrawerTitle>` so the accessible name is announced. Use `className="sr-only"` if the visible UI does not need a heading. Drawer is mobile-first — the panel slides up from the bottom and is grabbable by the small handle at the top.
## Features
- Bottom-anchored slide-up panel with swipe-down dismiss gesture (vaul-native).
- Snap-points support via vaul (`snapPoints={[0.4, 0.8]}`).
- Background-scale animation while the drawer is open (`shouldScaleBackground`, default `true`).
- Drag handle at the top of the panel (rendered automatically inside `<DrawerContent>`).
- Focus-trap on open; restore-focus on close (vaul → Radix-managed).
- Escape closes the drawer; click on the overlay backdrop closes the drawer.
- All sub-components forward refs.
- Theme-driven cva overrides for every slot via `useThemedVariants("Drawer<Slot>")`.
## Usage: Basic Example
```tsx
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@suaveplan/react-ui";
export function AccountDrawer() {
return (
<Drawer>
<DrawerTrigger>Open settings</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Account settings</DrawerTitle>
<DrawerDescription>
Manage your account preferences.
</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose>Close</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}
```
## Usage: Advanced Example
```tsx
import {
Button,
Drawer,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
} from "@suaveplan/react-ui";
import { useState } from "react";
export function SnappingFiltersDrawer() {
const [open, setOpen] = useState(false);
return (
<Drawer
open={open}
onOpenChange={setOpen}
snapPoints={[0.4, 0.7, 1.0]}
shouldScaleBackground
>
<Button onClick={() => setOpen(true)}>Open filters</Button>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Filters</DrawerTitle>
<DrawerDescription>
Drag the handle to expand.
</DrawerDescription>
</DrawerHeader>
<div className="p-4">…filter controls…</div>
<DrawerFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button onClick={() => setOpen(false)}>Apply</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}
```
## API Reference
### `<Drawer>`
Wraps vaul's `Drawer.Root`. Inherits every prop from vaul including:
| Prop | Type | Default | Description |
|---|---|---|---|
| `open` | `boolean` | _uncontrolled_ | Controlled open state. |
| `defaultOpen` | `boolean` | `false` | Initial open state. |
| `onOpenChange` | `(open: boolean) => void` | _none_ | Fires on open/close transitions. |
| `snapPoints` | `(number \| string)[]` | _none_ | Optional snap-point heights. |
| `shouldScaleBackground` | `boolean` | `true` | Scale the page behind the drawer while open. |
| `dismissible` | `boolean` | `true` | Allow Escape / click-outside / drag-down dismissal. |
| `modal` | `boolean` | `true` | Keep focus trapped inside the drawer. |
### `<DrawerContent>`
Renders the bottom-anchored panel. Self-portals via `<DrawerPortal>` and renders a drag handle at the top.
### `<DrawerTitle>`
REQUIRED — vaul/Radix surfaces a dev warning when omitted. Render `className="sr-only"` if no visible heading is needed.
### `<DrawerTrigger>`, `<DrawerPortal>`, `<DrawerOverlay>`, `<DrawerHandle>`, `<DrawerHeader>`, `<DrawerFooter>`, `<DrawerDescription>`, `<DrawerClose>`
Direct re-exports of the vaul sub-components with the chassis cva applied.
## Implementation Notes
- The chassis NEVER assigns a numeric `z-index` to the overlay or content panels — vaul/Radix portals handle stacking.
- `<DrawerContent>` self-portals via `<DrawerPortal>` and renders a `<DrawerHandle>` at the top automatically — consumers do not need to compose them manually. This matches the shadcn `drawer` upstream recipe.
- Drag-down dismissal is a vaul-native gesture that requires real pointer events; happy-dom-based unit tests cover the open/close lifecycle via Escape and `onOpenChange` rather than the drag gesture itself. End-to-end coverage of the swipe gesture lives in the consumer's Playwright suite.
- `shouldScaleBackground` defaults to `true` (chassis-overrides vaul's `false`) because the scale-down effect is the primary visual cue distinguishing a Drawer from a Sheet.
- Vaul's `DrawerClose` button forwards to `onOpenChange(false)` rather than mutating internal state directly — controlled consumers should listen on `onOpenChange` and update their local `open` state to honour the close request.