QuotaWidget
Кнопка «Квоты» с counter’ом исчерпанных квот, по клику открывающая Dropdown с шапкой проекта и сеткой карточек QuotaWidgetCard. Шапка содержит название проекта, ссылку на страницу квот (quotasUrl) и кнопку «Увеличить квоты» (при canEditQuota).
Когда использовать
-
Шапка продуктовой страницы или toolbar, где квоты — вторичная информация и раскрываются по требованию.
-
Нужен индикатор исчерпанных квот без постоянного места под список.
-
Узкий сайдбар, где список квот должен быть виден без портала:
- используйте
QuotaWidgetMini.
- используйте
-
Одна конкретная квота на странице ресурса:
- используйте
QuotaWidgetCard.
- используйте
-
✅ Оборачивайте страницу в
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'
Примеры использования
Базовый случай
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>
);
}Ошибка загрузки и повтор
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
QuotaWidgetProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
buttonProps | Pick<ButtonProps, "className" | "view" | "label" | "disabled" | "size" | "appearance" | "fullWidth"> | — | no | Свойства кнопки открытия виджета |
canEditQuota | boolean | — | yes | Флаг наличия прав на редактирование квот |
data-test-id | string | — | no | |
disableSorting | boolean | — | no | Флаг отключения сортировки квот |
error | boolean | — | yes | Флаг ошибки при загрузке квот |
hideIncreaseQuotaButton | boolean | — | no | Флаг скрытия кнопки увеличения квоты |
loading | boolean | — | yes | Флаг загрузки квот |
onIncreaseQuotaClick | (() => void) | — | no | Колбек нажатия на кнопку увеличения квот |
onQuotasUrlClick | (() => void) | — | no | Колбек клика по ссылке на страницу квот по проекту |
onRefresh | () => void | — | yes | Колбек на обновление списка квот при ошибке |
onWidgetOpen | (() => void) | — | no | Колбек открытия виджета квот |
projectName | string | — | yes | Название проекта, по которому отображаются квоты |
quotas | QuotaItem[] | — | yes | Список квот для отображения |
quotasUrl | string | — | yes | Ссылка на страницу квот по проекту |
Types
QuotaWidgetProps
QuotaItem
Related props
ButtonProps
QuotaWidgetContent
Types
QuotaWidgetContentProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
canEditQuota | boolean | — | yes | Флаг наличия прав на редактирование квот |
data-test-id | string | — | no | |
disableSorting | boolean | — | no | Флаг отключения сортировки квот |
error | boolean | — | yes | Флаг ошибки при загрузке квот |
hideIncreaseQuotaButton | boolean | — | no | Флаг скрытия кнопки увеличения квоты |
hideMobileHeader | boolean | false | no | Не рендерить мобильную шапку (её выносят в pinned-`title` контейнера, напр. BottomSheet). На desktop не влияет. |
hideMobileIncreaseButton | boolean | false | no | Не рендерить мобильную кнопку расширения (её выносят в pinned-`footer` контейнера). На desktop не влияет. |
loading | boolean | — | yes | Флаг загрузки квот |
onIncreaseQuotaClick | (() => void) | — | no | Колбек нажатия на кнопку увеличения квот |
onQuotasUrlClick | (() => void) | — | no | Колбек клика по ссылке на страницу квот по проекту |
onRefresh | () => void | — | yes | Колбек на обновление списка квот при ошибке |
onWidgetOpen | (() => void) | — | no | Колбек открытия виджета квот |
projectName | string | — | yes | Название проекта, по которому отображаются квоты |
quotas | QuotaItem[] | — | yes | Список квот для отображения |
quotasUrl | string | — | yes | Ссылка на страницу квот по проекту |
Types
QuotaWidgetContentProps
QuotaItem
Storybook
Figma
Смотри также
- QuotaWidgetMini — тот же список в аккордеоне без портала.
- QuotaWidgetCard — карточка одной квоты.
- Dropdown — нижележащий портальный примитив.