npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cloud-ru/ds-button-combo

v1.0.29

Published

Кнопка с основным действием и выпадающим списком дополнительных действий.

Readme

Button Combo

@cloud-ru/ds-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 задаёт стартовое действие, выбор пункта меняет основную кнопку

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

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

Выбранное действие и раскрытие списка управляются извне через пары value/onChange и open/onOpenChange

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

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

ButtonComboProps

| Prop | Type | Default | Description | |------|------|---------|-------------| | appearance | "critical" | "neutral" | "primary" | primary | Цветовое назначение обеих кнопок | | className | string | — | CSS-класс корневого контейнера | | data-test-id | string | — | | | defaultLabel | string | `` | Начальный label основной кнопки, пока ничего не выбрано | | defaultValue | ItemId | — | Начальный выбранный элемент (uncontrolled) | | disabled | boolean | — | Отключена | | dropdownClassName | string | — | CSS-класс выпадающего списка | | dropdownTriggerClassName | string | — | CSS-класс кнопки-триггера выпадающего списка | | fullWidth | boolean | — | Растянуть на всю ширину родителя | | innerRef | Ref<HTMLDivElement> | — | Ref на корневой DOM-элемент. Используем явный проп, чтобы не зависеть от forwardRef и не тащить type-assertions на экспорт. | | items | Item | — | Основные элементы списка (действия) | | loading | boolean | — | Состояние загрузки | | onChange | ((value: ItemId) => void) | — | Controlled: обработчик смены выбранного элемента | | onOpenChange | ((open: boolean) => void) | — | Обработчик изменения видимости выпадающего списка | | open | boolean | — | Управляет видимостью выпадающего списка | | optionClassName | string | — | CSS-класс основной (option) кнопки | | size | "l" | "m" | "s" | m | Размер | | tabIndex | number | — | HTML-атрибут tab-index корневого контейнера | | value | ItemId | — | Controlled: выбранный элемент | | view | "elevated" | "filled" | "function" | "outline" | "simple" | "tonal" | filled | Вариант оформления обеих кнопок |

Related types

Item

| Prop | Type | Default | Description | |------|------|---------|-------------| | afterContent | string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined | — | Слот после основного контента | | beforeContent | string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined | — | Слот до основного контента | | checked | boolean \| undefined | — | Управляемое состояние выбранности айтема | | className | string \| undefined | — | CSS-класс | | content | ItemContent | — | Основной контент айтема | | data-test-id | string \| undefined | — | | | disabled | boolean \| undefined | — | Флаг неактивности элемента | | hidden | boolean \| undefined | — | Скрыть элемент из списка (не рендерится и исключается из навигации) | | id | ItemId | — | Уникальный идентификатор | | inactive | boolean \| undefined | — | Флаг отображения отключения реакции на любое css состояние (hover/focus и тд) Также элемент исключается из навигации с клавиатуры и не может быть выбран (selection) | | itemRef | RefObject<HTMLElement> \| undefined | — | Ссылка на DOM-элемент айтема | | itemWrapRender | ((item: ReactNode) => ReactNode) \| undefined | — | Рендер-обёртка вокруг айтема (например, для проксирования в Tooltip/Link) | | label | string \| undefined | — | Название действия: текст пункта списка и label основной кнопки при выборе | | onClick | (e: MouseEvent<HTMLElement>) => void | — | Колбек обработки клика | | showSwitchIcon | boolean \| undefined | — | Флаг отображения иконки у чекбоксов | | switch | boolean \| undefined | — | Флаг отображения состояния выбранного элемента через switch |

Смотри также