DrawerCustom
DrawerCustom — низкоуровневая версия Drawer, которая не диктует структуру содержимого. Вы сами компонуете шапку, тело и футер из субкомпонентов DrawerCustom.Header, .Body, .Footer или собственной разметки.
Используйте DrawerCustom, когда стандартной шапки из Drawer недостаточно — например, нужна своя раскладка заголовка с несколькими действиями, кастомный футер с группами кнопок или нестандартный порядок секций.
Когда использовать
- Стандартная шапка / футер из
Drawerне подходят — нужна своя разметка. - Сложная раскладка нескольких секций внутри одной панели.
- Кастомные слоты (например, фиксированный поиск между шапкой и телом).
Во всех остальных случаях предпочтительнее Drawer — он дешевле в поддержке и даёт консистентные отступы.
Анатомия
Position
Сторона, с которой выезжает панель: right — стандартный side-panel (по умолчанию), left — для навигации и фильтров, top/bottom — для уведомлений и bottom-sheets на мобильных.
Width
Предустановленная ширина панели для position: left | right: s — для узких форм и фильтров, m — дефолт, l — для сложных форм и просмотрщиков. Также принимает число или строку CSS для точного контроля.
Установка
pnpm add @cloud-ru/ds-drawer
import { DrawerCustom } from '@cloud-ru/ds-drawer'
Примеры использования
Ручная композиция
import { Button } from '@cloud-ru/ds-button';
import { DrawerCustom } from '@cloud-ru/ds-drawer';
import { useState } from 'react';
export function CustomComposition() {
const [open, setOpen] = useState(false);
const close = () => setOpen(false);
return (
<>
<Button label='Открыть custom drawer' appearance='primary' view='filled' onClick={() => setOpen(true)} />
<DrawerCustom open={open} position='right' width='s' onClose={close}>
<DrawerCustom.Header title='Ручная композиция' subtitle='Header + Body + Footer собираются вручную.' />
<DrawerCustom.Body
content={
<div style={{ padding: 24 }}>
<p>Тело Drawer собирается из произвольной разметки.</p>
<p>Скролл включается автоматически при большом содержимом.</p>
</div>
}
/>
<DrawerCustom.Footer>
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
<Button label='Закрыть' appearance='neutral' view='outline' onClick={close} />
<Button label='Подтвердить' appearance='primary' view='filled' onClick={close} />
</div>
</DrawerCustom.Footer>
</DrawerCustom>
</>
);
}Props
Types
DrawerCustomProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined | — | no | |
className | string | — | no | CSS-класс для элемента с контентом |
closeOnPopstate | boolean | — | no | Закрывать дровер при перемещении по истории браузера |
container | string | HTMLElement | — | no | Контейнер в котором будет рендерится Drawer. По-умолчанию - body |
data-test-id | string | — | no | |
disableMotions | boolean | false | no | Отключить анимации |
footer | ReactElement<any, string | JSXElementConstructor<any>> | — | no | Футер |
heightAuto | boolean | false | no | Высота панели по контенту (только при `position: "top" | "bottom"`). |
nestedDrawer | ReactElement<Omit<DrawerCustomProps, "resizable">, string | JSXElementConstructor<any>> | — | no | Вложенный Drawer |
onClose | () => void | — | yes | Колбэк закрытия |
open | boolean | — | yes | Управление состоянием показан/не показан. |
position | "bottom" | "left" | "right" | "top" | — | yes | Расположение |
push | boolean | PushConfig | — | no | Смещение при открытии "вложенного" компонента |
resizable | { min: number; max?: number; default?: number; onResize?: ((width: number) => void) | undefined; onResizeEnd?: ((width: number) => void) | undefined; draggerTooltip?: string | undefined; } | undefined | 's' | no | Ширина (только при position: "left" | "right") |
rootClassName | string | — | no | CSS-класс для корневого элемента |
safeArea | boolean | true | no | Резервировать ли место под iOS notch / home-indicator и Android nav-bar. Реализовано паддингом на `.content` через `env(safe-area-inset-*)`: на устройстве без выреза/индикатора (и на desktop) inset = 0, поэтому никакого «лишнего» отступа не появляется; на notched-устройстве — ровно нужный. Верхний отступ добавляется только когда sheet раскрыт на полный вьюпорт (его верх под notch). |
showBlackout | boolean | true | no | Отображение темной подложки |
showButtonClosed | boolean | true | no | Отображение кнопки закрытия в шапке дровера |
snapPoints | SnapPoint[] | — | no | Массив фиксированных позиций sheet'а от меньшей к большей. По дефолту `undefined` — sheet `height: auto` с одним snap'ом по высоте контента. Пример: `[0.5, 1]` — sheet открывается на половину экрана, drag вверх раскрывает до full-viewport; drag вниз ниже `0.5` ведёт к закрытию. Контракт массива (движок не сортирует и не дедуплицирует — порядок и различимость на стороне потребителя): - строго по возрастанию: индекс `0` — самая компактная позиция, последний — top / expanded; - значения должны резолвиться в различные высоты (`['50%', 0.5]` на типичном вьюпорте дадут одну высоту → дубль-индекс будет недостижим свайпом); - `'fit-content'` имеет смысл только как ЕДИНСТВЕННЫЙ snap (без `snapPoints`); внутри массива фиксированных позиций его «контентная» высота не определена. |
swipeEnabled | boolean | true | no | Включает swipe-down для закрытия / swipe-up для раскрытия на следующий snap-point. При `swipeEnabled=false` snap-point по-прежнему можно переключить через controlled `snapIndex` prop'ом. |
width | string | number | 's' | no | Ширина (только при position: "left" | "right") |