@cloud-ru/ds-drawer

中文文档
2.3.0发布于 2026/11/11Apache-2.0TypeScriptESM + CJS近 30 天下载 6737

Пакет выезжающих панелей — компоненты Drawer и DrawerCustom с едиными токенами позиции и ширины.

原始使用文档
文档目录12 节
  1. Установка
  2. Смотри также
  3. Drawer
  4. Когда использовать
  5. Примеры использования
  6. Props
  7. Адаптивность
  8. DrawerCustom
  9. Когда использовать
  10. Анатомия
  11. Примеры использования
  12. Props

Drawer

@cloud-ru/ds-drawer — Пакет выезжающих панелей — компоненты Drawer и DrawerCustom с едиными токенами позиции и ширины.

Пакет @cloud-ru/ds-drawer предоставляет два компонента для боковых/нижних панелей: готовую сборку Drawer с пресетной шапкой, телом и футером и низкоуровневый DrawerCustom для ручной композиции.

  • Drawer — готовая панель с шапкой, прокручиваемым телом и опциональным футером. Покрывает 90% сценариев.
  • DrawerCustom — низкоуровневая версия без предопределённой структуры: собирайте из DrawerCustom.Header, .Body, .Footer или собственной разметки.

Установка

pnpm add @cloud-ru/ds-drawer
import { Drawer, DrawerCustom, POSITION, WIDTH } from '@cloud-ru/ds-drawer'

Смотри также

  • Modal — центрированное модальное окно для блокирующих подтверждений.
  • Popover — всплывающий слой рядом с триггером.

Drawer

Панель, выезжающая со стороны экрана — пресетная шапка, прокручиваемое тело и опциональный футер.

Панель, выезжающая со стороны экрана — для дополнительного контекста, фильтров, форм и пошаговых сценариев. Drawer собирает шапку (медиа, заголовок, подзаголовок, back-button, слот после заголовка), прокручиваемое тело и опциональный футер. Для ручной композиции используйте DrawerCustom.

Когда использовать

  • Форма, фильтры или детали, которые не помещаются в основной поток и требуют отдельного контекста.
  • Пошаговый сценарий с кнопкой «назад» в шапке — возвращение между экранами без потери контекста.
  • Нижний лист (bottom sheet) для компактных действий и подтверждений на мобильных устройствах.
  • Стек из двух-трёх связанных панелей через nestedDrawer.

Когда не нужен: критическое подтверждение, блокирующее остальной интерфейс (берите Modal), всплывающий поповер рядом с элементом (Popover), тост-уведомление (не блокирует UI).

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

Базовое использование

Контролируемое open/onClose, footer из ButtonGroup.

import { Button, ButtonGroup } from '@cloud-ru/ds-button';
import { Drawer } from '@cloud-ru/ds-drawer';
import { useState } from 'react';

export function Basic() {
  const [open, setOpen] = useState(false);
  const close = () => setOpen(false);

  return (
    <>
      <Button label='Открыть' appearance='primary' view='filled' onClick={() => setOpen(true)} />
      <Drawer
        open={open}
        position='right'
        onClose={close}
        title='Заголовок'
        subtitle='Короткое пояснение сценария'
        content='Основной контент тела. Сюда помещается форма, предупреждение или подробный текст.'
        footer={
          <ButtonGroup
            primaryAction={{ label: 'Продолжить', view: 'filled', onClick: close }}
            secondaryAction={{ label: 'Отмена', appearance: 'neutral', view: 'outline', onClick: close }}
          />
        }
      />
    </>
  );
}

Критическое действие

Critical primary, neutral outline secondary.

import { Button, ButtonGroup } from '@cloud-ru/ds-button';
import { Drawer } from '@cloud-ru/ds-drawer';
import { useState } from 'react';

