---
description: Conventions for drawers using the DrawerLayer composition pattern. Applies when opening drawers via useDrawerLayer (forms, detail views, nested drawers).
paths:
  - "src/**/*Drawer*"
  - "src/**/*drawer*"
  - "src/**/layers/**/*"
---

# Drawer Layer Conventions

Rules for drawers using material-hu's DrawerLayer API with the Composition pattern.

---

## 1. Pattern choice

| Scenario | Pattern |
|----------|---------|
| Content (e.g. form) owns all state that affects the drawer UI (title, loading, buttons) | **Composition + useDrawerLayer** (default) |
| Opener/parent must drive drawer config reactively while open | **useDrawerLayerItem** (rare — wizard with external step title, etc.) |

For most "form in a drawer" cases, use **Composition + useDrawerLayer**.

---

## 2. Composition pattern structure

The drawer content renders its own chrome using `Drawer.*` sub-components:

```tsx
import Drawer from '@material-hu/components/layers/Drawers/Drawer';
import { useDrawerLayer } from '@material-hu/components/layers/Drawers';

const MyFormDrawer = () => {
  const { closeDrawer } = useDrawerLayer();
  const { mutateAsync, isPending } = useMyMutation();

  const form = useForm({ ... });

  const onSubmit = form.handleSubmit((data) => mutateAsync(data));

  return (
    <>
      <Drawer.Header title="Crear proceso" onClose={closeDrawer} />
      <Drawer.Body>
        {/* Form fields here */}
      </Drawer.Body>
      <Drawer.Actions
        primaryButtonProps={{
          children: 'Guardar',
          onClick: onSubmit,
          loading: isPending,
        }}
        secondaryButtonProps={{
          children: 'Cancelar',
          onClick: closeDrawer,
        }}
      />
    </>
  );
};
```

Key rules:
- The **content** renders `Drawer.Header`, `Drawer.Body`, `Drawer.Actions`
- The **content** calls `closeDrawer()` — the opener never passes `onClose`
- Submit/cancel buttons live in `Drawer.Actions`, not as custom buttons inside the body
- Loading state for the primary button comes from the mutation's `isPending`

---

## 3. Opening a drawer

The opener uses `useDrawerLayer` and passes the content component:

```tsx
const { openDrawer } = useDrawerLayer();

const handleOpenCreate = () => {
  openDrawer({
    content: <MyFormProvider />,
    wrapperProps: { PaperProps: { sx: { maxWidth: 480 } } },
  });
};
```

- Do NOT pass `onClose`, `onSuccess`, or button props from the opener
- The content is self-contained — it owns close, submit, and all UI state
- Use `wrapperProps` for drawer width or other container customization

---

## 4. Form-in-drawer architecture

Split responsibilities:

```
Opener (list page, menu)
  └─ openDrawer({ content: <FormProvider /> })

FormProvider          ← fetches data, handles loading, renders form when ready
  └── FormDrawer      ← useForm, Drawer.Header/Body/Actions, submit
        └── FormContent  ← fields only (optional split for reuse)
```

- **Provider**: data loading + default values. No drawer chrome.
- **FormDrawer**: `useForm`, `Drawer.*` composition, submit handler.
- **FormContent** (optional): pure fields, reads `useFormContext()`. Useful when the same fields appear in a drawer and a full page.

---

## 5. Nested drawers

A drawer can open another drawer using the same pattern:

```tsx
// Inside a drawer's content
const { openDrawer, closeDrawer } = useDrawerLayer();

const handleOpenNestedForm = () => {
  openDrawer({
    content: <NestedFormDrawer />,
  });
};
```

The nested drawer renders its own `Drawer.Header/Body/Actions` and calls `closeDrawer()` to close itself (closes only the topmost drawer).

---

## 6. Checklist

- [ ] Content renders `Drawer.Header`, `Drawer.Body`, `Drawer.Actions`
- [ ] No `onClose` or button props passed from opener to content
- [ ] Content calls `closeDrawer()` from `useDrawerLayer()`
- [ ] Submit/cancel buttons are in `Drawer.Actions`, not custom buttons in the body
- [ ] Primary button uses `loading: isPending` from the mutation
