@cloud-ru/uikit-product-price-summary 0.5.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 (51) hide show
  1. package/CHANGELOG.md +833 -0
  2. package/LICENSE +201 -0
  3. package/README.md +8 -0
  4. package/package.json +60 -0
  5. package/src/components/ContentBlock/ContentBlock.tsx +39 -0
  6. package/src/components/ContentBlock/index.ts +1 -0
  7. package/src/components/ContentBlock/styles.module.scss +14 -0
  8. package/src/components/PriceSummary/PriceSummary.tsx +97 -0
  9. package/src/components/PriceSummary/components/DiscountBlock/DiscountBlock.tsx +42 -0
  10. package/src/components/PriceSummary/components/DiscountBlock/index.ts +1 -0
  11. package/src/components/PriceSummary/components/DiscountBlock/styles.module.scss +17 -0
  12. package/src/components/PriceSummary/components/DiscountPercentCell/DiscountPercentCell.tsx +37 -0
  13. package/src/components/PriceSummary/components/DiscountPercentCell/index.ts +1 -0
  14. package/src/components/PriceSummary/components/DiscountPercentCell/styles.module.scss +5 -0
  15. package/src/components/PriceSummary/components/Divider/Divider.tsx +5 -0
  16. package/src/components/PriceSummary/components/Divider/index.ts +1 -0
  17. package/src/components/PriceSummary/components/Divider/styles.module.scss +8 -0
  18. package/src/components/PriceSummary/components/HeaderBlock/HeaderBlock.tsx +35 -0
  19. package/src/components/PriceSummary/components/HeaderBlock/index.ts +1 -0
  20. package/src/components/PriceSummary/components/HeaderBlock/styles.module.scss +16 -0
  21. package/src/components/PriceSummary/components/InvoiceBlock/InvoiceBlock.tsx +41 -0
  22. package/src/components/PriceSummary/components/InvoiceBlock/index.ts +1 -0
  23. package/src/components/PriceSummary/components/InvoiceBlock/styles.module.scss +20 -0
  24. package/src/components/PriceSummary/components/InvoiceDetailsBlock/InvoiceDetailsBlock.tsx +48 -0
  25. package/src/components/PriceSummary/components/InvoiceDetailsBlock/index.ts +1 -0
  26. package/src/components/PriceSummary/components/InvoiceDetailsBlock/styles.module.scss +18 -0
  27. package/src/components/PriceSummary/components/InvoiceItemBlock/InvoiceItemBlock.tsx +62 -0
  28. package/src/components/PriceSummary/components/InvoiceItemBlock/index.ts +1 -0
  29. package/src/components/PriceSummary/components/InvoiceItemBlock/styles.module.scss +38 -0
  30. package/src/components/PriceSummary/components/InvoiceItemLabelCell/InvoiceItemLabelCell.tsx +39 -0
  31. package/src/components/PriceSummary/components/InvoiceItemLabelCell/index.ts +1 -0
  32. package/src/components/PriceSummary/components/InvoiceItemLabelCell/styles.module.scss +14 -0
  33. package/src/components/PriceSummary/components/PeriodDropdown/PeriodDropdown.tsx +48 -0
  34. package/src/components/PriceSummary/components/PeriodDropdown/index.ts +1 -0
  35. package/src/components/PriceSummary/components/PeriodDropdown/styles.module.scss +18 -0
  36. package/src/components/PriceSummary/components/TotalValueBlock/TotalValueBlock.tsx +104 -0
  37. package/src/components/PriceSummary/components/TotalValueBlock/index.ts +1 -0
  38. package/src/components/PriceSummary/components/TotalValueBlock/styles.module.scss +63 -0
  39. package/src/components/PriceSummary/index.ts +1 -0
  40. package/src/components/PriceSummary/styles.module.scss +14 -0
  41. package/src/components/PriceSummarySmall/PriceSummarySmall.tsx +53 -0
  42. package/src/components/PriceSummarySmall/index.ts +1 -0
  43. package/src/components/PriceSummarySmall/styles.module.scss +25 -0
  44. package/src/components/index.ts +2 -0
  45. package/src/helpers/formatters.ts +12 -0
  46. package/src/helpers/index.ts +1 -0
  47. package/src/hooks/index.ts +2 -0
  48. package/src/hooks/usePeriodFormat.ts +25 -0
  49. package/src/hooks/usePriceTotalValueFormatter.ts +14 -0
  50. package/src/index.ts +3 -0
  51. package/src/types/index.ts +49 -0