export function WithFooter() {
  const [open, setOpen] = useState(false);
  const close = () => setOpen(false);

  return (
    <>
      <Button label='Удалить…' appearance='critical' view='outline' onClick={() => setOpen(true)} />
      <Drawer
        open={open}
        position='right'
        onClose={close}
        title='Удалить запись'
        subtitle='Действие необратимо.'
        content='После подтверждения запись и все её ссылки исчезнут из списка.'
        footer={
          <ButtonGroup
            primaryAction={{
              label: 'Удалить',
              appearance: 'critical',
              view: 'filled',
              onClick: close,
            }}
            secondaryAction={{
              label: 'Отмена',
              appearance: 'neutral',
              view: 'outline',
              onClick: close,
            }}
          />
        }
      />
    </>
  );
}

С медиа-слотом

media рендерится над шапкой на всю ширину панели.

import { Button } from '@cloud-ru/ds-button';
import { Drawer } from '@cloud-ru/ds-drawer';
import { useState } from 'react';

export function WithMedia() {
  const [open, setOpen] = useState(false);
  const close = () => setOpen(false);

  return (
    <>
      <Button label='Открыть онбординг' appearance='primary' view='filled' onClick={() => setOpen(true)} />
      <Drawer
        open={open}
        position='right'
        width='m'
        onClose={close}
        media={
          <div
            style={{
              height: 200,
              background: 'linear-gradient(135deg, rgb(102 126 234), rgb(118 75 162))',
            }}
          />
        }
        title='Добро пожаловать'
        subtitle='Кратко о том, что изменилось в этой версии.'
        content='Список ключевых улучшений и ссылки на подробности могут размещаться в теле.'
      />
    </>
  );
}

Bottom sheet

position="bottom" + heightAuto — высота по контенту.

import { Button } from '@cloud-ru/ds-button';
import { Drawer } from '@cloud-ru/ds-drawer';
import { useState } from 'react';

export function HeightAuto() {
  const [open, setOpen] = useState(false);
  const close = () => setOpen(false);

  return (
    <>
      <Button label='Открыть bottom sheet' appearance='primary' view='filled' onClick={() => setOpen(true)} />
      <Drawer
        open={open}
        position='bottom'
        heightAuto
        onClose={close}
        title='Bottom sheet'
        subtitle='Высота рассчитывается по контенту'
        content='Подходит для компактных подтверждений на мобильных устройствах.'
      />
    </>
  );
}

Вложенный Drawer

Родитель сдвигается влево при открытии дочернего.

import { Button } from '@cloud-ru/ds-button';
import { Drawer } from '@cloud-ru/ds-drawer';
import { useState } from 'react';

export function NestedDrawer() {
  const [outerOpen, setOuterOpen] = useState(false);
  const [innerOpen, setInnerOpen] = useState(false);

  const closeAll = () => {
    setInnerOpen(false);
    setOuterOpen(false);
  };

  return (
    <>
      <Button label='Открыть родительский' appearance='primary' view='filled' onClick={() => setOuterOpen(true)} />
      <Drawer
        open={outerOpen}
        position='right'
        width='m'
        onClose={closeAll}
        title='Родительский Drawer'
        subtitle='При открытии вложенного — родитель сдвигается влево.'
        content={
          <Button label='Открыть вложенный' appearance='primary' view='outline' onClick={() => setInnerOpen(true)} />
        }
        nestedDrawer={
          <Drawer
            open={innerOpen}
            position='right'
            width='s'
            onClose={() => setInnerOpen(false)}
            title='Вложенный Drawer'
            subtitle='Кнопка «назад» возвращает к родителю'
            onBackButtonClick={() => setInnerOpen(false)}
            content='Вложенный Drawer рендерится через проп nestedDrawer родителя.'
          />
        }
      />
    </>
  );
}

Props

DrawerProps

