ButtonDropdown

Кнопка view='function' с AdaptiveDroplist: на desktopDroplist из @cloud-ru/ds-list, на mobile@cloud-ru/ds-modal со списком. Раскладка определяется контекстом AdaptiveProvider (см. @cloud-ru/ds-adaptive), а не пропом. Используется, в частности, в PriceSummary для выбора периода биллинга.

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

  • Нужен выбор одного значения из короткого списка (период, валюта, режим) без отдельного поля формы.
  • На desktop достаточно выпадающего списка у триггера; на mobile — полноэкранный modal со списком.

Когда не нужен ButtonDropdown:

  • Произвольный контент в overlay без списка — Dropdown.
  • Одиночное действие без меню — Button view='function'.

Анатомия

Trigger

Button view='function' appearance='neutral' (как buttonFunctionNeutral в Figma) с label и chevron up/down. При open={true} на триггер вешается data-pressed — в макете это stateLayer/text/opacity (прозрачность label/icon через @cloud-ru/ds-materials).

Droplist

ButtonDropdown рендерит Droplist из @cloud-ru/ds-list — он сам адаптивен (раскладку берёт из AdaptiveProvider, отдельного пропа нет):

  • desktop (по умолчанию) — список у триггера через popover.
  • mobile — список уходит в bottom-sheet (адаптивность @cloud-ru/ds-list).

items

Массив пунктов Droplist (content.label, onClick, id). При closeDroplistOnItemClick список закрывается после выбора.

Size (default s)

  • xs — кнопка и droplist рендерятся в размере s (алиас).
  • s — компактный размер.
  • m — средний размер.
  • l — крупный размер.

Appearance (default neutral)

Тон триггерной кнопки — те же значения, что у Button view='function':

  • neutral — нейтральный, основной вариант.
  • primary — акцентный.
  • critical — критическое действие.

open / onOpenChange

Controlled API через useValueControl (как у @cloud-ru/ds-utils).

Установка

pnpm add @cloud-ru/ds-uikit-product-button-predefined
import { ButtonDropdown } from '@cloud-ru/ds-uikit-product-button-predefined'

Базовый пример

<ButtonDropdown
  label='Period'
  size='s'
  closeDroplistOnItemClick
  items={[
    { id: 'month', content: { label: 'Month' }, onClick: () => setPeriod('month') },
    { id: 'year', content: { label: 'Year' }, onClick: () => setPeriod('year') },
  ]}
/>

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

Desktop basic

Desktop basicБазовый dropdown для выбора одного значения.
tsx
import { AdaptiveProvider, LAYOUT_TYPE } from '@cloud-ru/ds-adaptive';
import { ButtonDropdown } from '@cloud-ru/ds-uikit-product-button-predefined';
import { useState } from 'react';

const periods = [
  { id: 'month', label: 'Month' },
  { id: 'year', label: 'Year' },
];

export function DesktopBasic() {
  const [period, setPeriod] = useState(periods[0]);

  const items = periods.map(option => ({
    id: option.id,
    content: { label: option.label },
    onClick: () => setPeriod(option),
  }));

  return (
    <AdaptiveProvider layoutType={LAYOUT_TYPE.Desktop}>
      <ButtonDropdown label={period.label} size='s' items={items} closeDroplistOnItemClick />
    </AdaptiveProvider>
  );
}

Desktop open state

Desktop open stateОткрытое состояние dropdown (portal).
tsx
import { AdaptiveProvider, LAYOUT_TYPE } from '@cloud-ru/ds-adaptive';
import { ButtonDropdown } from '@cloud-ru/ds-uikit-product-button-predefined';
import { useState } from 'react';

const periods = [
  { id: 'month', label: 'Month' },
  { id: 'year', label: 'Year' },
];

export function DesktopOpen() {
  const [period, setPeriod] = useState(periods[0]);

  const items = periods.map(option => ({
    id: option.id,
    content: { label: option.label },
    onClick: () => setPeriod(option),
  }));

  return (
    <AdaptiveProvider layoutType={LAYOUT_TYPE.Desktop}>
      <ButtonDropdown label={period.label} size='m' open items={items} />
    </AdaptiveProvider>
  );
}

Mobile layout

Mobile layoutAdaptiveProvider layoutType=mobile открывает modal со списком.
tsx
import { AdaptiveProvider, LAYOUT_TYPE } from '@cloud-ru/ds-adaptive';
import { ButtonDropdown } from '@cloud-ru/ds-uikit-product-button-predefined';
import { useState } from 'react';

const periods = [
  { id: 'month', label: 'Month' },
  { id: 'year', label: 'Year' },
];

export function MobileLayout() {
  const [period, setPeriod] = useState(periods[0]);

  const items = periods.map(option => ({
    id: option.id,
    content: { label: option.label },
    onClick: () => setPeriod(option),
  }));

  return (
    <AdaptiveProvider layoutType={LAYOUT_TYPE.Mobile}>
      <ButtonDropdown label={period.label} size='s' closeDroplistOnItemClick items={items} />
    </AdaptiveProvider>
  );
}

Props

Types

PropsButtonDropdownProps
PropTypeDefaultRequiredDescription
appearance"critical" | "neutral" | "primary"neutralnoВариант оформления
as"button"noЭлемент или компонент для рендера: 'button' | 'a' | ComponentType (например Link из react-router-dom)
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefinedno
classNamestringnoДополнительный класс Класс триггерной кнопки.
closeDroplistOnItemClickbooleanfalsenoЗакрывать выпадающий список после клика на базовый айтем. Работает в режимах selection: 'none' | 'single'
closeOnPopstatebooleannoЗакрывать ли поповер при переходе по истории браузера
counterOmit<CounterProps, "size">noПропсы для counter. `appearance` можно задать явно (по умолчанию наследуется от appearance кнопки).
data-test-idstringno
disabledbooleannoОтключена
fullWidthbooleannoНа всю ширину
innerRef((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | nullnoRef на реальный DOM-элемент/инстанс, который рендерится через `as`. Используем явный проп, чтобы не зависеть от `forwardRef` и не тащить type-assertions на экспорт.
itemsItem[]yesОсновные элементы списка
labelstringnoТекст кнопки
loadingbooleannoСостояние загрузки
minWidthbooleannoМинимальная ширина контейнера (`min-width` из токена размера). По умолчанию `true`. `false` — кнопка сжимается по контенту вместо фиксированного минимума.
onOpenChange((open: boolean) => void)noКолбэк изменения раскрытия.
openbooleannoКонтролируемое состояние раскрытия.
placement"bottom" | "bottom-end" | "bottom-start" | "left" | "left-end" | "left-start" | "right" | "right-end" | "right-start" | "top" | "top-end" | "top-start"topnoПоложение поповера относительно своего триггера (children).
size"l" | "m" | "s" | "xs"snoРазмер триггера; для `xs` применяется кнопка `s`.
triggerClassNamestringnoCSS-класс триггера

Unions

Types

ButtonDropdownProps

Unions

Storybook

Figma