@cloud-ru/ds-uikit-product-widget

中文文档
2.1.0发布于 2026/11/11Apache-2.0TypeScriptESM + CJS近 30 天下载 6068

Карточка продуктового виджета с кликабельным заголовком, SegmentControl, действиями и состояниями loading/error.

原始使用文档
文档目录18 节
  1. Когда использовать
  2. Анатомия
  3. State (default default )
  4. Wide (default false )
  5. Action variant
  6. Slots
  7. Установка
  8. Примеры использования
  9. Контент и SegmentControl
  10. Wide desktop и действия
  11. Loading
  12. Error и повтор
  13. Props
  14. Смотри также
  15. Адаптивность
  16. Mobile layout
  17. Как форсировать платформу
  18. Платформенные пропы

Widget

@cloud-ru/ds-uikit-product-widget — Карточка продуктового виджета с кликабельным заголовком, SegmentControl, действиями и состояниями loading/error.

Widget — контейнер продуктовой карточки: TitleClickable в шапке, опциональный SegmentControl, слот управления, массив действий (Button, kebab/droplist) и body с состояниями default / loading / error.

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

  • Компактные блоки на dashboard и overview-страницах: заголовок-ссылка, переключатель вкладок, действия и контент в одной карточке.
  • Нужны единые состояния загрузки и ошибки с InfoBlock и кнопкой повтора без ручной вёрстки.
  • Действия должны адаптироваться по ширине: primary в шапке (wide desktop), overflow в kebab, на узком layout — кнопки в footer.

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

  • Простая карточка без шапки и действий:

    • используйте @cloud-ru/ds-block или @cloud-ru/ds-card.
  • Только кликабельный заголовок без оболочки:

    • используйте TitleClickable.
  • Сложная таблица или список с сортировкой и пагинацией:

    • используйте отдельный data-компонент, не оборачивайте в виджет.
  • ✅ Передавайте errorState.onClickUpdate и переключайте state обратно в default после успешного retry.

  • ❌ Оставлять state='error' без обработчика повтора — кнопка в InfoBlock не сможет восстановить контент.

  • ✅ На desktop с несколькими действиями включайте wide, чтобы primary и kebab жили в шапке.

  • ❌ Ожидать wide-раскладку на mobile — флаг wide принудительно отключается.

  • ✅ Оборачивайте демо и страницу с kebab/droplist в PortalContextProvider, если порталы рендерятся вне корня приложения.

  • ❌ Полагаться на глобальный portal-context из layout docs-сайта — каждый client:visible-островок изолирован.

  • ✅ Скрывайте лишние действия через hidden: true, не удаляя элемент из массива.

  • ❌ Дублировать один и тот же primary CTA в actions и в children — достаточно одного места.

Анатомия

State (default default)

Состояние из WIDGET_STATE:

  • default — рендерит children в body.
  • loading — skeleton в шапке; body — loadingState.loadingContent или skeleton при loadingState.showSkeleton.
  • error — InfoBlock с errorState и кнопкой onClickUpdate; видимые actions остаются в шапке для retry/навигации.

Wide (default false)

  • false — legacy layout: overflow-действия в kebab шапки, primary-кнопки на всю ширину под контентом (кроме error).
  • true — primary и kebab в одной строке шапки рядом с SegmentControl / actionsChildren. На mobile-раскладке игнорируется.

Action variant

Элемент actions[i] — discriminated union по variant (default — filled Button):

  • filled / outline / tonal / function / simple — пропсы @cloud-ru/ds-button + опциональный tooltip.
  • kebab — ButtonKebab + list.items (группы и пункты меню).
  • droplist — кнопка-триггер + выпадающий список.

Меню обоих вариантов рендерит Droplist из @cloud-ru/ds-list: на mobile список открывается в BottomSheet, на desktop — анкорным popover'ом. Раскладка берётся из AdaptiveProvider.

Общие поля: hidden, tooltip. Для списков: closeDroplistOnItemClick, controlled open / onOpenChange.

Slots

  • header — пропсы TitleClickable: title, href, icon, avatar, onClick, …
  • children — основной контент body.
  • segmentControl — пропсы SegmentControl в шапке (часто width: full на desktop).
  • actionsChildren — произвольный узел слева от кнопок (фильтр, badge, …).
  • loadingState / errorState — настройки соответствующих состояний.

Установка

pnpm add @cloud-ru/ds-uikit-product-widget
import { Widget, BUTTON_TYPE, WIDGET_STATE } from '@cloud-ru/ds-uikit-product-widget'

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

Контент и SegmentControl

Uncontrolled сегменты через defaultValue

import { WIDTH } from '@cloud-ru/ds-segment-control';
import { Widget } from '@cloud-ru/ds-uikit-product-widget';

export function DefaultContent() {
  return (
    <Widget
      header={{ title: 'Cloud servers', href: '#' }}
      segmentControl={{
        width: WIDTH.Full,
        defaultValue: 'overview',
        items: [
          { value: 'overview', label: 'Overview' },
          { value: 'events', label: 'Events' },
        ],
      }}
    >
      Keep product metrics, shortcuts, and status details in one compact card.
    </Widget>
  );
}