PropTypeDefaultDescription
additionalButtonBottomSheetActionButton—Дополнительная (третья) кнопка — пропсы Button (дефолт view='simple', appearance='neutral').
approveButtonBottomSheetActionButton—Основная кнопка действия — пропсы Button (дефолт view='filled', appearance='primary').
cancelButtonBottomSheetActionButton—Кнопка отмены — объект пропсов Button (по умолчанию view='outline', appearance='neutral').
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—
classNamestring—CSS-класс для элемента с контентом
CSS-класс
closeOnPopstateboolean—Закрывать дровер при перемещении по истории браузера
containerstring | HTMLElement—Контейнер в котором будет рендерится Drawer. По-умолчанию - body
contentReactNode—Содержимое body (альтернатива children).
contentRefRef<HTMLElement>—Ссылка на скроллируемый контейнер контента (например, для дозагрузки по скроллу).
data-test-idstring—
disableMotionsbooleanfalseОтключить анимации
footer(ReactElement<any, string | JSXElementConstructor<any>> & (string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<...> | ReactPortal | null))—Футер
Произвольный футер. Приоритетнее approveButton / cancelButton / additionalButton.
footerActionsOrientation"horizontal" | "vertical"'horizontal'Ориентация кнопок футера. Применяется только при двух кнопках; игнорируется при заданном footer.
heightAutobooleanfalseВысота панели по контенту (только при position: "top" | "bottom").
mediaReactNode—Медиа-контент
nestedDrawerDrawerProps—Вложенный Drawer
onBackButtonClick(() => void)—Callback клика на back-кнопку (слева в шапке).
Наличие callback'а авто-рендерит Button view='function' icon={<ArrowLeftSVG />}.
onClose() => void—Колбэк закрытия
onSnapIndexChange((snapIndex: number) => void)—Callback изменения активного snap'а (пересечение swipe-границы или click по UI).
Не вызывается при программной смене controlled snapIndex.
openboolean—Управление состоянием показан/не показан.
position"bottom" | "left" | "right" | "top"—Расположение
rootClassNamestring—CSS-класс для корневого элемента
safeAreaBottombooleantrueНижний safe-area отступ под iOS home-indicator и Android nav-bar. Размер — токен
safeArea.bottom.minHeight, на устройстве с большим env(safe-area-inset-bottom) — значение inset.
safeAreaTopbooleantrueВерхний safe-area отступ под iOS notch / status-bar. Размер — токен safeArea.top.minHeight,
на устройстве с большим env(safe-area-inset-top) — значение inset. Применяется, только когда
sheet раскрыт на полный вьюпорт: у неполного sheet'а верх и так не заходит под notch.
showBlackoutbooleantrueОтображение темной подложки
showButtonClosedbooleantrueОтображение кнопки закрытия в шапке дровера
slotAfterTitleReactNode—Slot справа от title (например, QuestionTooltip из @cloud-ru/ds-tooltip).
slotSecondTitleReactNode—Slot под подзаголовком — типично SearchBar, SegmentControl или Filter.
Рендерится на обеих поверхностях: secondWrapper в мастере bottomSheet,
subHeadlineWrapper в мастере window (modal / drawer).
snapIndexnumber—Controlled-индекс активного snap'а. Если задан, sheet всегда находится на этом snap'е;
swipe-up/down вызывают onSnapIndexChange, но не меняют позицию сами — consumer должен
передать новое значение.
snapPointsSnapPoint—Массив фиксированных позиций 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); внутри массива
фиксированных позиций его «контентная» высота не определена.
subtitleReactNode—Текстовая строка-подзаголовок под title.
swipeEnabledbooleantrueВключает swipe-down для закрытия / swipe-up для раскрытия на следующий snap-point.
При swipeEnabled=false snap-point по-прежнему можно переключить через controlled snapIndex prop'ом.
titleReactNode—Заголовок. Типографика зависит от поверхности: title-l на sheet, headline-s на window (modal/drawer).
truncate{ title?: number; subtitle?: number; } | undefined—Усечение строковых title/subtitle через TruncateString (число строк). Применяется только
когда задано — по умолчанию текст не усекается. Актуально для window-поверхности (modal/drawer),
где длинный заголовок иначе переносится на несколько строк.
widthWidth's'Ширина (только при position: "left" | "right")
withDividersbooleanfalseТолько mobile: разделители между шапкой/контентом/футером sheet'а.

DrawerProps

