Button Combo
Комбинированная кнопка: основное действие слева и триггер выпадающего списка с остальными действиями справа. Обе части — Button из @cloud-ru/ds-button с общими view / appearance / size; список — адаптивный Droplist из @cloud-ru/ds-list (на мобильной раскладке раскрывается в BottomSheet). Выбор пункта делает его основным действием кнопки.
Когда использовать
- Когда у действия есть основной сценарий и несколько родственных вариантов: Сохранить / Сохранить копию / Сохранить как шаблон.
- Когда нужно сэкономить место: вместо ряда кнопок — одна с выпадающим списком.
Когда не нужен:
- Для единственного действия — обычный
Buttonиз@cloud-ru/ds-button. - Для выбора значения (а не запуска действия) —
@cloud-ru/ds-segment-controlили select. - Для равнозначных несвязанных действий —
ButtonGroupиз@cloud-ru/ds-button.
Анатомия
View (default filled)
Оформление обеих частей комбо. Совпадает с осью view у @cloud-ru/ds-button:
filled— заполненная акцентом.tonal— мягкая тонированная подложка.outline— обводка без заливки.simple— только текст, без фона и рамки.elevated— светлая карточка с тенью.function— компактная текстовая, минимум отступов.
Appearance (default primary)
Цветовое назначение обеих частей:
primary— брендовый акцент, основное действие экрана.neutral— нейтральное, второстепенное действие.critical— деструктивное действие.
Size (default m)
Размер комбо: s, m, l. Прокидывается и в кнопки, и в выпадающий список.
Установка
pnpm add @cloud-ru/ds-button-combo
import { ButtonCombo } from '@cloud-ru/ds-button-combo'
Примеры использования
Основной сценарий
tsx
import { ButtonCombo, Item } from '@cloud-ru/ds-button-combo';
const items: Item[] = [
{ id: 'save', label: 'Сохранить', onClick: () => console.info('Сохранить') },
{ id: 'save-copy', label: 'Сохранить копию', onClick: () => console.info('Сохранить копию') },
{ id: 'save-template', label: 'Сохранить как шаблон', onClick: () => console.info('Сохранить как шаблон') },
];
export function Basic() {
return <ButtonCombo items={items} defaultValue='save' />;
}Варианты оформления
tsx
import { ButtonCombo, Item, VIEW } from '@cloud-ru/ds-button-combo';
const items: Item[] = [
{ id: 'run', label: 'Запустить', onClick: () => console.info('Запустить') },
{ id: 'run-debug', label: 'Запустить с отладкой', onClick: () => console.info('Запустить с отладкой') },
];
export function Views() {
return (
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
<ButtonCombo view={VIEW.Filled} items={items} defaultValue='run' />
<ButtonCombo view={VIEW.Tonal} items={items} defaultValue='run' />
<ButtonCombo view={VIEW.Outline} items={items} defaultValue='run' />
<ButtonCombo view={VIEW.Function} items={items} defaultValue='run' />
</div>
);
}Controlled
tsx
import { ButtonCombo, Item } from '@cloud-ru/ds-button-combo';
import { ItemId } from '@cloud-ru/ds-list';
import { useState } from 'react';
const items: Item[] = [
{ id: 'publish', label: 'Опубликовать', onClick: () => console.info('Опубликовать') },
{ id: 'draft', label: 'Сохранить черновик', onClick: () => console.info('Сохранить черновик') },
{ id: 'schedule', label: 'Запланировать', onClick: () => console.info('Запланировать') },
];
export function Controlled() {
const [value, setValue] = useState<ItemId>('publish');
const [open, setOpen] = useState(false);
return <ButtonCombo items={items} value={value} onChange={setValue} open={open} onOpenChange={setOpen} />;
}На всю ширину
tsx
import { ButtonCombo, Item } from '@cloud-ru/ds-button-combo';
import styles from './styles.module.scss';
const items: Item[] = [
{ id: 'confirm', label: 'Подтвердить', onClick: () => console.info('Подтвердить') },
{ id: 'confirm-notify', label: 'Подтвердить и уведомить', onClick: () => console.info('Подтвердить и уведомить') },
];
export function FullWidth() {
return (
<div className={styles.narrow}>
<ButtonCombo fullWidth items={items} defaultValue='confirm' />
</div>
);
}Props
Types
Props
ButtonComboProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
appearance | "critical" | "neutral" | "primary" | primary | no | Цветовое назначение обеих кнопок |
className | string | — | no | CSS-класс корневого контейнера |
data-test-id | string | — | no | |
defaultLabel | string | | no | Начальный label основной кнопки, пока ничего не выбрано |
defaultValue | ItemId | — | no | Начальный выбранный элемент (uncontrolled) |
disabled | boolean | — | no | Отключена |
dropdownClassName | string | — | no | CSS-класс выпадающего списка |
dropdownTriggerClassName | string | — | no | CSS-класс кнопки-триггера выпадающего списка |
fullWidth | boolean | — | no | Растянуть на всю ширину родителя |
innerRef | Ref<HTMLDivElement> | — | no | Ref на корневой DOM-элемент. Используем явный проп, чтобы не зависеть от `forwardRef` и не тащить type-assertions на экспорт. |
items | Item[] | — | yes | Основные элементы списка (действия) |
loading | boolean | — | no | Состояние загрузки |
onChange | ((value: ItemId) => void) | — | no | Controlled: обработчик смены выбранного элемента |
onOpenChange | ((open: boolean) => void) | — | no | Обработчик изменения видимости выпадающего списка |
open | boolean | — | no | Управляет видимостью выпадающего списка |
optionClassName | string | — | no | CSS-класс основной (option) кнопки |
size | "l" | "m" | "s" | m | no | Размер |
tabIndex | number | — | no | HTML-атрибут tab-index корневого контейнера |
value | ItemId | — | no | Controlled: выбранный элемент |
view | "elevated" | "filled" | "function" | "outline" | "simple" | "tonal" | filled | no | Вариант оформления обеих кнопок |