Wide desktop и действия

Primary в шапке, overflow в kebab; PortalContextProvider для dropdown

import { WIDTH } from '@cloud-ru/ds-segment-control';
import { BUTTON_TYPE, Widget } from '@cloud-ru/ds-uikit-product-widget';
import { useState } from 'react';

export function WithActions() {
  const [lastAction, setLastAction] = useState<string | null>(null);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      <Widget
        wide
        header={{ title: 'Managed databases', href: '#' }}
        segmentControl={{
          width: WIDTH.Auto,
          defaultValue: 'overview',
          items: [
            { value: 'overview', label: 'Overview' },
            { value: 'events', label: 'Events' },
          ],
        }}
        actions={[
          { label: 'Create', onClick: () => setLastAction('Create') },
          {
            variant: BUTTON_TYPE.Outline,
            label: 'Settings',
            onClick: () => setLastAction('Settings'),
          },
          {
            variant: BUTTON_TYPE.Kebab,
            list: {
              items: [
                { content: { label: 'Export' }, onClick: () => setLastAction('Export') },
                { content: { label: 'Archive' }, onClick: () => setLastAction('Archive') },
              ],
            },
          },
        ]}
      >
        Actions are shown in the header for wide desktop widgets.
      </Widget>
      {lastAction ? <span>Last action: {lastAction}</span> : null}
    </div>
  );
}

Loading

import { Widget } from '@cloud-ru/ds-uikit-product-widget';

export function LoadingState() {
  return (
    <Widget
      header={{ title: 'Billing', href: '#' }}
      state='loading'
      loadingState={{ showSkeleton: true }}
      actions={[{ label: 'Refresh' }]}
    >
      Billing summary
    </Widget>
  );
}

Error и повтор

onClickUpdate переключает state обратно в default

import { Widget } from '@cloud-ru/ds-uikit-product-widget';
import { useState } from 'react';

export function ErrorState() {
  const [state, setState] = useState<'default' | 'error'>('error');

  return (
    <Widget
      header={{ title: 'Monitoring', href: '#' }}
      state={state}
      errorState={{
        errorTitle: 'Metrics are unavailable',
        errorDescription: 'Try reloading the widget.',
        updateButtonLabel: 'Reload',
        onClickUpdate: () => setState('default'),
      }}
    >
      {state === 'error' ? 'Metrics' : 'Metrics loaded successfully.'}
    </Widget>
  );
}

Props

WidgetProps

PropTypeDefaultDescription
actionsAction | BaseAction | ButtonDroplistProps | ButtonKebabProps—Действия в шапке/footer.
actionsChildrenReactNode—Дополнительный слот рядом с действиями.
childrenReactNode—Контент виджета.
classNamestring—Дополнительный CSS-класс.
data-test-idstring—
errorStateWidgetErrorStateProps—Настройки error-состояния.
headerWidgetHeaderProps—Пропсы кликабельного заголовка.
loadingStateWidgetLoadingStateProps—Настройки loading-состояния.
segmentControlSegmentControlProps—Пропсы SegmentControl в шапке.
state"default" | "error" | "loading"—Состояние виджета.
wideboolean—Только desktop: wide-раскладка виджета. На mobile принудительно выключается (wide && !isMobile).
  • Action = ButtonAction | OutlineAction | TonalAction | FunctionAction | SimpleAction | KebabAction | DroplistAction

BaseAction

PropTypeDefaultDescription
hiddenboolean | undefined—Скрыть действие без удаления из массива.
tooltipTooltipProps—Tooltip вокруг кнопки действия.

ButtonDroplistProps

PropTypeDefaultDescription
buttonButtonProps—
listWidgetActionListProps—

ButtonKebabProps

PropTypeDefaultDescription
buttonButtonProps—
listWidgetActionListProps—

WidgetActionListProps

PropTypeDefaultDescription
classNamestring | undefined—
closeDroplistOnItemClickboolean | undefined—
itemsWidgetActionListEntry | WidgetActionListGroup | WidgetActionListItem—
onOpenChange((open: boolean) => void) | undefined—
openboolean | undefined—

WidgetErrorStateProps

PropTypeDefaultDescription
errorDescriptionstring | undefined—Описание ошибки.
errorIconIconPredefinedProps—Иконка InfoBlock.
errorTitlestring | undefined—Заголовок ошибки.
onClickUpdate(event: MouseEvent<HTMLElement, MouseEvent>) => void—Клик по кнопке повтора.
updateButtonLabelstring | undefined—Текст кнопки повтора.

WidgetHeaderProps

PropTypeDefaultDescription
childrenstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—
classNamestring | undefined—CSS-класс корневого контейнера.
data-test-idstring | undefined—
hrefstring—
iconstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Иконка слева, 24×24.
titlestring—Текст заголовка.
titleTagElementType | undefined—Семантический тег заголовка.

WidgetLoadingStateProps

