@cloud-ru/uikit-product-calculator 0.33.3

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 (205) hide show
  1. package/CHANGELOG.md +1508 -0
  2. package/LICENSE +201 -0
  3. package/README.md +85 -0
  4. package/package.json +75 -0
  5. package/src/components/Calculator/Calculator.tsx +79 -0
  6. package/src/components/Calculator/index.ts +1 -0
  7. package/src/components/CalculatorContent/CalculatorContent.tsx +38 -0
  8. package/src/components/CalculatorContent/index.ts +1 -0
  9. package/src/components/CalculatorContent/styles.module.scss +79 -0
  10. package/src/components/Catalog/Catalog.tsx +225 -0
  11. package/src/components/Catalog/components/PrivateCardHeader.tsx +63 -0
  12. package/src/components/Catalog/components/index.ts +1 -0
  13. package/src/components/Catalog/components/styles.module.scss +35 -0
  14. package/src/components/Catalog/index.ts +1 -0
  15. package/src/components/Catalog/styles.module.scss +104 -0
  16. package/src/components/Controls/AlertControl/AlertControl.tsx +44 -0
  17. package/src/components/Controls/AlertControl/index.ts +1 -0
  18. package/src/components/Controls/ArrayControl/ArrayControl.tsx +105 -0
  19. package/src/components/Controls/ArrayControl/index.ts +1 -0
  20. package/src/components/Controls/ArrayControl/styles.module.scss +33 -0
  21. package/src/components/Controls/CarouselControl/CarouselControl.tsx +173 -0
  22. package/src/components/Controls/CarouselControl/index.ts +1 -0
  23. package/src/components/Controls/CarouselControl/styles.module.scss +32 -0
  24. package/src/components/Controls/Control/Control.tsx +205 -0
  25. package/src/components/Controls/Control/index.ts +1 -0
  26. package/src/components/Controls/ObjectControl/ObjectControl.tsx +150 -0
  27. package/src/components/Controls/ObjectControl/index.ts +1 -0
  28. package/src/components/Controls/ObjectControl/styles.module.scss +18 -0
  29. package/src/components/Controls/ObjectDecorator/ObjectDecorator.tsx +23 -0
  30. package/src/components/Controls/ObjectDecorator/index.ts +1 -0
  31. package/src/components/Controls/ObjectDecorator/styles.module.scss +15 -0
  32. package/src/components/Controls/SegmentedControl/SegmentedControl.tsx +79 -0
  33. package/src/components/Controls/SegmentedControl/SegmentedControlCard.tsx +38 -0
  34. package/src/components/Controls/SegmentedControl/index.ts +1 -0
  35. package/src/components/Controls/SegmentedControl/styles.module.scss +8 -0
  36. package/src/components/Controls/SelectControl/SelectControl.tsx +172 -0
  37. package/src/components/Controls/SelectControl/index.ts +1 -0
  38. package/src/components/Controls/SelectControl/styles.module.scss +5 -0
  39. package/src/components/Controls/SliderControl/SliderControl.tsx +92 -0
  40. package/src/components/Controls/SliderControl/index.ts +1 -0
  41. package/src/components/Controls/StepperControl/StepperControl.tsx +160 -0
  42. package/src/components/Controls/StepperControl/index.ts +1 -0
  43. package/src/components/Controls/StepperControl/styles.module.scss +16 -0
  44. package/src/components/Controls/TableControl/TableControl.tsx +162 -0
  45. package/src/components/Controls/TableControl/helperComponents/StepperWithAllowedValues.tsx +50 -0
  46. package/src/components/Controls/TableControl/helperComponents/index.ts +1 -0
  47. package/src/components/Controls/TableControl/helperComponents/styles.module.scss +53 -0
  48. package/src/components/Controls/TableControl/index.ts +1 -0
  49. package/src/components/Controls/TableControl/styles.module.scss +104 -0
  50. package/src/components/Controls/ToggleCard/ToggleCard.tsx +46 -0
  51. package/src/components/Controls/ToggleCard/index.ts +1 -0
  52. package/src/components/Controls/ToggleCard/styles.module.scss +11 -0
  53. package/src/components/Controls/ToggleCardsControl/ToggleCardsControl.tsx +93 -0
  54. package/src/components/Controls/ToggleCardsControl/index.ts +1 -0
  55. package/src/components/Controls/ToggleCardsControl/styles.module.scss +40 -0
  56. package/src/components/Controls/ToggleControl/ToggleControl.tsx +59 -0
  57. package/src/components/Controls/ToggleControl/components/SwitchRow.tsx +98 -0
  58. package/src/components/Controls/ToggleControl/components/index.ts +1 -0
  59. package/src/components/Controls/ToggleControl/components/styles.module.scss +110 -0
  60. package/src/components/Controls/ToggleControl/index.ts +1 -0
  61. package/src/components/Controls/ToggleObjectControl/ToggleObjectControl.tsx +111 -0
  62. package/src/components/Controls/ToggleObjectControl/index.ts +1 -0
  63. package/src/components/Controls/ToggleObjectControl/styles.module.scss +13 -0
  64. package/src/components/Controls/constants.ts +16 -0
  65. package/src/components/Controls/index.tsx +26 -0
  66. package/src/components/Controls/types.ts +181 -0
  67. package/src/components/Controls/utils.ts +49 -0
  68. package/src/components/Disclaimer/Disclaimer.tsx +33 -0
  69. package/src/components/Disclaimer/index.ts +1 -0
  70. package/src/components/Disclaimer/styles.module.scss +14 -0
  71. package/src/components/HeaderContainer/index.tsx +32 -0
  72. package/src/components/HeaderContainer/styles.module.scss +24 -0
  73. package/src/components/PriceSummary/PriceSummary.tsx +158 -0
  74. package/src/components/PriceSummary/components/PricePeriodSelect/PricePeriodSelect.tsx +81 -0
  75. package/src/components/PriceSummary/components/PricePeriodSelect/index.ts +1 -0
  76. package/src/components/PriceSummary/components/ProductCard/ProductCard.tsx +85 -0
  77. package/src/components/PriceSummary/components/ProductCard/index.ts +1 -0
  78. package/src/components/PriceSummary/components/ProductCard/styles.module.scss +7 -0
  79. package/src/components/PriceSummary/components/ProductListActions/ProductListActions.tsx +118 -0
  80. package/src/components/PriceSummary/components/ProductListActions/index.ts +1 -0
  81. package/src/components/PriceSummary/components/ProductListActions/styles.module.scss +38 -0
  82. package/src/components/PriceSummary/components/index.ts +3 -0
  83. package/src/components/PriceSummary/hooks.ts +36 -0
  84. package/src/components/PriceSummary/index.ts +1 -0
  85. package/src/components/PriceSummary/styles.module.scss +110 -0
  86. package/src/components/PriceSummary/utils/getTotalPrice.ts +19 -0
  87. package/src/components/PriceSummary/utils/index.ts +2 -0
  88. package/src/components/PriceSummary/utils/transformToProductCardData.ts +36 -0
  89. package/src/components/ProductHeadline/ProductPageHeadline.tsx +134 -0
  90. package/src/components/ProductHeadline/components/PriceCount.tsx +93 -0
  91. package/src/components/ProductHeadline/components/index.ts +1 -0
  92. package/src/components/ProductHeadline/components/styles.module.scss +52 -0
  93. package/src/components/ProductHeadline/index.ts +1 -0
  94. package/src/components/ProductHeadline/styles.module.scss +32 -0
  95. package/src/components/ProductPage/ProductPage.tsx +105 -0
  96. package/src/components/ProductPage/index.ts +1 -0
  97. package/src/components/ProductPage/styles.module.scss +13 -0
  98. package/src/components/Welcome/Welcome.tsx +138 -0
  99. package/src/components/Welcome/index.ts +1 -0
  100. package/src/components/Welcome/styles.module.scss +155 -0
  101. package/src/components/index.ts +3 -0
  102. package/src/config/config.tsx +27 -0
  103. package/src/config/index.ts +2 -0
  104. package/src/config/platforms/advanced/catalog.ts +82 -0
  105. package/src/config/platforms/advanced/constants.ts +23 -0
  106. package/src/config/platforms/advanced/index.ts +5 -0
  107. package/src/config/platforms/advanced/platform.tsx +13 -0
  108. package/src/config/platforms/advanced/product-config/AdvancedCloudBackup.ts +76 -0
  109. package/src/config/platforms/advanced/product-config/AdvancedCloudContainerEngine.tsx +260 -0
  110. package/src/config/platforms/advanced/product-config/AdvancedCloudServer.tsx +526 -0
  111. package/src/config/platforms/advanced/product-config/AdvancedDataAsYouUse.ts +159 -0
  112. package/src/config/platforms/advanced/product-config/AdvancedDataLakeInsight.ts +134 -0
  113. package/src/config/platforms/advanced/product-config/AdvancedDataWarehouseService.ts +94 -0
  114. package/src/config/platforms/advanced/product-config/AdvancedDcsMemcached.ts +69 -0
  115. package/src/config/platforms/advanced/product-config/AdvancedDcsRedis.ts +220 -0
  116. package/src/config/platforms/advanced/product-config/AdvancedDmsKafka.tsx +186 -0
  117. package/src/config/platforms/advanced/product-config/AdvancedDmsRabbitMq.ts +202 -0
  118. package/src/config/platforms/advanced/product-config/AdvancedDocumentDatabase.tsx +323 -0
  119. package/src/config/platforms/advanced/product-config/AdvancedElasticLoadBalance.tsx +252 -0
  120. package/src/config/platforms/advanced/product-config/AdvancedElasticSearch.ts +365 -0
  121. package/src/config/platforms/advanced/product-config/AdvancedFunctionGraph.tsx +65 -0
  122. package/src/config/platforms/advanced/product-config/AdvancedMapReduce.tsx +394 -0
  123. package/src/config/platforms/advanced/product-config/AdvancedMySqlDataBase.ts +417 -0
  124. package/src/config/platforms/advanced/product-config/AdvancedNetwork.ts +146 -0
  125. package/src/config/platforms/advanced/product-config/AdvancedObjectStorage.ts +232 -0
  126. package/src/config/platforms/advanced/product-config/AdvancedPostgreSqlDatabase.ts +402 -0
  127. package/src/config/platforms/advanced/product-config/AdvancedScalableFile.tsx +161 -0
  128. package/src/config/platforms/advanced/product-config/AdvancedSqlDatabase.ts +380 -0
  129. package/src/config/platforms/advanced/product-config/index.ts +21 -0
  130. package/src/config/platforms/advanced/products.ts +267 -0
  131. package/src/config/platforms/evolution/catalog.tsx +107 -0
  132. package/src/config/platforms/evolution/constants.ts +80 -0
  133. package/src/config/platforms/evolution/index.ts +5 -0
  134. package/src/config/platforms/evolution/platform.tsx +13 -0
  135. package/src/config/platforms/evolution/product-config/EvolutionArenadataDb.ts +151 -0
  136. package/src/config/platforms/evolution/product-config/EvolutionArtifactRegistry.ts +57 -0
  137. package/src/config/platforms/evolution/product-config/EvolutionBareMetal.tsx +172 -0
  138. package/src/config/platforms/evolution/product-config/EvolutionCloudServer.tsx +407 -0
  139. package/src/config/platforms/evolution/product-config/EvolutionCloudServerFreeTier.tsx +185 -0
  140. package/src/config/platforms/evolution/product-config/EvolutionCloudServerGpu.tsx +301 -0
  141. package/src/config/platforms/evolution/product-config/EvolutionContainerApps.ts +84 -0
  142. package/src/config/platforms/evolution/product-config/EvolutionContainerAppsFreeTier.ts +29 -0
  143. package/src/config/platforms/evolution/product-config/EvolutionKubernetes.ts +388 -0
  144. package/src/config/platforms/evolution/product-config/EvolutionManagedMetastore.ts +38 -0
  145. package/src/config/platforms/evolution/product-config/EvolutionManagedRedis.ts +161 -0
  146. package/src/config/platforms/evolution/product-config/EvolutionManagedSpark.ts +360 -0
  147. package/src/config/platforms/evolution/product-config/EvolutionManagedTrino.ts +53 -0
  148. package/src/config/platforms/evolution/product-config/EvolutionMlInference.tsx +703 -0
  149. package/src/config/platforms/evolution/product-config/EvolutionPostgreSql.ts +217 -0
  150. package/src/config/platforms/evolution/product-config/EvolutionPublicIp.ts +39 -0
  151. package/src/config/platforms/evolution/product-config/EvolutionSnatGateway.ts +22 -0
  152. package/src/config/platforms/evolution/product-config/EvolutionStorageS3.ts +223 -0
  153. package/src/config/platforms/evolution/product-config/EvolutionStorageS3FreeTier.ts +97 -0
  154. package/src/config/platforms/evolution/product-config/index.ts +17 -0
  155. package/src/config/platforms/evolution/products.ts +260 -0
  156. package/src/config/platforms/index.ts +3 -0
  157. package/src/config/platforms/vmware/catalog.ts +19 -0
  158. package/src/config/platforms/vmware/constants.ts +6 -0
  159. package/src/config/platforms/vmware/index.ts +5 -0
  160. package/src/config/platforms/vmware/platform.tsx +13 -0
  161. package/src/config/platforms/vmware/product-config/VmWareCloudBackup.ts +56 -0
  162. package/src/config/platforms/vmware/product-config/VmWareVirtualDataCenter.ts +309 -0
  163. package/src/config/platforms/vmware/product-config/VmWareVirtualMachinesBackup.ts +58 -0
  164. package/src/config/platforms/vmware/product-config/VmWareVirtualWorkspaces.tsx +210 -0
  165. package/src/config/platforms/vmware/product-config/index.ts +4 -0
  166. package/src/config/platforms/vmware/products.ts +63 -0
  167. package/src/config/styles.module.scss +9 -0
  168. package/src/config/utils/disk.ts +94 -0
  169. package/src/config/utils/diskPostgreSqlMySQL.ts +94 -0
  170. package/src/config/utils/eip.ts +104 -0
  171. package/src/config/utils/index.ts +52 -0
  172. package/src/config/utils/obs.ts +100 -0
  173. package/src/constants.ts +56 -0
  174. package/src/contexts/CalculatorContext.tsx +193 -0
  175. package/src/contexts/ProductContext.ts +25 -0
  176. package/src/contexts/index.ts +2 -0
  177. package/src/hooks/index.ts +6 -0
  178. package/src/hooks/useAdaptive.ts +20 -0
  179. package/src/hooks/useCatalogCardClick.ts +37 -0
  180. package/src/hooks/useProductClick.ts +14 -0
  181. package/src/hooks/useProductDelete.ts +46 -0
  182. package/src/index.ts +6 -0
  183. package/src/services/constants.ts +5 -0
  184. package/src/services/getFetcherFn.ts +57 -0
  185. package/src/services/getInitPrice.ts +64 -0
  186. package/src/services/getOnDownloadFileClick.ts +37 -0
  187. package/src/services/getOnShareClick.ts +56 -0
  188. package/src/services/index.ts +4 -0
  189. package/src/types/CalculatorType.ts +16 -0
  190. package/src/types/CatalogConfig.ts +13 -0
  191. package/src/types/Category.ts +14 -0
  192. package/src/types/FetcherFn.ts +26 -0
  193. package/src/types/FormValues.ts +4 -0
  194. package/src/types/Platform.ts +24 -0
  195. package/src/types/Price.ts +18 -0
  196. package/src/types/Product.ts +39 -0
  197. package/src/types/ProductState.ts +11 -0
  198. package/src/types/State.ts +7 -0
  199. package/src/types/index.ts +10 -0
  200. package/src/utils/filterNonEmptyArrays.ts +13 -0
  201. package/src/utils/formatNumber.ts +5 -0
  202. package/src/utils/getPrice.ts +38 -0
  203. package/src/utils/index.ts +5 -0
  204. package/src/utils/parseKeyToDataTest.ts +14 -0
  205. package/src/utils/value.ts +11 -0
