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'

Примеры использования

Ручная композиция

Ручная композицияHeader + Body + Footer собираются вручную.
tsx
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

PropsDrawerCustomProps
PropTypeDefaultRequiredDescription
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefinedno
classNamestringnoCSS-класс для элемента с контентом
closeOnPopstatebooleannoЗакрывать дровер при перемещении по истории браузера
containerstring | HTMLElementnoКонтейнер в котором будет рендерится Drawer. По-умолчанию - body
data-test-idstringno
disableMotionsbooleanfalsenoОтключить анимации
footerReactElement<any, string | JSXElementConstructor<any>>noФутер
heightAutobooleanfalsenoВысота панели по контенту (только при `position: "top" | "bottom"`).
nestedDrawerReactElement<Omit<DrawerCustomProps, "resizable">, string | JSXElementConstructor<any>>noВложенный Drawer
onClose() => voidyesКолбэк закрытия
openbooleanyesУправление состоянием показан/не показан.
position"bottom" | "left" | "right" | "top"yesРасположение
pushboolean | PushConfignoСмещение при открытии "вложенного" компонента
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")
rootClassNamestringnoCSS-класс для корневого элемента
safeAreabooleantruenoРезервировать ли место под iOS notch / home-indicator и Android nav-bar. Реализовано паддингом на `.content` через `env(safe-area-inset-*)`: на устройстве без выреза/индикатора (и на desktop) inset = 0, поэтому никакого «лишнего» отступа не появляется; на notched-устройстве — ровно нужный. Верхний отступ добавляется только когда sheet раскрыт на полный вьюпорт (его верх под notch).
showBlackoutbooleantruenoОтображение темной подложки
showButtonClosedbooleantruenoОтображение кнопки закрытия в шапке дровера
snapPointsSnapPoint[]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`); внутри массива фиксированных позиций его «контентная» высота не определена.
swipeEnabledbooleantruenoВключает swipe-down для закрытия / swipe-up для раскрытия на следующий snap-point. При `swipeEnabled=false` snap-point по-прежнему можно переключить через controlled `snapIndex` prop'ом.
widthstring | number's'noШирина (только при position: "left" | "right")

Unions

Types

DrawerCustomProps

Unions

Storybook

Figma