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'

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

Основной сценарий

Основной сценарийUncontrolled: defaultValue задаёт стартовое действие, выбор пункта меняет основную кнопку
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' />;
}

Варианты оформления

Варианты оформленияОсь view: filled, tonal, outline, function
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

ControlledВыбранное действие и раскрытие списка управляются извне через пары value/onChange и open/onOpenChange
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} />;
}

На всю ширину

На всю ширинуfullWidth растягивает основную кнопку по контейнеру, триггер сохраняет свою ширину
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

PropsButtonComboProps
PropTypeDefaultRequiredDescription
appearance"critical" | "neutral" | "primary"primarynoЦветовое назначение обеих кнопок
classNamestringnoCSS-класс корневого контейнера
data-test-idstringno
defaultLabelstringnoНачальный label основной кнопки, пока ничего не выбрано
defaultValueItemIdnoНачальный выбранный элемент (uncontrolled)
disabledbooleannoОтключена
dropdownClassNamestringnoCSS-класс выпадающего списка
dropdownTriggerClassNamestringnoCSS-класс кнопки-триггера выпадающего списка
fullWidthbooleannoРастянуть на всю ширину родителя
innerRefRef<HTMLDivElement>noRef на корневой DOM-элемент. Используем явный проп, чтобы не зависеть от `forwardRef` и не тащить type-assertions на экспорт.
itemsItem[]yesОсновные элементы списка (действия)
loadingbooleannoСостояние загрузки
onChange((value: ItemId) => void)noControlled: обработчик смены выбранного элемента
onOpenChange((open: boolean) => void)noОбработчик изменения видимости выпадающего списка
openbooleannoУправляет видимостью выпадающего списка
optionClassNamestringnoCSS-класс основной (option) кнопки
size"l" | "m" | "s"mnoРазмер
tabIndexnumbernoHTML-атрибут tab-index корневого контейнера
valueItemIdnoControlled: выбранный элемент
view"elevated" | "filled" | "function" | "outline" | "simple" | "tonal"fillednoВариант оформления обеих кнопок

Types

ButtonComboProps

Storybook

Figma

Смотри также