QuotaWidget

Кнопка «Квоты» с counter’ом исчерпанных квот, по клику открывающая Dropdown с шапкой проекта и сеткой карточек QuotaWidgetCard. Шапка содержит название проекта, ссылку на страницу квот (quotasUrl) и кнопку «Увеличить квоты» (при canEditQuota).

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

  • Шапка продуктовой страницы или toolbar, где квоты — вторичная информация и раскрываются по требованию.

  • Нужен индикатор исчерпанных квот без постоянного места под список.

  • Узкий сайдбар, где список квот должен быть виден без портала:

  • Одна конкретная квота на странице ресурса:

  • ✅ Оборачивайте страницу в PortalContextProvider — контент рендерится в портал Dropdown.

  • ❌ Рендерить виджет без portal-контекста — выпадающий список не появится.

  • ✅ Переключайте error обратно после успешного onRefresh — кнопка повтора в состоянии ошибки.

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

Анатомия

Триггер

Button view='function' с label «Квоты» и chevron-иконкой; counter показывает число исчерпанных квот (remains <= 0). Внешний вид настраивается через buttonProps (size, appearance, label, fullWidth, disabled, className).

Шапка проекта

  • projectName — название проекта.
  • quotasUrl + onQuotasUrlClick — ссылка на страницу квот.
  • canEditQuota + onIncreaseQuotaClick — кнопка «Увеличить квоты»; скрывается hideIncreaseQuotaButton.

Список квот

Сетка карточек QuotaWidgetCard. Исчерпанные квоты поднимаются в начало (отключается disableSorting). Состояния:

  • loading — skeleton-карточки.
  • error — блок ошибки с кнопкой повтора (onRefresh).

Контент без триггера — QuotaWidgetContent

Шапка проекта и сетка карточек без кнопки-триггера и без Dropdown. Нужен, когда раскрытие даёт контейнер снаружи — например sideBlock формы, который на mobile уже открывает BottomSheet. Принимает те же данные и состояния, что QuotaWidget.

На mobile QuotaWidget выносит шапку и кнопку «Увеличить квоты» в pinned-слоты title и footer листа, чтобы они не скроллились вместе с карточками. За это отвечают два пропа:

  • hideMobileHeader — не рендерить мобильную шапку внутри контента.
  • hideMobileIncreaseButton — не рендерить мобильную кнопку расширения внутри контента.

Оба влияют только на mobile-раскладку. Включайте их, когда сами кладёте заголовок и кнопку в слоты своего контейнера, иначе элементы отрисуются дважды.

Установка

pnpm add @cloud-ru/ds-uikit-product-quota
import { QuotaWidget } from '@cloud-ru/ds-uikit-product-quota'

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

Базовый случай

Базовый случайCounter исчерпанных квот на триггере, сетка карточек в dropdown
tsx
import { QuotaItem, QuotaWidget } from '@cloud-ru/ds-uikit-product-quota';
import { useState } from 'react';

const QUOTAS: QuotaItem[] = [
  { name: 'vCPU', limit: 100, usage: 42, remains: 58, unitDisplayName: 'cores' },
  { name: 'RAM', limit: 256, usage: 230, remains: 26, unitDisplayName: 'GB' },
  { name: 'SSD', limit: 1000, usage: 1000, remains: 0, unitDisplayName: 'GB' },
  { name: 'Public IP', limit: 10, usage: 9, remains: 1, unitDisplayName: 'pcs' },
];

