@cloud-ru/ds-uikit-product-quota 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +458 -0
  4. package/dist/cjs/components/QuotaWidget/QuotaIncreaseButton.d.ts +4 -0
  5. package/dist/cjs/components/QuotaWidget/QuotaIncreaseButton.js +15 -0
  6. package/dist/cjs/components/QuotaWidget/QuotaMobileHeader.d.ts +4 -0
  7. package/dist/cjs/components/QuotaWidget/QuotaMobileHeader.js +15 -0
  8. package/dist/cjs/components/QuotaWidget/QuotaWidget.d.ts +12 -0
  9. package/dist/cjs/components/QuotaWidget/QuotaWidget.js +44 -0
  10. package/dist/cjs/components/QuotaWidget/QuotaWidgetContent.d.ts +27 -0
  11. package/dist/cjs/components/QuotaWidget/QuotaWidgetContent.js +33 -0
  12. package/dist/cjs/components/QuotaWidget/components/ProjectHeader/ProjectHeader.d.ts +7 -0
  13. package/dist/cjs/components/QuotaWidget/components/ProjectHeader/ProjectHeader.js +19 -0
  14. package/dist/cjs/components/QuotaWidget/components/ProjectHeader/index.d.ts +1 -0
  15. package/dist/cjs/components/QuotaWidget/components/ProjectHeader/index.js +17 -0
  16. package/dist/cjs/components/QuotaWidget/components/ProjectHeader/styles.module.css +30 -0
  17. package/dist/cjs/components/QuotaWidget/index.d.ts +2 -0
  18. package/dist/cjs/components/QuotaWidget/index.js +18 -0
  19. package/dist/cjs/components/QuotaWidget/styles.module.css +49 -0
  20. package/dist/cjs/components/QuotaWidgetCard/QuotaWidgetCard.d.ts +13 -0
  21. package/dist/cjs/components/QuotaWidgetCard/QuotaWidgetCard.js +53 -0
  22. package/dist/cjs/components/QuotaWidgetCard/components/QuotaTooltip/QuotaTooltip.d.ts +6 -0
  23. package/dist/cjs/components/QuotaWidgetCard/components/QuotaTooltip/QuotaTooltip.js +23 -0
  24. package/dist/cjs/components/QuotaWidgetCard/components/QuotaTooltip/index.d.ts +1 -0
  25. package/dist/cjs/components/QuotaWidgetCard/components/QuotaTooltip/index.js +17 -0
  26. package/dist/cjs/components/QuotaWidgetCard/components/QuotaTooltip/styles.module.css +34 -0
  27. package/dist/cjs/components/QuotaWidgetCard/index.d.ts +1 -0
  28. package/dist/cjs/components/QuotaWidgetCard/index.js +17 -0
  29. package/dist/cjs/components/QuotaWidgetCard/styles.module.css +97 -0
  30. package/dist/cjs/components/QuotaWidgetMini/QuotaWidgetMini.d.ts +7 -0
  31. package/dist/cjs/components/QuotaWidgetMini/QuotaWidgetMini.js +27 -0
  32. package/dist/cjs/components/QuotaWidgetMini/index.d.ts +1 -0
  33. package/dist/cjs/components/QuotaWidgetMini/index.js +17 -0
  34. package/dist/cjs/components/QuotaWidgetMini/styles.module.css +15 -0
  35. package/dist/cjs/components/index.d.ts +3 -0
  36. package/dist/cjs/components/index.js +19 -0
  37. package/dist/cjs/constants.d.ts +17 -0
  38. package/dist/cjs/constants.js +20 -0
  39. package/dist/cjs/helperComponents/QuotaCardsGrid/QuotaCardsGrid.d.ts +6 -0
  40. package/dist/cjs/helperComponents/QuotaCardsGrid/QuotaCardsGrid.js +32 -0
  41. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/Grid.d.ts +6 -0
  42. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/Grid.js +30 -0
  43. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/QuotaCardsSkeleton.d.ts +6 -0
  44. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/QuotaCardsSkeleton.js +16 -0
  45. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/index.d.ts +1 -0
  46. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/index.js +17 -0
  47. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/styles.module.css +10 -0
  48. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/index.d.ts +1 -0
  49. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/index.js +17 -0
  50. package/dist/cjs/helperComponents/QuotaCardsGrid/components/Grid/styles.module.css +9 -0
  51. package/dist/cjs/helperComponents/QuotaCardsGrid/components/QuotaError/QuotaError.d.ts +5 -0
  52. package/dist/cjs/helperComponents/QuotaCardsGrid/components/QuotaError/QuotaError.js +12 -0
  53. package/dist/cjs/helperComponents/QuotaCardsGrid/components/QuotaError/index.d.ts +1 -0
  54. package/dist/cjs/helperComponents/QuotaCardsGrid/components/QuotaError/index.js +17 -0
  55. package/dist/cjs/helperComponents/QuotaCardsGrid/index.d.ts +1 -0
  56. package/dist/cjs/helperComponents/QuotaCardsGrid/index.js +17 -0
  57. package/dist/cjs/helperComponents/QuotaCardsGrid/styles.module.css +16 -0
  58. package/dist/cjs/index.d.ts +5 -0
  59. package/dist/cjs/index.js +21 -0
  60. package/dist/cjs/locale/index.d.ts +91 -0
  61. package/dist/cjs/locale/index.js +50 -0
  62. package/dist/cjs/package.json +3 -0
  63. package/dist/cjs/style.css +260 -0
  64. package/dist/cjs/types.d.ts +34 -0
  65. package/dist/cjs/types.js +2 -0
  66. package/dist/cjs/utils/checkIsExceeded.d.ts +2 -0
  67. package/dist/cjs/utils/checkIsExceeded.js +5 -0
  68. package/dist/cjs/utils/formatNumber.d.ts +1 -0
  69. package/dist/cjs/utils/formatNumber.js +6 -0
  70. package/dist/cjs/utils/getPercent.d.ts +2 -0
  71. package/dist/cjs/utils/getPercent.js +9 -0
  72. package/dist/cjs/utils/index.d.ts +3 -0
  73. package/dist/cjs/utils/index.js +19 -0
  74. package/dist/esm/components/QuotaWidget/QuotaIncreaseButton.d.ts +4 -0
  75. package/dist/esm/components/QuotaWidget/QuotaIncreaseButton.js +9 -0
  76. package/dist/esm/components/QuotaWidget/QuotaMobileHeader.d.ts +4 -0
  77. package/dist/esm/components/QuotaWidget/QuotaMobileHeader.js +9 -0
  78. package/dist/esm/components/QuotaWidget/QuotaWidget.d.ts +12 -0
  79. package/dist/esm/components/QuotaWidget/QuotaWidget.js +41 -0
  80. package/dist/esm/components/QuotaWidget/QuotaWidgetContent.d.ts +27 -0
  81. package/dist/esm/components/QuotaWidget/QuotaWidgetContent.js +27 -0
  82. package/dist/esm/components/QuotaWidget/components/ProjectHeader/ProjectHeader.d.ts +7 -0
  83. package/dist/esm/components/QuotaWidget/components/ProjectHeader/ProjectHeader.js +13 -0
  84. package/dist/esm/components/QuotaWidget/components/ProjectHeader/index.d.ts +1 -0
  85. package/dist/esm/components/QuotaWidget/components/ProjectHeader/index.js +1 -0
  86. package/dist/esm/components/QuotaWidget/components/ProjectHeader/styles.module.css +30 -0
  87. package/dist/esm/components/QuotaWidget/index.d.ts +2 -0
  88. package/dist/esm/components/QuotaWidget/index.js +2 -0
  89. package/dist/esm/components/QuotaWidget/styles.module.css +49 -0
  90. package/dist/esm/components/QuotaWidgetCard/QuotaWidgetCard.d.ts +13 -0
  91. package/dist/esm/components/QuotaWidgetCard/QuotaWidgetCard.js +47 -0
  92. package/dist/esm/components/QuotaWidgetCard/components/QuotaTooltip/QuotaTooltip.d.ts +6 -0
  93. package/dist/esm/components/QuotaWidgetCard/components/QuotaTooltip/QuotaTooltip.js +17 -0
  94. package/dist/esm/components/QuotaWidgetCard/components/QuotaTooltip/index.d.ts +1 -0
  95. package/dist/esm/components/QuotaWidgetCard/components/QuotaTooltip/index.js +1 -0
  96. package/dist/esm/components/QuotaWidgetCard/components/QuotaTooltip/styles.module.css +34 -0
  97. package/dist/esm/components/QuotaWidgetCard/index.d.ts +1 -0
  98. package/dist/esm/components/QuotaWidgetCard/index.js +1 -0
  99. package/dist/esm/components/QuotaWidgetCard/styles.module.css +97 -0
  100. package/dist/esm/components/QuotaWidgetMini/QuotaWidgetMini.d.ts +7 -0
  101. package/dist/esm/components/QuotaWidgetMini/QuotaWidgetMini.js +21 -0
  102. package/dist/esm/components/QuotaWidgetMini/index.d.ts +1 -0
  103. package/dist/esm/components/QuotaWidgetMini/index.js +1 -0
  104. package/dist/esm/components/QuotaWidgetMini/styles.module.css +15 -0
  105. package/dist/esm/components/index.d.ts +3 -0
  106. package/dist/esm/components/index.js +3 -0
  107. package/dist/esm/constants.d.ts +17 -0
  108. package/dist/esm/constants.js +17 -0
  109. package/dist/esm/helperComponents/QuotaCardsGrid/QuotaCardsGrid.d.ts +6 -0
  110. package/dist/esm/helperComponents/QuotaCardsGrid/QuotaCardsGrid.js +26 -0
  111. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/Grid.d.ts +6 -0
  112. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/Grid.js +24 -0
  113. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/QuotaCardsSkeleton.d.ts +6 -0
  114. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/QuotaCardsSkeleton.js +10 -0
  115. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/index.d.ts +1 -0
  116. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/index.js +1 -0
  117. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/styles.module.css +10 -0
  118. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/index.d.ts +1 -0
  119. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/index.js +1 -0
  120. package/dist/esm/helperComponents/QuotaCardsGrid/components/Grid/styles.module.css +9 -0
  121. package/dist/esm/helperComponents/QuotaCardsGrid/components/QuotaError/QuotaError.d.ts +5 -0
  122. package/dist/esm/helperComponents/QuotaCardsGrid/components/QuotaError/QuotaError.js +9 -0
  123. package/dist/esm/helperComponents/QuotaCardsGrid/components/QuotaError/index.d.ts +1 -0
  124. package/dist/esm/helperComponents/QuotaCardsGrid/components/QuotaError/index.js +1 -0
  125. package/dist/esm/helperComponents/QuotaCardsGrid/index.d.ts +1 -0
  126. package/dist/esm/helperComponents/QuotaCardsGrid/index.js +1 -0
  127. package/dist/esm/helperComponents/QuotaCardsGrid/styles.module.css +16 -0
  128. package/dist/esm/index.d.ts +5 -0
  129. package/dist/esm/index.js +5 -0
  130. package/dist/esm/locale/index.d.ts +91 -0
  131. package/dist/esm/locale/index.js +47 -0
  132. package/dist/esm/style.css +260 -0
  133. package/dist/esm/types.d.ts +34 -0
  134. package/dist/esm/types.js +1 -0
  135. package/dist/esm/utils/checkIsExceeded.d.ts +2 -0
  136. package/dist/esm/utils/checkIsExceeded.js +1 -0
  137. package/dist/esm/utils/formatNumber.d.ts +1 -0
  138. package/dist/esm/utils/formatNumber.js +3 -0
  139. package/dist/esm/utils/getPercent.d.ts +2 -0
  140. package/dist/esm/utils/getPercent.js +6 -0
  141. package/dist/esm/utils/index.d.ts +3 -0
  142. package/dist/esm/utils/index.js +3 -0
  143. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  144. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  145. package/package.json +63 -0
  146. package/src/components/QuotaWidget/QuotaIncreaseButton.tsx +21 -0
  147. package/src/components/QuotaWidget/QuotaMobileHeader.tsx +18 -0
  148. package/src/components/QuotaWidget/QuotaWidget.tsx +109 -0
  149. package/src/components/QuotaWidget/QuotaWidgetContent.tsx +91 -0
  150. package/src/components/QuotaWidget/components/ProjectHeader/ProjectHeader.tsx +59 -0
  151. package/src/components/QuotaWidget/components/ProjectHeader/index.ts +1 -0
  152. package/src/components/QuotaWidget/components/ProjectHeader/styles.module.scss +29 -0
  153. package/src/components/QuotaWidget/index.ts +2 -0
  154. package/src/components/QuotaWidget/styles.module.scss +52 -0
  155. package/src/components/QuotaWidgetCard/QuotaWidgetCard.tsx +127 -0
  156. package/src/components/QuotaWidgetCard/components/QuotaTooltip/QuotaTooltip.tsx +48 -0
  157. package/src/components/QuotaWidgetCard/components/QuotaTooltip/index.ts +1 -0
  158. package/src/components/QuotaWidgetCard/components/QuotaTooltip/styles.module.scss +30 -0
  159. package/src/components/QuotaWidgetCard/index.ts +1 -0
  160. package/src/components/QuotaWidgetCard/styles.module.scss +97 -0
  161. package/src/components/QuotaWidgetMini/QuotaWidgetMini.tsx +93 -0
  162. package/src/components/QuotaWidgetMini/index.ts +1 -0
  163. package/src/components/QuotaWidgetMini/styles.module.scss +18 -0
  164. package/src/components/index.ts +3 -0
  165. package/src/constants.ts +17 -0
  166. package/src/helperComponents/QuotaCardsGrid/QuotaCardsGrid.tsx +54 -0
  167. package/src/helperComponents/QuotaCardsGrid/components/Grid/Grid.tsx +47 -0
  168. package/src/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/QuotaCardsSkeleton.tsx +24 -0
  169. package/src/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/index.ts +1 -0
  170. package/src/helperComponents/QuotaCardsGrid/components/Grid/components/QuotaWidgetCardsSkeleton/styles.module.scss +15 -0
  171. package/src/helperComponents/QuotaCardsGrid/components/Grid/index.ts +1 -0
  172. package/src/helperComponents/QuotaCardsGrid/components/Grid/styles.module.scss +12 -0
  173. package/src/helperComponents/QuotaCardsGrid/components/QuotaError/QuotaError.tsx +31 -0
  174. package/src/helperComponents/QuotaCardsGrid/components/QuotaError/index.ts +1 -0
  175. package/src/helperComponents/QuotaCardsGrid/index.ts +1 -0
  176. package/src/helperComponents/QuotaCardsGrid/styles.module.scss +16 -0
  177. package/src/index.ts +5 -0
  178. package/src/locale/index.ts +51 -0
  179. package/src/types.ts +35 -0
  180. package/src/utils/checkIsExceeded.ts +3 -0
  181. package/src/utils/formatNumber.ts +3 -0
  182. package/src/utils/getPercent.ts +9 -0
  183. package/src/utils/index.ts +3 -0
