@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.
- package/CHANGELOG.md +833 -0
- package/LICENSE +201 -0
- package/README.md +8 -0
- package/package.json +60 -0
- package/src/components/ContentBlock/ContentBlock.tsx +39 -0
- package/src/components/ContentBlock/index.ts +1 -0
- package/src/components/ContentBlock/styles.module.scss +14 -0
- package/src/components/PriceSummary/PriceSummary.tsx +97 -0
- package/src/components/PriceSummary/components/DiscountBlock/DiscountBlock.tsx +42 -0
- package/src/components/PriceSummary/components/DiscountBlock/index.ts +1 -0
- package/src/components/PriceSummary/components/DiscountBlock/styles.module.scss +17 -0
- package/src/components/PriceSummary/components/DiscountPercentCell/DiscountPercentCell.tsx +37 -0
- package/src/components/PriceSummary/components/DiscountPercentCell/index.ts +1 -0
- package/src/components/PriceSummary/components/DiscountPercentCell/styles.module.scss +5 -0
- package/src/components/PriceSummary/components/Divider/Divider.tsx +5 -0
- package/src/components/PriceSummary/components/Divider/index.ts +1 -0
- package/src/components/PriceSummary/components/Divider/styles.module.scss +8 -0
- package/src/components/PriceSummary/components/HeaderBlock/HeaderBlock.tsx +35 -0
- package/src/components/PriceSummary/components/HeaderBlock/index.ts +1 -0
- package/src/components/PriceSummary/components/HeaderBlock/styles.module.scss +16 -0
- package/src/components/PriceSummary/components/InvoiceBlock/InvoiceBlock.tsx +41 -0
- package/src/components/PriceSummary/components/InvoiceBlock/index.ts +1 -0
- package/src/components/PriceSummary/components/InvoiceBlock/styles.module.scss +20 -0
- package/src/components/PriceSummary/components/InvoiceDetailsBlock/InvoiceDetailsBlock.tsx +48 -0
- package/src/components/PriceSummary/components/InvoiceDetailsBlock/index.ts +1 -0
- package/src/components/PriceSummary/components/InvoiceDetailsBlock/styles.module.scss +18 -0
- package/src/components/PriceSummary/components/InvoiceItemBlock/InvoiceItemBlock.tsx +62 -0
- package/src/components/PriceSummary/components/InvoiceItemBlock/index.ts +1 -0
- package/src/components/PriceSummary/components/InvoiceItemBlock/styles.module.scss +38 -0
- package/src/components/PriceSummary/components/InvoiceItemLabelCell/InvoiceItemLabelCell.tsx +39 -0
- package/src/components/PriceSummary/components/InvoiceItemLabelCell/index.ts +1 -0
- package/src/components/PriceSummary/components/InvoiceItemLabelCell/styles.module.scss +14 -0
- package/src/components/PriceSummary/components/PeriodDropdown/PeriodDropdown.tsx +48 -0
- package/src/components/PriceSummary/components/PeriodDropdown/index.ts +1 -0
- package/src/components/PriceSummary/components/PeriodDropdown/styles.module.scss +18 -0
- package/src/components/PriceSummary/components/TotalValueBlock/TotalValueBlock.tsx +104 -0
- package/src/components/PriceSummary/components/TotalValueBlock/index.ts +1 -0
- package/src/components/PriceSummary/components/TotalValueBlock/styles.module.scss +63 -0
- package/src/components/PriceSummary/index.ts +1 -0
- package/src/components/PriceSummary/styles.module.scss +14 -0
- package/src/components/PriceSummarySmall/PriceSummarySmall.tsx +53 -0
- package/src/components/PriceSummarySmall/index.ts +1 -0
- package/src/components/PriceSummarySmall/styles.module.scss +25 -0
- package/src/components/index.ts +2 -0
- package/src/helpers/formatters.ts +12 -0
- package/src/helpers/index.ts +1 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/usePeriodFormat.ts +25 -0
- package/src/hooks/usePriceTotalValueFormatter.ts +14 -0
- package/src/index.ts +3 -0
- 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,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,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,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