PropTypeDefaultDescription
loadingContentstring | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined—Кастомный контент для состояния загрузки.
showSkeletonboolean | undefined—Показывать skeleton-заглушку в body.
  • WidgetState = "default" | "error" | "loading"

Смотри также

  • TitleClickable — заголовок-ссылка в шапке виджета.
  • SegmentControl — переключатель вкладок в шапке.
  • InfoBlock — блок ошибки внутри state='error'.

Адаптивность

Widget — адаптивный компонент: DOM остаётся единым, но при mobile-раскладке карточка перестраивается. Раскладку он берёт из AdaptiveProvider (контекст @cloud-ru/ds-adaptive); публичный API единый для обеих платформ:

  • desktop (по умолчанию) — учитывает wide и динамическое схлопывание действий в kebab по ширине контейнера.
  • mobile — узкий режим: wide принудительно выключен, primary-кнопки уезжают в footer.

Верстайте под desktop и поставьте один <AdaptiveProvider> в корне приложения — mobile-перестроение включается автоматически (desktop-first). Пропа layoutType у компонента нет: источник раскладки — только контекст.

Mobile layout

wide игнорируется, primary уезжает в footer

import { AdaptiveProvider, LAYOUT_TYPE } from '@cloud-ru/ds-adaptive';
import { BUTTON_TYPE, Widget } from '@cloud-ru/ds-uikit-product-widget';
import { useState } from 'react';

export function MobileLayout() {
  const [lastAction, setLastAction] = useState<string | null>(null);

  return (
    <AdaptiveProvider layoutType={LAYOUT_TYPE.Mobile}>
      <div style={{ maxWidth: 360, display: 'flex', flexDirection: 'column', gap: 8 }}>
        <Widget
          wide
          header={{ title: 'Object storage', href: '#' }}
          actions={[
            { label: 'Upload', onClick: () => setLastAction('Upload') },
            {
              variant: BUTTON_TYPE.Kebab,
              list: {
                items: [
                  {
                    content: { label: 'Delete bucket' },
                    onClick: () => setLastAction('Delete bucket'),
                  },
                ],
              },
            },
          ]}
        >
          On mobile, wide is ignored: primary actions move to the footer, overflow goes to kebab.
        </Widget>
        {lastAction ? <span>Last action: {lastAction}</span> : null}
      </div>
    </AdaptiveProvider>
  );
}

Как форсировать платформу

Форс — только контекстом, не пропом:

  • Поддерево — вложенный провайдер:
    import { AdaptiveProvider } from '@cloud-ru/ds-adaptive'
    
    <AdaptiveProvider layoutType='mobile'>
      <Widget header={header} actions={actions}>{content}</Widget>
    </AdaptiveProvider>
    
  • Отдельный компонент — withLayoutType (module-scope, сахар над провайдером):
    import { withLayoutType } from '@cloud-ru/ds-adaptive'
    import { Widget } from '@cloud-ru/ds-uikit-product-widget'
    
    const MobileWidget = withLayoutType(Widget, 'mobile')
    

Платформенные пропы

Таблица синхронизирована с JSDoc-пометками у WidgetProps.

Пропdesktopmobile
wideиспользуетсяигнорируется (принудительно выключен)

Подробнее о модели адаптивности — Адаптивность — паттерн.

版本与发布记录

@cloud-ru/ds-uikit-product-widget 共发布 179 个版本,首次发布于 2026/08/18;过去 12 个月发布了 179 个版本。

版本发布日期距今
2.1.0latest2026/11/11今天
2.0.8-preview-e8fe6b56.0preview2026/11/11今天
2.0.8-preview-c0e895ed.02026/11/11今天
2.0.8-preview-90b10112.02026/11/11今天
2.0.8-preview-fd7dd437.02026/11/101 天前
2.0.8-preview-9a75a2a9.02026/11/101 天前

关于 @cloud-ru/ds-uikit-product-widget 的常见问题

@cloud-ru/ds-uikit-product-widget 是什么?

Карточка продуктового виджета с кликабельным заголовком, SegmentControl, действиями и состояниями loading/error.

如何安装 @cloud-ru/ds-uikit-product-widget?

在项目目录运行 npm install @cloud-ru/ds-uikit-product-widget,或使用 pnpm add / yarn add / bun add。

@cloud-ru/ds-uikit-product-widget 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cloud-ru/ds-uikit-product-widget 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cloud-ru/ds-uikit-product-widget 可以商用吗?

@cloud-ru/ds-uikit-product-widget 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@cloud-ru/ds-uikit-product-widget 的下载量有多少?

根据 npm 官方下载统计,@cloud-ru/ds-uikit-product-widget 在 2026/10/10 – 2026/11/08 这 30 天内被下载 6,068 次,日均约 202 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cloud-ru/ds-uikit-product-widget 还在维护吗?

最新版本 2.1.0 发布于 2026/11/11(今天);至今共发布 179 个版本,其中过去 12 个月发布了 179 个;首次发布于 2026/08/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。