PropTypeDefaultDescription
additionalButtonBottomSheetActionButton—Дополнительная (третья) кнопка — пропсы Button (дефолт view='simple', appearance='neutral').
approveButtonBottomSheetActionButton—Основная кнопка действия — пропсы Button (дефолт view='filled', appearance='primary').
cancelButtonBottomSheetActionButton—Кнопка отмены — объект пропсов Button (по умолчанию view='outline', appearance='neutral').
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—
classNamestring | undefined—CSS-класс для элемента с контентом
CSS-класс
closeOnPopstateboolean | undefined—Закрывать дровер при перемещении по истории браузера
containerstring | HTMLElement | undefined—Контейнер в котором будет рендерится Drawer. По-умолчанию - body
contentstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Содержимое body (альтернатива children).
contentRefRef<HTMLElement> | undefined—Ссылка на скроллируемый контейнер контента (например, для дозагрузки по скроллу).
data-test-idstring | undefined—
disableMotionsboolean | undefined—Отключить анимации
footer(ReactElement<any, string | JSXElementConstructor<any>> & (string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null)) | undefined—Футер
Произвольный футер. Приоритетнее approveButton / cancelButton / additionalButton.
footerActionsOrientation"horizontal" | "vertical"—Ориентация кнопок футера. Применяется только при двух кнопках; игнорируется при заданном footer.
heightAutoboolean | undefined—Высота панели по контенту (только при position: "top" | "bottom").
mediastring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Медиа-контент
nestedDrawerDrawerProps—Вложенный Drawer
onBackButtonClick(() => void) | undefined—Callback клика на back-кнопку (слева в шапке).
Наличие callback'а авто-рендерит Button view='function' icon={<ArrowLeftSVG />}.
onClose() => void—Колбэк закрытия
onSnapIndexChange((snapIndex: number) => void) | undefined—Callback изменения активного snap'а (пересечение swipe-границы или click по UI).
Не вызывается при программной смене controlled snapIndex.
openboolean—Управление состоянием показан/не показан.
position"bottom" | "left" | "right" | "top"—Расположение
rootClassNamestring | undefined—CSS-класс для корневого элемента
safeAreaBottomboolean | undefined—Нижний safe-area отступ под iOS home-indicator и Android nav-bar. Размер — токен
safeArea.bottom.minHeight, на устройстве с большим env(safe-area-inset-bottom) — значение inset.
safeAreaTopboolean | undefined—Верхний safe-area отступ под iOS notch / status-bar. Размер — токен safeArea.top.minHeight,
на устройстве с большим env(safe-area-inset-top) — значение inset. Применяется, только когда
sheet раскрыт на полный вьюпорт: у неполного sheet'а верх и так не заходит под notch.
showBlackoutboolean | undefined—Отображение темной подложки
showButtonClosedboolean | undefined—Отображение кнопки закрытия в шапке дровера
slotAfterTitlestring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Slot справа от title (например, QuestionTooltip из @cloud-ru/ds-tooltip).
slotSecondTitlestring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Slot под подзаголовком — типично SearchBar, SegmentControl или Filter.
Рендерится на обеих поверхностях: secondWrapper в мастере bottomSheet,
subHeadlineWrapper в мастере window (modal / drawer).
snapIndexnumber | undefined—Controlled-индекс активного snap'а. Если задан, sheet всегда находится на этом snap'е;
swipe-up/down вызывают onSnapIndexChange, но не меняют позицию сами — consumer должен
передать новое значение.
snapPointsSnapPoint—Массив фиксированных позиций 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); внутри массива
фиксированных позиций его «контентная» высота не определена.
subtitlestring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Текстовая строка-подзаголовок под title.
swipeEnabledboolean | undefined—Включает swipe-down для закрытия / swipe-up для раскрытия на следующий snap-point.
При swipeEnabled=false snap-point по-прежнему можно переключить через controlled snapIndex prop'ом.
titlestring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Заголовок. Типографика зависит от поверхности: title-l на sheet, headline-s на window (modal/drawer).
truncate{ title?: number; subtitle?: number; } | undefined—Усечение строковых title/subtitle через TruncateString (число строк). Применяется только
когда задано — по умолчанию текст не усекается. Актуально для window-поверхности (modal/drawer),
где длинный заголовок иначе переносится на несколько строк.
widthWidth—Ширина (только при position: "left" | "right")
withDividersboolean | undefined—Только mobile: разделители между шапкой/контентом/футером sheet'а.
  • Position = "bottom" | "left" | "right" | "top"

  • Width = "l" | "m" | "s"