@@ -0,0 +1,93 @@
1
+ import { Accordion } from '@cloud-ru/ds-accordion';
2
+ import { Button } from '@cloud-ru/ds-button';
3
+ import { Counter } from '@cloud-ru/ds-counter';
4
+ import { TruncateString } from '@cloud-ru/ds-truncate-string';
5
+ import { WithSupportProps } from '@cloud-ru/ds-utils';
6
+
7
+ import { TEST_IDS } from '../../constants';
8
+ import { QuotaCardsGrid } from '../../helperComponents/QuotaCardsGrid';
9
+ import { quotaLocale } from '../../locale';
10
+ import { QuotaWidgetPropsBase } from '../../types';
11
+ import { checkIsExceeded } from '../../utils';
12
+ import styles from './styles.module.scss';
13
+
14
+ export type QuotaWidgetMiniProps = WithSupportProps<
15
+ QuotaWidgetPropsBase & {
16
+ /** Флаг раскрытия аккордиона по умолчанию */
17
+ defaultExpanded?: boolean;
18
+ }
19
+ >;
20
+
21
+ const ACCORDION_ID = 'quotas-widget-accordion';
22
+
23
+ export function QuotaWidgetMini({
24
+ quotas,
25
+ disableSorting,
26
+ loading,
27
+ error,
28
+ onRefresh,
29
+ projectName,
30
+ canEditQuota,
31
+ defaultExpanded,
32
+ hideIncreaseQuotaButton,
33
+ onIncreaseQuotaClick,
34
+ onWidgetOpen,
35
+ 'data-test-id': dataTestId,
36
+ ...props
37
+ }: QuotaWidgetMiniProps) {
38
+ const { t } = quotaLocale.useTranslations();
39
+
40
+ const exhaustedCount = quotas.filter(checkIsExceeded).length;
41
+
42
+ const handleExpandedChange = (expanded: string | undefined) => {
43
+ if (expanded) {
44
+ onWidgetOpen?.();
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div data-test-id={dataTestId ?? TEST_IDS.quotaWidgetMini.root}>
50
+ <Accordion
51
+ {...props}
52
+ expandedDefault={defaultExpanded ? ACCORDION_ID : undefined}
53
+ onExpandedChange={handleExpandedChange}
54
+ >
55
+ <Accordion.CollapseBlockSecondary
56
+ id={ACCORDION_ID}
57
+ className={styles.collapse}
58
+ data-test-id={TEST_IDS.quotaWidgetMini.trigger}
59
+ title={t('quotas')}
60
+ afterTitle={
61
+ !error && !loading && exhaustedCount > 0 ? (
62
+ <Counter value={exhaustedCount} size='xs' appearance='red' />
63
+ ) : undefined
64
+ }
65
+ subTitle={<TruncateString maxLines={1} text={projectName} />}
66
+ >
67
+ <div className={styles.body} data-test-id={TEST_IDS.quotaWidgetMini.content}>
68
+ <QuotaCardsGrid
69
+ isAccordion
70
+ quotas={quotas}
71
+ disableSorting={disableSorting}
72
+ loading={loading}
73
+ error={error}
74
+ onRefresh={onRefresh}
75
+ />
76
+
77
+ {!error && canEditQuota && !hideIncreaseQuotaButton && (
78
+ <Button
79
+ className={styles.button}
80
+ fullWidth
81
+ view='outline'
82
+ appearance='neutral'
83
+ label={t('increaseQuota')}
84
+ size='m'
85
+ onClick={onIncreaseQuotaClick}
86
+ />
87
+ )}
88
+ </div>
89
+ </Accordion.CollapseBlockSecondary>
90
+ </Accordion>
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1 @@
1
+ export * from './QuotaWidgetMini';
@@ -0,0 +1,18 @@
1
+ @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
+
3
+ .collapse {
4
+ width: 304px;
5
+ }
6
+
7
+ .body {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: base.$sn-primitive-dimension-16;
11
+ align-items: flex-start;
12
+
13
+ width: 100%;
14
+ }
15
+
16
+ .button {
17
+ width: 100%;
18
+ }
@@ -0,0 +1,3 @@
1
+ export * from './QuotaWidget';
2
+ export * from './QuotaWidgetMini';
3
+ export * from './QuotaWidgetCard';
@@ -0,0 +1,17 @@
1
+ export const TEST_IDS = {
2
+ quotaWidget: {
3
+ root: 'quota-widget',
4
+ trigger: 'quota-widget__trigger',
5
+ content: 'quota-widget__content',
6
+ projectHeader: 'quota-widget__project-header',
7
+ },
8
+ quotaWidgetMini: {
9
+ root: 'quota-widget-mini',
10
+ trigger: 'quota-widget-mini__trigger',
11
+ content: 'quota-widget-mini__content',
12
+ },
13
+ quotaWidgetCard: {
14
+ root: 'quota-widget-card',
15
+ tooltip: 'quota-widget-card__tooltip',
16
+ },
17
+ } as const;
@@ -0,0 +1,54 @@
1
+ import { isMobileLayout, useAdaptiveLayout } from '@cloud-ru/ds-adaptive';
2
+ import { Scroll } from '@cloud-ru/ds-scroll';
3
+
4
+ import { QuotaWidgetPropsBase } from '../../types';
5
+ import { Grid } from './components/Grid';
6
+ import { QuotaError } from './components/QuotaError';
7
+ import styles from './styles.module.scss';
8
+
9
+ type QuotaCardsGridProps = Pick<
10
+ QuotaWidgetPropsBase,
11
+ 'quotas' | 'disableSorting' | 'loading' | 'error' | 'onRefresh'
12
+ > & {
13
+ isAccordion?: boolean;
14
+ };
15
+
16
+ export function QuotaCardsGrid({
17
+ quotas,
18
+ loading,
19
+ error,
20
+ onRefresh,
21
+ disableSorting = false,
22
+ isAccordion = false,
23
+ }: QuotaCardsGridProps) {
24
+ // На мобилке bottom-sheet-виджет full-height: снимаем фикс-cap высоты (`max-height`), карточки
25
+ // скроллятся телом листа. Для аккордеона (`QuotaWidgetMini`) это НЕ применяем — у него свой cap.
26
+ const isMobile = isMobileLayout(useAdaptiveLayout().layoutType);
27
+
28
+ const wrapperAttributes = {
29
+ className: styles.wrapper,
30
+ 'data-accordion': isAccordion,
31
+ 'data-mobile': isMobile && !isAccordion,
32
+ };
33
+
34
+ const gridProps = {
35
+ quotas,
36
+ loading,
37
+ disableSorting,
38
+ isAccordion,
39
+ };
40
+
41
+ if (error) {
42
+ return (
43
+ <div {...wrapperAttributes}>
44
+ <QuotaError onRefresh={onRefresh} />
45
+ </div>
46
+ );
47
+ }
48
+
49
+ return (
50
+ <Scroll {...wrapperAttributes} size='s'>
51
+ <Grid {...gridProps} />
52
+ </Scroll>
53
+ );
54
+ }
@@ -0,0 +1,47 @@
1
+ import { isMobileLayout, useAdaptiveLayout } from '@cloud-ru/ds-adaptive';
2
+
3
+ import { QuotaWidgetCard } from '../../../../components/QuotaWidgetCard';
4
+ import { TEST_IDS } from '../../../../constants';
5
+ import { QuotaItem, QuotaWidgetPropsBase } from '../../../../types';
6
+ import { getPercent } from '../../../../utils/getPercent';
7
+ import { QuotaWidgetCardsSkeleton } from './components/QuotaWidgetCardsSkeleton';
8
+ import styles from './styles.module.scss';
9
+
10
+ function sortQuotas(quotas: QuotaItem[]): QuotaItem[] {
11
+ return [...quotas].sort((first, second) => {
12
+ const percentFirst = getPercent(first);
13
+ const percentSecond = getPercent(second);
14
+
15
+ if (percentSecond !== percentFirst) {
16
+ return percentSecond - percentFirst;
17
+ }
18
+
19
+ return first.name.localeCompare(second.name);
20
+ });
21
+ }
22
+
23
+ type GridProps = Pick<QuotaWidgetPropsBase, 'quotas' | 'loading' | 'disableSorting'> & {
24
+ isAccordion?: boolean;
25
+ };
26
+
27
+ export function Grid({ quotas, loading, disableSorting = false, isAccordion = false }: GridProps) {
28
+ const sortedQuotas = disableSorting ? quotas : sortQuotas(quotas);
29
+
30
+ // На мобилке — строго одна колонка на всю ширину (BottomSheet-раскладка узкая, 2 колонки не влезают).
31
+ const { layoutType } = useAdaptiveLayout();
32
+ const isSingleColumn = (quotas.length <= 1 && !loading) || isAccordion || isMobileLayout(layoutType);
33
+
34
+ return (
35
+ <div className={styles.grid} data-single={isSingleColumn}>
36
+ <QuotaWidgetCardsSkeleton loading={loading}>
37
+ {sortedQuotas.map(quota => (
38
+ <QuotaWidgetCard
39
+ key={quota.name}
40
+ quota={quota}
41
+ data-test-id={`${TEST_IDS.quotaWidgetCard.root}--${quota.name}`}
42
+ />
43
+ ))}
44
+ </QuotaWidgetCardsSkeleton>
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,24 @@
1
+ import { SkeletonText } from '@cloud-ru/ds-skeleton';
2
+ import { PropsWithChildren } from 'react';
3
+
4
+ import styles from './styles.module.scss';
5
+
6
+ type QuotaWidgetCardsSkeletonProps = PropsWithChildren<{
7
+ loading: boolean;
8
+ }>;
9
+
10
+ const SKELETON_COUNT = 6;
11
+
12
+ export function QuotaWidgetCardsSkeleton({ loading, children }: QuotaWidgetCardsSkeletonProps) {
13
+ if (loading) {
14
+ return [...Array(SKELETON_COUNT)].map((_, index) => (
15
+ <div className={styles.card} key={index}>
16
+ <SkeletonText loading lines={1} variant='body' size='m' />
17
+ <SkeletonText loading lines={1} variant='body' size='m' />
18
+ <SkeletonText loading lines={1} variant='body' size='l' />
19
+ </div>
20
+ ));
21
+ }
22
+
23
+ return children;
24
+ }
@@ -0,0 +1,15 @@
1
+ @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
+ @use '@cloud-ru/ds-figma-variables/build/scss/components/card.module' as card;
3
+
4
+ .card {
5
+ display: flex;
6
+ flex: 1;
7
+ flex-direction: column;
8
+ row-gap: base.$sn-primitive-dimension-4;
9
+
10
+ min-width: 0;
11
+ padding: base.$sn-primitive-dimension-8;
12
+ border-radius: card.$sn-card-anatomy-radius-s-container-borderRadius;
13
+
14
+ background-color: base.$sn-theme-color-neutral-decorTransparent;
15
+ }
@@ -0,0 +1 @@
1
+ export * from './Grid';
@@ -0,0 +1,12 @@
1
+ @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
+
3
+ .grid {
4
+ display: grid;
5
+ grid-template-columns: repeat(2, minmax(0, 1fr));
6
+ gap: base.$sn-primitive-dimension-4 base.$sn-primitive-dimension-4;
7
+ width: 100%;
8
+
9
+ &[data-single='true'] {
10
+ grid-template-columns: minmax(0, 1fr);
11
+ }
12
+ }
@@ -0,0 +1,31 @@
1
+ import { Button } from '@cloud-ru/ds-button';
2
+ import { CrossSVG, UpdateSVG } from '@cloud-ru/ds-icons/interface/system';
3
+ import { InfoBlock } from '@cloud-ru/ds-info-block';
4
+
5
+ import { quotaLocale } from '../../../../locale';
6
+
7
+ type QuotaErrorProps = {
8
+ onRefresh: () => void;
9
+ };
10
+
11
+ export function QuotaError({ onRefresh }: QuotaErrorProps) {
12
+ const { t } = quotaLocale.useTranslations();
13
+
14
+ return (
15
+ <InfoBlock
16
+ size='m'
17
+ align='vertical'
18
+ content={t('errorText')}
19
+ icon={{ icon: CrossSVG, appearance: 'neutral', background: true }}
20
+ footer={
21
+ <Button
22
+ appearance='neutral'
23
+ size='m'
24
+ label={t('errorButton')}
25
+ icon={<UpdateSVG size={24} />}
26
+ onClick={onRefresh}
27
+ />
28
+ }
29
+ />
30
+ );
31
+ }
@@ -0,0 +1 @@
1
+ export * from './QuotaError';
@@ -0,0 +1 @@
1
+ export * from './QuotaCardsGrid';
@@ -0,0 +1,16 @@
1
+ .wrapper {
2
+ width: 100%;
3
+ height: auto;
4
+ max-height: 382px;
5
+
6
+ &[data-accordion='true'] {
7
+ max-height: 400px;
8
+ }
9
+
10
+ /* Mobile (full-height BottomSheet): без фикс-cap — карточки заполняют тело листа и скроллятся им. */
11
+ &[data-mobile='true'] {
12
+ flex: 1 1 auto;
13
+ min-height: 0;
14
+ max-height: none;
15
+ }
16
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './components';
2
+ export * from './constants';
3
+ export * from './types';
4
+ export * from './utils';
5
+ export * from './locale';
@@ -0,0 +1,51 @@
1
+ import { defineLocale, defineMessages } from '@cloud-ru/ds-locale';
2
+
3
+ const QUOTA_MESSAGES = defineMessages({
4
+ 'en-GB': {
5
+ quotas: 'Quotas',
6
+ widgetTitle: {
7
+ withoutProject: 'Project quotas',
8
+ quotes: 'Project quotas «{{project}}»',
9
+ noQuotes: 'Project quotas {{project}}',
10
+ },
11
+ widgetSubtitle: 'Current quota balance',
12
+ increaseQuota: 'Increase quota',
13
+ cardAvailable: 'Available',
14
+ cardRemaining: 'Remaining',
15
+ tooltipAvailable: 'Available',
16
+ tooltipUsed: 'Used',
17
+ tooltipRemaining: 'Remaining',
18
+ tooltipExhaustedHint: {
19
+ first: 'Quota exhausted.',
20
+ second: 'To increase the quota, contact the quota or organization administrator.',
21
+ },
22
+ errorText: 'Failed to load data',
23
+ errorButton: 'Refresh',
24
+ },
25
+ 'ru-RU': {
26
+ quotas: 'Квоты',
27
+ widgetTitle: {
28
+ withoutProject: 'Квоты проекта',
29
+ quotes: 'Квоты проекта «{{project}}»',
30
+ noQuotes: 'Квоты проекта {{project}}',
31
+ },
32
+ widgetSubtitle: 'Остаток по квотам на текущий момент',
33
+ increaseQuota: 'Увеличить квоту',
34
+ cardAvailable: 'Доступно',
35
+ cardRemaining: 'Остаток',
36
+ tooltipAvailable: 'Доступно',
37
+ tooltipUsed: 'Использовано',
38
+ tooltipRemaining: 'Остаток',
39
+ tooltipExhaustedHint: {
40
+ first: 'Квота исчерпана.',
41
+ second: 'Чтобы увеличить квоту, обратитесь к администратору квот или организации.',
42
+ },
43
+ errorText: 'Не удалось загрузить данные',
44
+ errorButton: 'Обновить',
45
+ },
46
+ });
47
+
48
+ export type QuotaMessages = (typeof QUOTA_MESSAGES)['en-GB'];
49
+
50
+ /** locale компонента Quota: `quotaLocale.useTranslations()` в коде, `quotaLocale.extend(...)` в сервисе. */
51
+ export const quotaLocale = defineLocale('@cloud-ru/ds-uikit-product-quota', QUOTA_MESSAGES);
package/src/types.ts ADDED
@@ -0,0 +1,35 @@
1
+ export type QuotaItem = {
2
+ /** Название квоты */
3
+ name: string;
4
+ /** Лимит квоты */
5
+ limit: number;
6
+ /** Потребление квоты */
7
+ usage: number;
8
+ /** Остаток квоты */
9
+ remains: number;
10
+ /** Единица измерения квоты */
11
+ unitDisplayName: string;
12
+ };
13
+
14
+ export type QuotaWidgetPropsBase = {
15
+ /** Список квот для отображения */
16
+ quotas: QuotaItem[];
17
+ /** Флаг отключения сортировки квот */
18
+ disableSorting?: boolean;
19
+ /** Флаг загрузки квот */
20
+ loading: boolean;
21
+ /** Флаг ошибки при загрузке квот */
22
+ error: boolean;
23
+ /** Колбек на обновление списка квот при ошибке */
24
+ onRefresh: () => void;
25
+ /** Название проекта, по которому отображаются квоты */
26
+ projectName: string;
27
+ /** Флаг наличия прав на редактирование квот */
28
+ canEditQuota: boolean;
29
+ /** Флаг скрытия кнопки увеличения квоты */
30
+ hideIncreaseQuotaButton?: boolean;
31
+ /** Колбек нажатия на кнопку увеличения квот */
32
+ onIncreaseQuotaClick?: () => void;
33
+ /** Колбек открытия виджета квот */
34
+ onWidgetOpen?: () => void;
35
+ };
@@ -0,0 +1,3 @@
1
+ import { QuotaItem } from '../types';
2
+
3
+ export const checkIsExceeded = (quota: QuotaItem) => quota.remains <= 0;
@@ -0,0 +1,3 @@
1
+ export function formatNumber(value: number) {
2
+ return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
3
+ }
@@ -0,0 +1,9 @@
1
+ import { QuotaItem } from '../types';
2
+
3
+ export function getPercent(quota: QuotaItem): number {
4
+ if (quota.limit === 0) {
5
+ return 100;
6
+ }
7
+
8
+ return (quota.usage / quota.limit) * 100;
9
+ }
@@ -0,0 +1,3 @@
1
+ export * from './checkIsExceeded';
2
+ export * from './formatNumber';
3
+ export * from './getPercent';