@@ -0,0 +1,173 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+
3
+ import { ChevronLeftSVG, ChevronRightSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { ButtonFunction } from '@snack-uikit/button';
5
+ import { Carousel, CarouselProps } from '@snack-uikit/carousel';
6
+ import { FieldDecorator } from '@snack-uikit/fields';
7
+ import { PaginationSlider } from '@snack-uikit/pagination';
8
+ import { ToggleGroup } from '@snack-uikit/toggles';
9
+
10
+ import { useAdaptive } from '../../../hooks';
11
+ import { FormValues } from '../../../types';
12
+ import { parseKeyToDataTest } from '../../../utils';
13
+ import { CONTROL } from '../constants';
14
+ import { ToggleCard, ToggleCardItem } from '../ToggleCard';
15
+ import { BaseControlWithItems } from '../types';
16
+ import styles from './styles.module.scss';
17
+
18
+ type CarouselUiProps = Pick<
19
+ CarouselProps,
20
+ | 'transition'
21
+ | 'scrollBy'
22
+ | 'className'
23
+ | 'swipe'
24
+ | 'infiniteScroll'
25
+ | 'arrows'
26
+ | 'showItems'
27
+ | 'pagination'
28
+ | 'gap'
29
+ | 'swipeActivateLength'
30
+ >;
31
+
32
+ export type CarouselControl = {
33
+ type: typeof CONTROL.Carousel;
34
+ defaultValue?: string;
35
+ } & BaseControlWithItems<ToggleCardItem, CarouselUiProps>;
36
+
37
+ export type CarouselControlUiProps = {
38
+ value?: string;
39
+ onChange?(value: string): void;
40
+ watchedValues?: FormValues;
41
+ } & CarouselControl;
42
+
43
+ export function CarouselControlUi({
44
+ items: itemsProp,
45
+ value,
46
+ onChange,
47
+ uiProps,
48
+ decoratorProps,
49
+ watchedValues,
50
+ relateFn,
51
+ accessorKey,
52
+ }: CarouselControlUiProps) {
53
+ const { isMobile } = useAdaptive();
54
+
55
+ const {
56
+ items: relatedItems,
57
+ decoratorProps: relatedDecoratorProps,
58
+ uiProps: relatedUiProps,
59
+ } = useMemo(() => relateFn?.(watchedValues ?? {}) ?? {}, [relateFn, watchedValues]);
60
+
61
+ const items = useMemo(() => relatedItems ?? itemsProp, [itemsProp, relatedItems]);
62
+ const [page, setPage] = useState<number>(0);
63
+
64
+ const maxPage = isMobile ? items.length : items.length - 1;
65
+
66
+ const dataTestAttribute = parseKeyToDataTest('carousel', accessorKey);
67
+ const navigationDataTestAttribute = parseKeyToDataTest('carousel-navigation');
68
+
69
+ useEffect(() => {
70
+ const pageIndex = items.findIndex(item => String(item.value) === String(value));
71
+
72
+ if (pageIndex !== -1) {
73
+ const itemPage = isMobile ? pageIndex : Math.max(pageIndex - 1, 0);
74
+
75
+ const timeout = setTimeout(() => {
76
+ setPage(itemPage);
77
+ }, 0);
78
+
79
+ return () => {
80
+ clearTimeout(timeout);
81
+ };
82
+ }
83
+ }, [isMobile, items, value]);
84
+
85
+ useEffect(() => {
86
+ const pageIndex = items.findIndex(item => String(item.value) === String(value));
87
+
88
+ if (page > maxPage + 1) {
89
+ setPage(pageIndex);
90
+ }
91
+ }, [value, onChange, items, page, maxPage]);
92
+
93
+ const decPage = () => {
94
+ setPage(page => Math.max(0, page - 1));
95
+ };
96
+ const incPage = () => {
97
+ setPage(page => Math.min(page + 1, Math.max(maxPage - 1, 0)));
98
+ };
99
+
100
+ useEffect(() => {
101
+ if (!items.find(item => String(item.value) === String(value))) {
102
+ onChange?.(String(items?.[0]?.value));
103
+ setPage(0);
104
+ }
105
+ }, [value, onChange, items, relatedItems]);
106
+
107
+ const visible = relatedUiProps?.visible ?? uiProps?.visible ?? true;
108
+
109
+ if (!visible) {
110
+ return null;
111
+ }
112
+
113
+ return (
114
+ <div className={styles.carousel}>
115
+ <FieldDecorator
116
+ size='m'
117
+ caption={
118
+ maxPage > 1
119
+ ? ((
120
+ <div className={styles.controls}>
121
+ <ButtonFunction
122
+ icon={<ChevronLeftSVG />}
123
+ size='s'
124
+ onClick={decPage}
125
+ data-test-id={`${navigationDataTestAttribute}-button-left`}
126
+ />
127
+ <ButtonFunction
128
+ icon={<ChevronRightSVG />}
129
+ size='s'
130
+ onClick={incPage}
131
+ data-test-id={`${navigationDataTestAttribute}-button-right`}
132
+ />
133
+ </div>
134
+ ) as unknown as string)
135
+ : undefined
136
+ }
137
+ {...decoratorProps}
138
+ {...relatedDecoratorProps}
139
+ data-test-id={dataTestAttribute}
140
+ >
141
+ <ToggleGroup
142
+ selectionMode='single'
143
+ value={value}
144
+ onChange={value => {
145
+ value && onChange?.(value);
146
+ }}
147
+ >
148
+ <Carousel
149
+ pagination={false}
150
+ arrows={false}
151
+ {...uiProps}
152
+ scrollBy={1}
153
+ swipe
154
+ infiniteScroll={false}
155
+ showItems={isMobile ? 1.1 : 2.1}
156
+ state={{
157
+ page: page || 0,
158
+ onChange: page => setPage(page % maxPage),
159
+ }}
160
+ >
161
+ {items.map(item => (
162
+ <ToggleCard {...item} key={item.value} />
163
+ ))}
164
+ </Carousel>
165
+ </ToggleGroup>
166
+
167
+ <div className={styles.pagination}>
168
+ {maxPage > 1 && <PaginationSlider page={page + 1} total={maxPage} onChange={page => setPage(page - 1)} />}
169
+ </div>
170
+ </FieldDecorator>
171
+ </div>
172
+ );
173
+ }
@@ -0,0 +1 @@
1
+ export * from './CarouselControl';
@@ -0,0 +1,32 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as stv;
2
+
3
+ .carousel {
4
+ position: relative;
5
+ display: flex;
6
+ flex-direction: column;
7
+
8
+ gap: stv.$dimension-1m;
9
+
10
+ ul {
11
+ margin: 0;
12
+ padding: 0 0 0 stv.$dimension-2m;
13
+ list-style-type: disc;
14
+ }
15
+
16
+ li {
17
+ list-style-type: disc;
18
+ }
19
+ }
20
+
21
+ .controls {
22
+ display: flex;
23
+ flex-shrink: 0;
24
+ align-items: center;
25
+ color: stv.$sys-neutral-text-support;
26
+ }
27
+
28
+ .pagination {
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ }
@@ -0,0 +1,205 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import { useCalculatorContext, useProductContext } from '../../../contexts';
4
+ import { AnyType, FormValues, PRICE_PERIOD } from '../../../types';
5
+ import { getValue, setValue } from '../../../utils';
6
+ import { AlertControlUi } from '../AlertControl';
7
+ import { ArrayControlUi } from '../ArrayControl';
8
+ import { CarouselControlUi } from '../CarouselControl';
9
+ import { CONTROL } from '../constants';
10
+ import { ObjectControlUi } from '../ObjectControl';
11
+ import { SegmentedControlUi } from '../SegmentedControl';
12
+ import { SelectMultipleUi, SelectSingleUi } from '../SelectControl';
13
+ import { SliderControlUi } from '../SliderControl';
14
+ import { StepperControlUi } from '../StepperControl';
15
+ import { TableControlUi } from '../TableControl';
16
+ import { ToggleCardsControlUi } from '../ToggleCardsControl';
17
+ import { ToggleControlUi } from '../ToggleControl';
18
+ import { ToggleObjectControlUi } from '../ToggleObjectControl';
19
+ import { FormControl } from '../types';
20
+
21
+ type ControlProps = {
22
+ formControl: FormControl & {
23
+ accessorKey?: string;
24
+ onChangePeriod?: (period: PRICE_PERIOD, setValue: (arr: [string, AnyType][]) => void) => void;
25
+ canChangeWholePricePeriod?: boolean;
26
+ };
27
+ };
28
+
29
+ export function Control({ formControl }: ControlProps) {
30
+ const { calculatorType } = useCalculatorContext();
31
+ const { value: valueProp, onChange: onChangeProp, priceList: priceListProp } = useProductContext();
32
+ const { pricePeriod, setPricePeriod, onAnalyticsClick } = useCalculatorContext();
33
+
34
+ const accessorKey = formControl?.accessorKey ?? '';
35
+
36
+ useEffect(() => {
37
+ const setValueFn = (arr: [string, AnyType][]) => {
38
+ arr.forEach(([accessorKey, newValue]) => {
39
+ setValue(valueProp, accessorKey, newValue);
40
+ });
41
+
42
+ onChangeProp(valueProp);
43
+ };
44
+
45
+ // Если в разных продуктах есть поля с одинаковыми названиями ключа,
46
+ // то в formControl может возникнуть конфликт с определением и изменением значений этих полей.
47
+ // Попробуйте поменять название поля на другое уникальное значение.
48
+
49
+ formControl?.onChangePeriod?.(pricePeriod, setValueFn);
50
+
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ }, [pricePeriod]);
53
+
54
+ const watchedValues = Object.keys(formControl?.watchedControls ?? {})?.reduce((res, fieldName) => {
55
+ const key = formControl?.watchedControls?.[fieldName];
56
+
57
+ if (key) {
58
+ res[fieldName] = getValue(valueProp, key);
59
+ }
60
+ return res;
61
+ }, {} as FormValues);
62
+
63
+ watchedValues.calculatorType = calculatorType;
64
+
65
+ if (formControl.type === CONTROL.Object) {
66
+ return <ObjectControlUi {...formControl} watchedValues={watchedValues} />;
67
+ }
68
+
69
+ if (formControl.type === CONTROL.Array) {
70
+ return <ArrayControlUi {...formControl} />;
71
+ }
72
+
73
+ const value = getValue(valueProp, accessorKey);
74
+ const priceList = getValue(priceListProp, accessorKey);
75
+ let onChange = (uiType: string) => (newValue: AnyType) => {
76
+ if (formControl?.canChangeWholePricePeriod) {
77
+ setPricePeriod(newValue);
78
+ }
79
+ if (typeof newValue === 'object') {
80
+ onAnalyticsClick(Object.values(newValue).join(' '), `${uiType}-${accessorKey}`);
81
+ } else {
82
+ onAnalyticsClick(String(newValue), `${uiType}-${accessorKey}`);
83
+ }
84
+ setValue(valueProp, accessorKey, newValue);
85
+ onChangeProp(valueProp);
86
+ };
87
+
88
+ if (formControl?.onChangeFn) {
89
+ const setValueFn = (arr: [string, AnyType][]) => {
90
+ arr.forEach(([accessorKey, newValue]) => {
91
+ setValue(valueProp, accessorKey, newValue);
92
+ });
93
+
94
+ onChangeProp(valueProp);
95
+ };
96
+
97
+ onChange = (uiType: string) => (newValue: AnyType) => {
98
+ onAnalyticsClick(String(newValue), `${uiType}-${accessorKey}`);
99
+ formControl?.onChangeFn?.(newValue, setValueFn);
100
+ };
101
+ }
102
+
103
+ switch (formControl.type) {
104
+ case CONTROL.Table: {
105
+ return (
106
+ <TableControlUi {...formControl} value={value} onChange={onChange('table-control-ui')} priceList={priceList} />
107
+ );
108
+ }
109
+ case CONTROL.Carousel: {
110
+ return (
111
+ <CarouselControlUi
112
+ {...formControl}
113
+ value={value}
114
+ onChange={onChange('carousel-control-ui')}
115
+ watchedValues={watchedValues}
116
+ />
117
+ );
118
+ }
119
+ case CONTROL.ToggleCards: {
120
+ return (
121
+ <ToggleCardsControlUi
122
+ {...formControl}
123
+ value={value}
124
+ onChange={onChange('toggle-cards-control-ui')}
125
+ watchedValues={watchedValues}
126
+ />
127
+ );
128
+ }
129
+ case CONTROL.SelectSingle: {
130
+ return (
131
+ <SelectSingleUi
132
+ {...formControl}
133
+ value={value}
134
+ onChange={onChange('select-single-ui')}
135
+ watchedValues={watchedValues}
136
+ />
137
+ );
138
+ }
139
+ case CONTROL.SelectMultiple: {
140
+ return (
141
+ <SelectMultipleUi
142
+ {...formControl}
143
+ value={value}
144
+ onChange={onChange('select-multiple-ui')}
145
+ watchedValues={watchedValues}
146
+ />
147
+ );
148
+ }
149
+ case CONTROL.Segmented: {
150
+ return (
151
+ <SegmentedControlUi
152
+ {...formControl}
153
+ value={value}
154
+ onChange={onChange('segment-control-ui')}
155
+ watchedValues={watchedValues}
156
+ />
157
+ );
158
+ }
159
+ case CONTROL.Toggle: {
160
+ return (
161
+ <ToggleControlUi
162
+ {...formControl}
163
+ value={value}
164
+ onChange={onChange('toggle-control-ui')}
165
+ watchedValues={watchedValues}
166
+ />
167
+ );
168
+ }
169
+ case CONTROL.Stepper: {
170
+ return (
171
+ <StepperControlUi
172
+ {...formControl}
173
+ value={value}
174
+ onChange={onChange('stepper-control-ui')}
175
+ watchedValues={watchedValues}
176
+ />
177
+ );
178
+ }
179
+ case CONTROL.ToggleObject: {
180
+ return (
181
+ <ToggleObjectControlUi
182
+ {...formControl}
183
+ value={value}
184
+ onChange={onChange('toggle-object-ui')}
185
+ watchedValues={watchedValues}
186
+ />
187
+ );
188
+ }
189
+ case CONTROL.Alert: {
190
+ return <AlertControlUi {...formControl} watchedValues={watchedValues} />;
191
+ }
192
+ case CONTROL.Slider: {
193
+ return (
194
+ <SliderControlUi
195
+ {...formControl}
196
+ value={value}
197
+ onChange={onChange('slider-control-ui')}
198
+ watchedValues={watchedValues}
199
+ />
200
+ );
201
+ }
202
+ default:
203
+ throw new Error('not reachable');
204
+ }
205
+ }
@@ -0,0 +1 @@
1
+ export * from './Control';
@@ -0,0 +1,150 @@
1
+ import { ReactNode, useMemo } from 'react';
2
+
3
+ import { useCalculatorContext } from '../../../contexts';
4
+ import { AnyType, FormValues, PRICE_PERIOD } from '../../../types';
5
+ import { parseKeyToDataTest } from '../../../utils';
6
+ import { FormControl } from '..';
7
+ import { CONTROL } from '../constants';
8
+ import { Control } from '../Control';
9
+ import { ObjectDecorator } from '../ObjectDecorator';
10
+ import { BaseDecoratorProps } from '../types';
11
+ import styles from './styles.module.scss';
12
+
13
+ type RowConfig = {
14
+ controls: string[];
15
+ template?: string;
16
+ };
17
+
18
+ type FormRow = string | [string, string] | ReactNode | RowConfig;
19
+
20
+ function isRowConfig(row: FormRow): row is RowConfig {
21
+ return Boolean(typeof row === 'object' && row && 'controls' in row);
22
+ }
23
+
24
+ export type ObjectControl = {
25
+ type: typeof CONTROL.Object;
26
+ /**
27
+ * Контроллы для описания конфигурации продукта
28
+ *
29
+ * Ключи используются для ui схемы
30
+ *
31
+ * @example {
32
+ * alert: {
33
+ * type: CONTROL.Alert,
34
+ * ...someAlertProps
35
+ * },
36
+ * cpu: {
37
+ * type: CONTROL.Segmented,
38
+ * ...someSegmentedProps
39
+ * }
40
+ * }
41
+ */
42
+ controls: Record<string, FormControl>;
43
+ /**
44
+ * Построчная UI схема для отображения контроллов.
45
+ *
46
+ * Элементы - ключи объекта controls
47
+ *
48
+ * @example ['specification', ['cpu', 'ram'], ['systemDisk', 'additionalDisk'], ['instanceCount']],
49
+ */
50
+ ui: FormRow[];
51
+ /** Флаг, отрисовывать элемент или нет */
52
+ visible?: boolean;
53
+ /** Исходное значение */
54
+ defaultValue?: FormValues;
55
+ decoratorProps?: Pick<BaseDecoratorProps, 'label' | 'labelTooltip' | 'labelTooltipPlacement'>;
56
+ /**
57
+ * Отслеживаемые "более главные" поля
58
+ *
59
+ * @example { storageClass: 'storages[0].obs.storageClass' },
60
+ */
61
+ watchedControls?: Record<string, string>;
62
+ /**
63
+ * Функция для изменения параметров контролла
64
+ *
65
+ * @param watchedValues - вычисленные 'watchedControls'
66
+ *
67
+ * @example ({ storageClass }) => {
68
+ * if (storageClass !== StorageClassItem.Cold) {
69
+ * return {
70
+ * visible: false,
71
+ * };
72
+ * }
73
+ * }
74
+ */
75
+ relateFn?(watchedValues: FormValues):
76
+ | {
77
+ visible?: boolean;
78
+ }
79
+ | undefined;
80
+ onChangePeriod?: (value: PRICE_PERIOD, setValue: (arr: [string, AnyType][]) => void) => void;
81
+ canChangeWholePricePeriod?: boolean;
82
+ };
83
+
84
+ type ObjectControlUiProps = ObjectControl & { watchedValues?: FormValues };
85
+
86
+ export function ObjectControlUi({
87
+ controls,
88
+ ui,
89
+ watchedValues,
90
+ relateFn,
91
+ visible: visibleProp,
92
+ decoratorProps,
93
+ }: ObjectControlUiProps) {
94
+ const { layoutType } = useCalculatorContext();
95
+ const isMobile = layoutType === 'mobile';
96
+ const { visible: relatedVisible } = useMemo(() => relateFn?.(watchedValues ?? {}) ?? {}, [relateFn, watchedValues]);
97
+
98
+ const dataTestAttribute = parseKeyToDataTest('row');
99
+
100
+ const visible = relatedVisible ?? visibleProp ?? true;
101
+
102
+ if (!visible) {
103
+ return null;
104
+ }
105
+
106
+ return (
107
+ <>
108
+ {decoratorProps && decoratorProps.label && <ObjectDecorator {...decoratorProps} />}
109
+
110
+ {ui.map(uiRow => {
111
+ if (typeof uiRow === 'string') {
112
+ return <Control formControl={controls[uiRow]} key={uiRow} />;
113
+ }
114
+
115
+ if (Array.isArray(uiRow)) {
116
+ return (
117
+ <div
118
+ className={styles.row}
119
+ data-test-id={dataTestAttribute}
120
+ data-mobile={isMobile || undefined}
121
+ key={uiRow.join(' ')}
122
+ >
123
+ {uiRow.map(rowItem => (
124
+ <Control formControl={controls[rowItem]} key={rowItem} />
125
+ ))}
126
+ </div>
127
+ );
128
+ }
129
+
130
+ if (isRowConfig(uiRow)) {
131
+ return (
132
+ <div
133
+ className={styles.rowConfig}
134
+ key={uiRow.controls.join(' ')}
135
+ style={{
136
+ gridTemplateColumns: uiRow.template || '1fr 84px',
137
+ }}
138
+ >
139
+ {uiRow.controls.map(rowItem => (
140
+ <Control formControl={controls[rowItem]} key={rowItem} />
141
+ ))}
142
+ </div>
143
+ );
144
+ }
145
+
146
+ return <>{uiRow}</>;
147
+ })}
148
+ </>
149
+ );
150
+ }
@@ -0,0 +1 @@
1
+ export * from './ObjectControl';
@@ -0,0 +1,18 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as stv;
2
+
3
+ .row {
4
+ display: grid;
5
+ grid-template-columns: 1fr 1fr;
6
+ gap: stv.$dimension-2m;
7
+
8
+ &[data-mobile] {
9
+ grid-template-columns: 1fr;
10
+ }
11
+ }
12
+
13
+ .rowConfig {
14
+ display: grid;
15
+ grid-template-columns: 1fr 84px;
16
+ gap: stv.$dimension-1m;
17
+ width: 100%;
18
+ }
@@ -0,0 +1,23 @@
1
+ import { QuestionTooltip } from '@snack-uikit/tooltip';
2
+ import { Typography } from '@snack-uikit/typography';
3
+
4
+ import { BaseDecoratorProps } from '../types';
5
+ import styles from './styles.module.scss';
6
+
7
+ export function ObjectDecorator({
8
+ label,
9
+ labelTooltip,
10
+ labelTooltipPlacement,
11
+ }: Pick<BaseDecoratorProps, 'label' | 'labelTooltip' | 'labelTooltipPlacement'>) {
12
+ return (
13
+ <span className={styles.title}>
14
+ <Typography.SansTitleM>{label}</Typography.SansTitleM>
15
+
16
+ {labelTooltip && (
17
+ <span className={styles.tipWrapperInline}>
18
+ <QuestionTooltip placement={labelTooltipPlacement} data-pointer tip={labelTooltip} size='s' tabIndex={-1} />
19
+ </span>
20
+ )}
21
+ </span>
22
+ );
23
+ }
@@ -0,0 +1 @@
1
+ export * from './ObjectDecorator';
@@ -0,0 +1,15 @@
1
+ .tipWrapper {
2
+ display: inline-flex;
3
+ height: 20px;
4
+ align-items: center;
5
+ }
6
+
7
+ .tipWrapperInline {
8
+ display: inline-flex;
9
+ height: 20px;
10
+ padding-left: 4px;
11
+
12
+ > span {
13
+ transform: translate(0, 4px);
14
+ }
15
+ }