Адаптивность

Drawer — адаптивный компонент с переключением поверхности (surface-swap). Раскладку он берёт из AdaptiveProvider (контекст @cloud-ru/ds-adaptive); публичный API единый для обеих платформ:

  • desktop (по умолчанию) — панель, выезжающая со стороны экрана (left/right/top/bottom).
  • mobile — контент рендерится в BottomSheet из @cloud-ru/ds-bottom-sheet (панель снизу со свайпом для закрытия).

Верстайте под desktop и поставьте один <AdaptiveProvider> в корне приложения — mobile-поверхность включается автоматически (desktop-first). Пропа layoutType у компонента нет: источник раскладки — только контекст.

Как форсировать платформу

Форс — только контекстом, не пропом:

  • Поддерево — вложенный провайдер:
    import { AdaptiveProvider } from '@cloud-ru/ds-adaptive'
    
    <AdaptiveProvider layoutType='mobile'>
      <Drawer open={open} onClose={close} content={…} />
    </AdaptiveProvider>
    
  • Отдельный компонент — withLayoutType (module-scope, сахар над провайдером):
    import { withLayoutType } from '@cloud-ru/ds-adaptive'
    import { Drawer } from '@cloud-ru/ds-drawer'
    
    const MobileDrawer = withLayoutType(Drawer, 'mobile')
    

Платформенные пропы

Часть пропов управляет геометрией desktop-панели и на mobile молча игнорируется (у BottomSheet своя поверхность снизу). Таблица синхронизирована с type-level JSDoc у DrawerProps.

Пропыdesktopmobile
position, width, heightAuto, nestedDrawerиспользуетсяигнорируется
title, subtitle, slotAfterTitle, onBackButtonClick, media, content, footerиспользуетсяиспользуется
open, onClose, showBlackout, container, closeOnPopstate, className, rootClassNameиспользуетсяиспользуется

Подробнее о модели адаптивности — Adaptive.

DrawerCustom

Низкоуровневая сборка Drawer — полный контроль над разметкой через Header / Body / Footer субкомпоненты.

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 для точного контроля.

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

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

Header + Body + Footer собираются вручную.

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

DrawerCustomProps

PropTypeDefaultDescription
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—
classNamestring—CSS-класс для элемента с контентом
closeOnPopstateboolean—Закрывать дровер при перемещении по истории браузера
containerstring | HTMLElement—Контейнер в котором будет рендерится Drawer. По-умолчанию - body
data-test-idstring—
disableMotionsbooleanfalseОтключить анимации
footerReactElement<any, string | JSXElementConstructor<any>>—Футер
heightAutobooleanfalseВысота панели по контенту (только при position: "top" | "bottom").
nestedDrawerDrawerCustomProps—Вложенный Drawer
onClose() => void—Колбэк закрытия
openboolean—Управление состоянием показан/не показан.
position"bottom" | "left" | "right" | "top"—Расположение
pushboolean | PushConfig—Смещение при открытии "вложенного" компонента
resizableResizableMax's'Ширина (только при position: "left" | "right")
rootClassNamestring—CSS-класс для корневого элемента
safeAreaBottombooleantrueНижний safe-area отступ под iOS home-indicator и Android nav-bar. Размер — токен
safeArea.bottom.minHeight, на устройстве с большим env(safe-area-inset-bottom) — значение inset.
safeAreaTopbooleantrueВерхний safe-area отступ под iOS notch / status-bar. Размер — токен safeArea.top.minHeight,
на устройстве с большим env(safe-area-inset-top) — значение inset. Применяется, только когда
sheet раскрыт на полный вьюпорт: у неполного sheet'а верх и так не заходит под notch.
showBlackoutbooleantrueОтображение темной подложки
showButtonClosedbooleantrueОтображение кнопки закрытия в шапке дровера
snapPointsSnapPoint—Массив фиксированных позиций 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); внутри массива
фиксированных позиций его «контентная» высота не определена.
swipeEnabledbooleantrueВключает swipe-down для закрытия / swipe-up для раскрытия на следующий snap-point.
При swipeEnabled=false snap-point по-прежнему можно переключить через controlled snapIndex prop'ом.
widthWidth's'Ширина (только при position: "left" | "right")