@@ -0,0 +1,20 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ .accordion {
4
+ --space-accordion-collapse-block-secondary-horizontal-padding: 0;
5
+ --space-accordion-collapse-block-secondary-vertical-padding: #{styles-theme-variables.$dimension-1m};
6
+ --space-accordion-collapse-block-secondary-gap: #{styles-theme-variables.$dimension-1m};
7
+ --border-width-accordion-collapse-block-secondary: 0;
8
+ --accordion-header-padding-vertical: 0;
9
+ }
10
+
11
+ .accordionHeaderTitle {
12
+ color: styles-theme-variables.$sys-neutral-text-support;
13
+ padding: 2px 0;
14
+ }
15
+
16
+ .accordionContent {
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: styles-theme-variables.$dimension-1m;
20
+ }
@@ -0,0 +1,48 @@
1
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
2
+ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
3
+ import { Typography } from '@snack-uikit/typography';
4
+
5
+ import { formatCurrency, formatQuantity } from '../../../../helpers';
6
+ import { InvoiceDetails } from '../../../../types';
7
+ import { Divider } from '../Divider';
8
+ import { InvoiceItemBlock } from '../InvoiceItemBlock';
9
+ import styles from './styles.module.scss';
10
+
11
+ export type InvoiceDetailsBlockProps = WithLayoutType<{
12
+ invoice: InvoiceDetails;
13
+ }>;
14
+
15
+ export function InvoiceDetailsBlock({ invoice, layoutType }: InvoiceDetailsBlockProps) {
16
+ const { t } = useLocale('PriceSummary');
17
+
18
+ return (
19
+ <div className={styles.main}>
20
+ {invoice.title && (
21
+ <>
22
+ <div className={styles.header}>
23
+ <Typography.SansLabelM>{invoice.title}</Typography.SansLabelM>
24
+ {invoice.quantity && <Typography.SansLabelM>{formatQuantity(invoice.quantity)}</Typography.SansLabelM>}
25
+ </div>
26
+
27
+ <Divider />
28
+ </>
29
+ )}
30
+
31
+ {invoice.items.map((item, index) => (
32
+ <InvoiceItemBlock key={index} item={item} index={index} layoutType={layoutType} />
33
+ ))}
34
+
35
+ {invoice.price !== undefined && (
36
+ <>
37
+ <Divider />
38
+
39
+ <div className={styles.footer}>
40
+ <Typography.SansLabelM>
41
+ {t('price')}: {formatCurrency(invoice.price)}
42
+ </Typography.SansLabelM>
43
+ </div>
44
+ </>
45
+ )}
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1 @@
1
+ export * from './InvoiceDetailsBlock';
@@ -0,0 +1,18 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ .main {
4
+ border-radius: styles-theme-variables.$dimension-050m;
5
+ padding: styles-theme-variables.$dimension-050m styles-theme-variables.$dimension-1m;
6
+ border: 1px dashed styles-theme-variables.$sys-neutral-decor-default;
7
+ }
8
+
9
+ .header {
10
+ display: flex;
11
+ justify-content: space-between;
12
+ }
13
+
14
+ .footer {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: flex-end;
18
+ }
@@ -0,0 +1,62 @@
1
+ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
2
+ import { Typography } from '@snack-uikit/typography';
3
+
4
+ import { formatCurrency } from '../../../../helpers';
5
+ import { InvoiceItem } from '../../../../types';
6
+ import { DiscountPercentCell } from '../DiscountPercentCell';
7
+ import { Divider } from '../Divider';
8
+ import { InvoiceItemLabelCell } from '../InvoiceItemLabelCell';
9
+ import styles from './styles.module.scss';
10
+
11
+ export type InvoiceItemBlockProps = WithLayoutType<{
12
+ item: InvoiceItem;
13
+ index: number;
14
+ }>;
15
+
16
+ export function InvoiceItemBlock({ item, index, layoutType }: InvoiceItemBlockProps) {
17
+ const isEven = (index + 1) % 2 === 0;
18
+
19
+ const isSecondary = item.primary === undefined ? isEven : !item.primary;
20
+
21
+ const getPriceItem = () => {
22
+ if (item.hidePrice === false) {
23
+ return item.price !== undefined ? formatCurrency(item.price) : 'n/a';
24
+ }
25
+
26
+ return item.price !== undefined ? formatCurrency(item.price) : undefined;
27
+ };
28
+
29
+ return (
30
+ <>
31
+ {item.topDivider && <Divider />}
32
+
33
+ <div className={styles.itemGrid} data-discount={Boolean(item.discount)}>
34
+ {'label' in item && item.label !== undefined && (
35
+ <>
36
+ <div className={styles.labelCell} data-secondary={isSecondary}>
37
+ <InvoiceItemLabelCell item={item} layoutType={layoutType} />
38
+ </div>
39
+
40
+ <Typography.SansBodyS tag='div' className={styles.priceCell} data-secondary={isSecondary}>
41
+ {getPriceItem()}
42
+ </Typography.SansBodyS>
43
+ </>
44
+ )}
45
+
46
+ {item.discount && (
47
+ <>
48
+ <div className={styles.percentCell} data-secondary={isSecondary}>
49
+ <DiscountPercentCell discount={item.discount} layoutType={layoutType} />
50
+ </div>
51
+
52
+ <Typography.SansBodyS tag='div' className={styles.discountCell} data-secondary={isSecondary}>
53
+ {formatCurrency(-Math.abs(item.discount.value))}
54
+ </Typography.SansBodyS>
55
+ </>
56
+ )}
57
+ </div>
58
+
59
+ {item.bottomDivider && <Divider />}
60
+ </>
61
+ );
62
+ }
@@ -0,0 +1 @@
1
+ export * from './InvoiceItemBlock';
@@ -0,0 +1,38 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ .itemGrid {
4
+ display: grid;
5
+ grid-template-columns: 1fr auto;
6
+ column-gap: styles-theme-variables.$dimension-1m;
7
+
8
+ &[data-discount='true'] {
9
+ margin-bottom: styles-theme-variables.$dimension-050m;
10
+ }
11
+ }
12
+
13
+ .labelCell {
14
+ display: flex;
15
+ overflow: hidden;
16
+ gap: styles-theme-variables.$dimension-050m;
17
+ }
18
+
19
+ .priceCell {
20
+ justify-self: end;
21
+ }
22
+
23
+ .labelCell,
24
+ .priceCell,
25
+ .percentCell {
26
+ &[data-secondary='true'] {
27
+ color: styles-theme-variables.$sys-neutral-text-light;
28
+ }
29
+ }
30
+
31
+ .discountCell {
32
+ justify-self: end;
33
+ color: styles-theme-variables.$sys-red-accent-default;
34
+
35
+ &[data-secondary='true'] {
36
+ color: styles-theme-variables.$sys-red-text-disabled;
37
+ }
38
+ }
@@ -0,0 +1,39 @@
1
+ import { AdaptiveQuestionTooltip } from '@cloud-ru/uikit-product-mobile-tooltip';
2
+ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
3
+ import { TruncateString } from '@snack-uikit/truncate-string';
4
+ import { Typography } from '@snack-uikit/typography';
5
+
6
+ import { formatQuantity } from '../../../../helpers';
7
+ import { InvoiceItem } from '../../../../types';
8
+ import styles from './styles.module.scss';
9
+
10
+ export type InvoiceItemLabelCellProps = WithLayoutType<{
11
+ item: InvoiceItem;
12
+ }>;
13
+
14
+ export function InvoiceItemLabelCell({ item, layoutType }: InvoiceItemLabelCellProps) {
15
+ return (
16
+ 'label' in item &&
17
+ item.label !== undefined && (
18
+ <>
19
+ <div className={styles.labelCell}>
20
+ <Typography.SansBodyS className={styles.label}>
21
+ {item.labelMaxLines ? <TruncateString text={item.label} maxLines={item.labelMaxLines} /> : item.label}
22
+ </Typography.SansBodyS>
23
+
24
+ {item.labelTooltip && (
25
+ <AdaptiveQuestionTooltip
26
+ layoutType={layoutType}
27
+ tip={item.labelTooltip}
28
+ trigger={layoutType === 'mobile' ? 'click' : 'hover'}
29
+ />
30
+ )}
31
+ </div>
32
+
33
+ {item.quantity && (
34
+ <Typography.SansBodyS className={styles.quantity}>{formatQuantity(item.quantity)}</Typography.SansBodyS>
35
+ )}
36
+ </>
37
+ )
38
+ );
39
+ }
@@ -0,0 +1 @@
1
+ export * from './InvoiceItemLabelCell';
@@ -0,0 +1,14 @@
1
+ .labelCell {
2
+ display: flex;
3
+ gap: 2px;
4
+ }
5
+
6
+ .label {
7
+ flex: 0 1 auto;
8
+ min-width: 0;
9
+ }
10
+
11
+ .quantity {
12
+ flex: 0 0 auto;
13
+ white-space: nowrap;
14
+ }
@@ -0,0 +1,48 @@
1
+ import { ButtonDropdown } from '@cloud-ru/uikit-product-button-predefined';
2
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
3
+ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
4
+ import { Typography } from '@snack-uikit/typography';
5
+
6
+ import { usePeriodFormat } from '../../../../hooks';
7
+ import { PricePeriod } from '../../../../types';
8
+ import styles from './styles.module.scss';
9
+
10
+ export type PeriodDropdownProps = WithLayoutType<{
11
+ period: PricePeriod;
12
+ periodOptions: PricePeriod[];
13
+ onPeriodChanged?: (period: PricePeriod) => void;
14
+ }>;
15
+
16
+ export function PeriodDropdown({ period, onPeriodChanged = () => {}, periodOptions, layoutType }: PeriodDropdownProps) {
17
+ const { t } = useLocale('PriceSummary');
18
+ const formatPeriod = usePeriodFormat();
19
+
20
+ const actions = periodOptions
21
+ .filter(item => item !== period)
22
+ .map(item => ({
23
+ content: { option: formatPeriod(item) },
24
+ onClick: () => onPeriodChanged(item),
25
+ }));
26
+
27
+ return (
28
+ <div className={styles.period} data-single={actions.length === 0 ? true : undefined}>
29
+ <Typography.SansBodyM>{t('total')}</Typography.SansBodyM>
30
+
31
+ {actions.length === 0 && (
32
+ <div className={styles.single}>
33
+ <Typography.SansBodyM>{formatPeriod(period)}</Typography.SansBodyM>
34
+ </div>
35
+ )}
36
+
37
+ {actions.length > 0 && (
38
+ <ButtonDropdown
39
+ size='s'
40
+ label={formatPeriod(period)}
41
+ items={actions}
42
+ closeDroplistOnItemClick
43
+ layoutType={layoutType}
44
+ />
45
+ )}
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1 @@
1
+ export * from './PeriodDropdown';
@@ -0,0 +1,18 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ .period {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: styles-theme-variables.$dimension-1m;
7
+
8
+ &[data-single] {
9
+ gap: styles-theme-variables.$dimension-050m;
10
+ }
11
+ }
12
+
13
+ .single {
14
+ min-height: styles-theme-variables.$size-button-s;
15
+ display: flex;
16
+ align-items: center;
17
+ color: styles-theme-variables.$sys-neutral-text-light;
18
+ }
@@ -0,0 +1,104 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ import { InfoFilledSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { AlarmFilledSVG, CrossFilledSVG, QuestionSVG } from '@snack-uikit/icons';
6
+ import { Link } from '@snack-uikit/link';
7
+ import { Tooltip } from '@snack-uikit/tooltip';
8
+ import { Typography } from '@snack-uikit/typography';
9
+ import { ValueOf } from '@snack-uikit/utils';
10
+
11
+ import { formatCurrency } from '../../../../helpers';
12
+ import { TotalSumType } from '../../../../types';
13
+ import styles from './styles.module.scss';
14
+
15
+ export const APPEARANCE_STATE = {
16
+ Default: 'default',
17
+ UserError: 'userError',
18
+ SystemError: 'systemError',
19
+ Warning: 'warning',
20
+ } as const;
21
+
22
+ export type AppearanceState = ValueOf<typeof APPEARANCE_STATE>;
23
+
24
+ export type TotalValueBlockProps = {
25
+ value?: number;
26
+ totalSumType?: TotalSumType;
27
+ hint?: string;
28
+ hintAppearance?: AppearanceState;
29
+ showHintTooltip?: boolean;
30
+ hintTooltipText?: ReactNode;
31
+ hintLink?: {
32
+ href?: string;
33
+ text: string;
34
+ };
35
+ showHintLink?: boolean;
36
+ };
37
+
38
+ function getAppearanceIcon(appearance: string) {
39
+ let Component: typeof InfoFilledSVG;
40
+
41
+ switch (appearance) {
42
+ case APPEARANCE_STATE.Warning:
43
+ Component = AlarmFilledSVG;
44
+ break;
45
+ case APPEARANCE_STATE.UserError:
46
+ Component = CrossFilledSVG;
47
+ break;
48
+ case APPEARANCE_STATE.SystemError:
49
+ Component = QuestionSVG;
50
+ break;
51
+ case APPEARANCE_STATE.Default:
52
+ default:
53
+ Component = InfoFilledSVG;
54
+ break;
55
+ }
56
+
57
+ return <Component size={16} data-appearance={appearance} className={styles.hintIcon} />;
58
+ }
59
+
60
+ export function TotalValueBlock({
61
+ value,
62
+ totalSumType = 'equal',
63
+ hint,
64
+ hintAppearance = APPEARANCE_STATE.Default,
65
+ showHintTooltip,
66
+ hintTooltipText,
67
+ hintLink,
68
+ showHintLink,
69
+ }: TotalValueBlockProps) {
70
+ const { t } = useLocale('PriceSummary');
71
+
72
+ const totalSumPrefix = totalSumType === 'from' ? `${t('totalSumFromPrefix')} ` : '';
73
+
74
+ return (
75
+ <div className={styles.content} data-appearance={hintAppearance}>
76
+ <Typography.LightHeadlineS>
77
+ {value !== undefined ? `${totalSumPrefix}${formatCurrency(Number(value))}` : 'N/A'}
78
+ </Typography.LightHeadlineS>
79
+
80
+ <Tooltip
81
+ open={showHintTooltip && hintTooltipText ? undefined : false}
82
+ tip={hintTooltipText}
83
+ placement='left-start'
84
+ >
85
+ {hint && (
86
+ <div className={styles.hint} data-appearance={hintAppearance}>
87
+ {getAppearanceIcon(hintAppearance)}
88
+ <Typography.SansBodyS>{hint}</Typography.SansBodyS>
89
+ </div>
90
+ )}
91
+ </Tooltip>
92
+ {showHintLink && (
93
+ <Link
94
+ textMode='accent'
95
+ appearance='neutral'
96
+ size='s'
97
+ href={hintLink?.href}
98
+ text={hintLink?.text}
99
+ className={styles.link}
100
+ />
101
+ )}
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1 @@
1
+ export * from './TotalValueBlock';
@@ -0,0 +1,63 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ .content {
4
+ display: flex;
5
+ flex-direction: column;
6
+ text-align: start;
7
+
8
+ &[data-appearance='userError'] {
9
+ color: styles-theme-variables.$sys-red-text-main;
10
+ }
11
+
12
+ &[data-appearance='warning'] {
13
+ color: styles-theme-variables.$sys-yellow-text-main;
14
+ }
15
+
16
+ &[data-appearance='systemError'] {
17
+ color: styles-theme-variables.$sys-neutral-text-disabled;
18
+ }
19
+ }
20
+
21
+ .hint {
22
+ display: flex;
23
+ align-items: center;
24
+ gap: styles-theme-variables.$dimension-025m;
25
+ color: styles-theme-variables.$sys-neutral-text-light;
26
+
27
+ &[data-appearance='userError'] {
28
+ color: styles-theme-variables.$sys-red-text-main;
29
+ }
30
+
31
+ &[data-appearance='warning'] {
32
+ color: styles-theme-variables.$sys-yellow-text-main;
33
+ }
34
+
35
+ &[data-appearance='systemError'] {
36
+ color: styles-theme-variables.$sys-neutral-text-disabled;
37
+ }
38
+ }
39
+
40
+ .hintIcon {
41
+ flex-shrink: 0;
42
+ box-sizing: content-box;
43
+
44
+ &[data-appearance='default'] {
45
+ color: styles-theme-variables.$sys-neutral-accent-default;
46
+ }
47
+
48
+ &[data-appearance='userError'] {
49
+ color: styles-theme-variables.$sys-red-accent-default;
50
+ }
51
+
52
+ &[data-appearance='warning'] {
53
+ color: styles-theme-variables.$sys-yellow-accent-default;
54
+ }
55
+
56
+ &[data-appearance='systemError'] {
57
+ color: styles-theme-variables.$sys-neutral-text-disabled;
58
+ }
59
+ }
60
+
61
+ .link {
62
+ margin-left: 18px;
63
+ }
@@ -0,0 +1 @@
1
+ export * from './PriceSummary';
@@ -0,0 +1,14 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ .priceSummary {
4
+ box-sizing: border-box;
5
+ width: 100%;
6
+ min-width: 280px;
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: styles-theme-variables.$dimension-050m;
10
+ padding: styles-theme-variables.$dimension-2m;
11
+ border-radius: styles-theme-variables.$dimension-1m;
12
+ background-color: styles-theme-variables.$sys-neutral-background1-level;
13
+ color: styles-theme-variables.$sys-neutral-text-main;
14
+ }
@@ -0,0 +1,53 @@
1
+ import cn from 'classnames';
2
+
3
+ import { InfoFilledSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { extractSupportProps, WithSupportProps } from '@cloud-ru/uikit-product-utils';
6
+ import { ButtonFunction } from '@snack-uikit/button';
7
+ import { LinkProps } from '@snack-uikit/link';
8
+ import { Typography } from '@snack-uikit/typography';
9
+
10
+ import { formatCurrency } from '../../helpers';
11
+ import { ContentBlock, ContentBlockProps } from '../ContentBlock';
12
+ import styles from './styles.module.scss';
13
+
14
+ export type PriceSummarySmallProps = WithSupportProps<
15
+ ContentBlockProps & {
16
+ value: number | undefined;
17
+ docsLink?: {
18
+ href?: LinkProps['href'];
19
+ text?: LinkProps['text'];
20
+ };
21
+ className?: string;
22
+ }
23
+ >;
24
+
25
+ export function PriceSummarySmall({
26
+ value = 0,
27
+ docsLink,
28
+ loading,
29
+ dataError,
30
+ onRetry,
31
+ className,
32
+ ...rest
33
+ }: PriceSummarySmallProps) {
34
+ const { t } = useLocale('PriceSummary');
35
+
36
+ return (
37
+ <div className={cn(styles.priceSummarySmall, className)} {...extractSupportProps(rest)}>
38
+ <Typography.SansBodyM>{t('total')}</Typography.SansBodyM>
39
+
40
+ <ContentBlock loading={loading} dataError={dataError} onRetry={onRetry}>
41
+ <div className={styles.value}>
42
+ <InfoFilledSVG size={16} className={styles.icon} />
43
+
44
+ <Typography.SansTitleM>{formatCurrency(value)}</Typography.SansTitleM>
45
+ </div>
46
+
47
+ {docsLink?.href && (
48
+ <ButtonFunction size='xs' label={docsLink.text || t('costLink')} href={docsLink.href} target='_blank' />
49
+ )}
50
+ </ContentBlock>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1 @@
1
+ export * from './PriceSummarySmall';
@@ -0,0 +1,25 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables';
2
+
3
+ .priceSummarySmall {
4
+ box-sizing: border-box;
5
+ width: 100%;
6
+ min-width: 170px;
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: styles-theme-variables.$dimension-050m;
10
+ padding: styles-theme-variables.$dimension-2m;
11
+ border-radius: styles-theme-variables.$dimension-1m;
12
+ background-color: styles-theme-variables.$sys-neutral-background1-level;
13
+ color: styles-theme-variables.$sys-neutral-text-main;
14
+ }
15
+
16
+ .value {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: styles-theme-variables.$dimension-050m;
20
+ }
21
+
22
+ .icon {
23
+ flex-shrink: 0;
24
+ color: styles-theme-variables.$sys-neutral-text-light;
25
+ }
@@ -0,0 +1,2 @@
1
+ export * from './PriceSummary';
2
+ export * from './PriceSummarySmall';
@@ -0,0 +1,12 @@
1
+ import { formatNumber } from '@cloud-ru/ft-formatters';
2
+
3
+ export function formatCurrency(value: number) {
4
+ return formatNumber(value.toFixed(2), {
5
+ precision: 2,
6
+ type: formatNumber.types.Currency,
7
+ });
8
+ }
9
+
10
+ export function formatQuantity(value: string | number) {
11
+ return typeof value === 'string' ? value : '×' + value;
12
+ }
@@ -0,0 +1 @@
1
+ export * from './formatters';
@@ -0,0 +1,2 @@
1
+ export * from './usePeriodFormat';
2
+ export * from './usePriceTotalValueFormatter';
@@ -0,0 +1,25 @@
1
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
2
+
3
+ import { PricePeriod } from '../types';
4
+
5
+ export function usePeriodFormat() {
6
+ const { t } = useLocale('PriceSummary');
7
+
8
+ return function formatPeriod(period: PricePeriod): string {
9
+ switch (period) {
10
+ case PricePeriod.Year:
11
+ return t('pricePeriodYear');
12
+ case PricePeriod.Month:
13
+ return t('pricePeriodMonth');
14
+ case PricePeriod.Day:
15
+ return t('pricePeriodDay');
16
+ case PricePeriod.Hour:
17
+ return t('pricePeriodHour');
18
+ case PricePeriod.Minute:
19
+ return t('pricePeriodMinute');
20
+
21
+ default:
22
+ throw new Error('not reachable');
23
+ }
24
+ };
25
+ }
@@ -0,0 +1,14 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { formatCurrency } from '../helpers';
4
+ import { PricePeriod } from '../types';
5
+ import { usePeriodFormat } from './usePeriodFormat';
6
+
7
+ export function usePriceTotalValueFormatter() {
8
+ const formatPeriod = usePeriodFormat();
9
+
10
+ return useCallback(
11
+ (value: number | undefined, period: PricePeriod) => `${formatCurrency(value || 0)} ${formatPeriod(period)}`,
12
+ [formatPeriod],
13
+ );
14
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './components';
2
+ export * from './hooks/usePriceTotalValueFormatter';
3
+ export * from './types';