export function QuotaWidgetBasic() {
  const [loading, setLoading] = useState(false);

  const reload = () => {
    setLoading(true);
    setTimeout(() => setLoading(false), 800);
  };

  return (
    // В продукте виджет живёт у правого края страницы — dropdown прижат к правому краю триггера
    <div style={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
      <QuotaWidget
        quotas={QUOTAS}
        projectName='ml-platform-production'
        quotasUrl='#'
        canEditQuota={false}
        loading={loading}
        error={false}
        onRefresh={reload}
      />
    </div>
  );
}

Ошибка загрузки и повтор

Ошибка загрузки и повторerror показывает блок ошибки; onRefresh переключает в loading и восстанавливает список
tsx
import { QuotaItem, QuotaWidget } from '@cloud-ru/ds-uikit-product-quota';
import { useState } from 'react';

const QUOTAS: QuotaItem[] = [
  { name: 'vCPU', limit: 100, usage: 42, remains: 58, unitDisplayName: 'cores' },
  { name: 'RAM', limit: 256, usage: 230, remains: 26, unitDisplayName: 'GB' },
];

export function QuotaWidgetError() {
  const [error, setError] = useState(true);
  const [loading, setLoading] = useState(false);

  const handleRefresh = () => {
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      setError(false);
    }, 800);
  };

  return (
    // В продукте виджет живёт у правого края страницы — dropdown прижат к правому краю триггера
    <div style={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
      <QuotaWidget
        quotas={QUOTAS}
        projectName='ml-platform-production'
        quotasUrl='#'
        canEditQuota={false}
        loading={loading}
        error={error}
        onRefresh={handleRefresh}
      />
    </div>
  );
}

Props

QuotaWidget

Types

PropsQuotaWidgetProps
PropTypeDefaultRequiredDescription
buttonPropsPick<ButtonProps, "className" | "view" | "label" | "disabled" | "size" | "appearance" | "fullWidth">noСвойства кнопки открытия виджета
canEditQuotabooleanyesФлаг наличия прав на редактирование квот
data-test-idstringno
disableSortingbooleannoФлаг отключения сортировки квот
errorbooleanyesФлаг ошибки при загрузке квот
hideIncreaseQuotaButtonbooleannoФлаг скрытия кнопки увеличения квоты
loadingbooleanyesФлаг загрузки квот
onIncreaseQuotaClick(() => void)noКолбек нажатия на кнопку увеличения квот
onQuotasUrlClick(() => void)noКолбек клика по ссылке на страницу квот по проекту
onRefresh() => voidyesКолбек на обновление списка квот при ошибке
onWidgetOpen(() => void)noКолбек открытия виджета квот
projectNamestringyesНазвание проекта, по которому отображаются квоты
quotasQuotaItem[]yesСписок квот для отображения
quotasUrlstringyesСсылка на страницу квот по проекту

Types

QuotaWidgetProps

QuotaWidgetContent

Types

PropsQuotaWidgetContentProps
PropTypeDefaultRequiredDescription
canEditQuotabooleanyesФлаг наличия прав на редактирование квот
data-test-idstringno
disableSortingbooleannoФлаг отключения сортировки квот
errorbooleanyesФлаг ошибки при загрузке квот
hideIncreaseQuotaButtonbooleannoФлаг скрытия кнопки увеличения квоты
hideMobileHeaderbooleanfalsenoНе рендерить мобильную шапку (её выносят в pinned-`title` контейнера, напр. BottomSheet). На desktop не влияет.
hideMobileIncreaseButtonbooleanfalsenoНе рендерить мобильную кнопку расширения (её выносят в pinned-`footer` контейнера). На desktop не влияет.
loadingbooleanyesФлаг загрузки квот
onIncreaseQuotaClick(() => void)noКолбек нажатия на кнопку увеличения квот
onQuotasUrlClick(() => void)noКолбек клика по ссылке на страницу квот по проекту
onRefresh() => voidyesКолбек на обновление списка квот при ошибке
onWidgetOpen(() => void)noКолбек открытия виджета квот
projectNamestringyesНазвание проекта, по которому отображаются квоты
quotasQuotaItem[]yesСписок квот для отображения
quotasUrlstringyesСсылка на страницу квот по проекту

Types

QuotaWidgetContentProps

Storybook

Figma

Смотри также

  • QuotaWidgetMini — тот же список в аккордеоне без портала.
  • QuotaWidgetCard — карточка одной квоты.
  • Dropdown — нижележащий портальный примитив.