DrawerCustomProps

PropTypeDefaultDescription
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—
classNamestring | undefined—CSS-класс для элемента с контентом
closeOnPopstateboolean | undefined—Закрывать дровер при перемещении по истории браузера
containerstring | HTMLElement | undefined—Контейнер в котором будет рендерится Drawer. По-умолчанию - body
data-test-idstring | undefined—
disableMotionsboolean | undefined—Отключить анимации
footerReactElement<any, string | JSXElementConstructor<any>> | undefined—Футер
heightAutoboolean | undefined—Высота панели по контенту (только при position: "top" | "bottom").
nestedDrawerDrawerCustomProps—Вложенный Drawer
onClose() => void—Колбэк закрытия
openboolean—Управление состоянием показан/не показан.
position"bottom" | "left" | "right" | "top"—Расположение
pushboolean | PushConfig | undefined—Смещение при открытии "вложенного" компонента
resizableResizableMax—Ширина (только при position: "left" | "right")
rootClassNamestring | undefined—CSS-класс для корневого элемента
safeAreaBottomboolean | undefined—Нижний safe-area отступ под iOS home-indicator и Android nav-bar. Размер — токен
safeArea.bottom.minHeight, на устройстве с большим env(safe-area-inset-bottom) — значение inset.
safeAreaTopboolean | undefined—Верхний safe-area отступ под iOS notch / status-bar. Размер — токен safeArea.top.minHeight,
на устройстве с большим env(safe-area-inset-top) — значение inset. Применяется, только когда
sheet раскрыт на полный вьюпорт: у неполного sheet'а верх и так не заходит под notch.
showBlackoutboolean | undefined—Отображение темной подложки
showButtonClosedboolean | undefined—Отображение кнопки закрытия в шапке дровера
snapPointsSnapPoint—Массив фиксированных позиций 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); внутри массива
фиксированных позиций его «контентная» высота не определена.
swipeEnabledboolean | undefined—Включает swipe-down для закрытия / swipe-up для раскрытия на следующий snap-point.
При swipeEnabled=false snap-point по-прежнему можно переключить через controlled snapIndex prop'ом.
widthWidth—Ширина (только при position: "left" | "right")
  • Position = "bottom" | "left" | "right" | "top"

  • ResizableMax = number | "full"

  • Width = "l" | "m" | "s"

版本与发布记录

@cloud-ru/ds-drawer 共发布 140 个版本,首次发布于 2026/08/18;过去 12 个月发布了 140 个版本。

版本发布日期距今
2.3.0latest2026/11/11今天
2.2.2-preview-e8fe6b56.0preview2026/11/11今天
2.2.2-preview-c0e895ed.02026/11/11今天
2.2.2-preview-90b10112.02026/11/11今天
2.2.2-preview-fd7dd437.02026/11/101 天前
2.2.2-preview-9a75a2a9.02026/11/101 天前

关于 @cloud-ru/ds-drawer 的常见问题

@cloud-ru/ds-drawer 是什么?

Пакет выезжающих панелей — компоненты Drawer и DrawerCustom с едиными токенами позиции и ширины.

如何安装 @cloud-ru/ds-drawer?

在项目目录运行 npm install @cloud-ru/ds-drawer,或使用 pnpm add / yarn add / bun add。

@cloud-ru/ds-drawer 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cloud-ru/ds-drawer 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cloud-ru/ds-drawer 可以商用吗?

@cloud-ru/ds-drawer 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@cloud-ru/ds-drawer 的下载量有多少?

根据 npm 官方下载统计,@cloud-ru/ds-drawer 在 2026/10/10 – 2026/11/08 这 30 天内被下载 6,737 次,日均约 225 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cloud-ru/ds-drawer 还在维护吗?

最新版本 2.3.0 发布于 2026/11/11(今天);至今共发布 140 个版本,其中过去 12 个月发布了 140 个;首次发布于 2026/08/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。