# Drawer

- Page: <https://kznlabs.github.io/labsUI/components/drawer>

## Drawer (`drawer`, component)

Edge panel (left, right, top, bottom) that springs in and swipes away. Shares Dialog's focus trap, scroll lock and Esc handling. Sidebar uses it below md.

- Install: `npx @kznlabs/labsui add drawer`
- Docs: <https://kznlabs.github.io/labsUI/components/drawer>
- Brings: button, button-variants, dialog, platform, use-controllable-state, utils
- npm: clsx@^2.1.1, lucide-react@1.63.0, motion@14.7.1, tailwind-merge@^3.7.0

### API

- `<Drawer>`
  - `children: ReactNode`: `DialogTrigger` and `DialogContent`.
  - `defaultOpen?: boolean` = `false`: Open at first render (uncontrolled).
  - `onOpenChange?: (open: boolean) => void`: Called on the trigger, Esc, backdrop click or `DialogClose`.
  - `open?: boolean`: Controlled state; pair with `onOpenChange`.
- `<DrawerTrigger>` (+ Button props)
  - `children?: ReactNode`: Label; its width is reserved so loading and success never resize the button.
  - `confirmLabel?: ReactNode`: Two-step action: first click swaps the label to this, second click runs `onClick`.
  - `confirmTimeout?: number`: Ms before an armed confirm step resets. Also resets on blur or Esc.
  - `loading?: boolean`: Disables the button and shows a spinner before the label.
  - `loadingLabel?: ReactNode`: Label shown while loading, with the spinner. Defaults to `children`.
  - `size?: "default" | "sm" | "lg" | "icon"`: Height and padding; "icon" is square, so give it an `aria-label`.
  - `status?: ButtonStatus`: In-place feedback: `loading` spins, `success` swaps the label to `successLabel`.
  - `successLabel?: ReactNode`: Text shown on `status="success"`, with a check icon. Defaults to "Done".
  - `swap?: SwapDirection`: Where the old label slides out to on a swap. Default "left".
  - `variant?: "default" | "secondary" | "outline" | "ghost" | "destructive"`: Fill and emphasis: one primary action per view, `destructive` for irreversible ones.
- `<DrawerClose>` (+ Button props)
  - `children?: ReactNode`: Label; its width is reserved so loading and success never resize the button.
  - `confirmLabel?: ReactNode`: Two-step action: first click swaps the label to this, second click runs `onClick`.
  - `confirmTimeout?: number`: Ms before an armed confirm step resets. Also resets on blur or Esc.
  - `loading?: boolean`: Disables the button and shows a spinner before the label.
  - `loadingLabel?: ReactNode`: Label shown while loading, with the spinner. Defaults to `children`.
  - `size?: "default" | "sm" | "lg" | "icon"`: Height and padding; "icon" is square, so give it an `aria-label`.
  - `status?: ButtonStatus`: In-place feedback: `loading` spins, `success` swaps the label to `successLabel`.
  - `successLabel?: ReactNode`: Text shown on `status="success"`, with a check icon. Defaults to "Done".
  - `swap?: SwapDirection`: Where the old label slides out to on a swap. Default "left".
  - `variant?: "default" | "secondary" | "outline" | "ghost" | "destructive"` = `"outline"`: Fill and emphasis: one primary action per view, `destructive` for irreversible ones.
- `<DrawerTitle>` (+ <h2> props)
- `<DrawerDescription>` (+ <p> props)
- `<DrawerContent>`: Panel that slides in from an edge on a spring. Swipe toward the edge to dismiss: side drawers
drag from anywhere, top/bottom drawers from the handle or header so their content can scroll.
  - `children: ReactNode`: Header, body and footer.
  - `showClose?: boolean` = `side === "left" || side === "right"`: Close button in the corner. On by default for side drawers.
  - `side?: Side` = `"right"`: Edge it slides in from.
- `<DrawerHeader>` (+ <div> props): Also a drag handle on top/bottom drawers.
- `<DrawerBody>` (+ <div> props): Scrolls when content is taller than the drawer.
- `<DrawerFooter>` (+ <div> props)

- `type Side = "left" | "right" | "top" | "bottom"`

### Usage

```tsx
<Drawer><DrawerTrigger>Cart</DrawerTrigger><DrawerContent side="right"><DrawerHeader><DrawerTitle>Cart</DrawerTitle></DrawerHeader><DrawerBody>…</DrawerBody></DrawerContent></Drawer>
```

### Example: cart-drawer.tsx

```tsx
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer";

export function CartDrawer({ items }: { items: string[] }) {
  return (
    <Drawer>
      <DrawerTrigger variant="outline">Cart</DrawerTrigger>
      <DrawerContent side="right">
        <DrawerHeader>
          <DrawerTitle>Your cart</DrawerTitle>
          <DrawerDescription>{items.length} items</DrawerDescription>
        </DrawerHeader>
        <DrawerBody>
          <ul>{items.map((i) => <li key={i}>{i}</li>)}</ul>
        </DrawerBody>
        <DrawerFooter>
          <DrawerClose>Keep shopping</DrawerClose>
          <DrawerClose variant="default">Checkout</DrawerClose>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  );
}
```
