ButtonDropdown
Кнопка view='function' с AdaptiveDroplist: на desktop — Droplist из @cloud-ru/ds-list, на mobile — @cloud-ru/ds-modal со списком. Раскладка определяется контекстом AdaptiveProvider (см. @cloud-ru/ds-adaptive), а не пропом. Используется, в частности, в PriceSummary для выбора периода биллинга.
Когда использовать
- Нужен выбор одного значения из короткого списка (период, валюта, режим) без отдельного поля формы.
- На desktop достаточно выпадающего списка у триггера; на mobile — полноэкранный modal со списком.
Когда не нужен ButtonDropdown:
- Произвольный контент в overlay без списка —
Dropdown. - Одиночное действие без меню —
Buttonview='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
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
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
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
ButtonDropdownProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
appearance | "critical" | "neutral" | "primary" | neutral | no | Вариант оформления |
as | "button" | — | no | Элемент или компонент для рендера: 'button' | 'a' | ComponentType (например Link из react-router-dom) |
children | string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined | — | no | |
className | string | — | no | Дополнительный класс Класс триггерной кнопки. |
closeDroplistOnItemClick | boolean | false | no | Закрывать выпадающий список после клика на базовый айтем. Работает в режимах selection: 'none' | 'single' |
closeOnPopstate | boolean | — | no | Закрывать ли поповер при переходе по истории браузера |
counter | Omit<CounterProps, "size"> | — | no | Пропсы для counter. `appearance` можно задать явно (по умолчанию наследуется от appearance кнопки). |
data-test-id | string | — | no | |
disabled | boolean | — | no | Отключена |
fullWidth | boolean | — | no | На всю ширину |
innerRef | ((instance: HTMLButtonElement | null) => void) | RefObject<HTMLButtonElement> | null | — | no | Ref на реальный DOM-элемент/инстанс, который рендерится через `as`. Используем явный проп, чтобы не зависеть от `forwardRef` и не тащить type-assertions на экспорт. |
items | Item[] | — | yes | Основные элементы списка |
label | string | — | no | Текст кнопки |
loading | boolean | — | no | Состояние загрузки |
minWidth | boolean | — | no | Минимальная ширина контейнера (`min-width` из токена размера). По умолчанию `true`. `false` — кнопка сжимается по контенту вместо фиксированного минимума. |
onOpenChange | ((open: boolean) => void) | — | no | Колбэк изменения раскрытия. |
open | boolean | — | no | Контролируемое состояние раскрытия. |
placement | "bottom" | "bottom-end" | "bottom-start" | "left" | "left-end" | "left-start" | "right" | "right-end" | "right-start" | "top" | "top-end" | "top-start" | top | no | Положение поповера относительно своего триггера (children). |
size | "l" | "m" | "s" | "xs" | s | no | Размер триггера; для `xs` применяется кнопка `s`. |
triggerClassName | string | — | no | CSS-класс триггера |