@cloud-ru/uikit-product-site-cards 0.11.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 +690 -0
- package/LICENSE +201 -0
- package/README.md +8 -0
- package/package.json +51 -0
- package/src/components/CardBasic/CardBasic.tsx +74 -0
- package/src/components/CardBasic/index.ts +1 -0
- package/src/components/CardBasic/styles.module.scss +45 -0
- package/src/components/CardBasic/utils.ts +14 -0
- package/src/components/CardCase/CardCase.tsx +123 -0
- package/src/components/CardCase/index.ts +1 -0
- package/src/components/CardCase/styles.module.scss +68 -0
- package/src/components/CardInfo/CardInfo.tsx +120 -0
- package/src/components/CardInfo/index.ts +1 -0
- package/src/components/CardInfo/styles.module.scss +52 -0
- package/src/components/CardInfo/utilsTypes.ts +3 -0
- package/src/components/CardList/CardList.tsx +90 -0
- package/src/components/CardList/index.ts +1 -0
- package/src/components/CardList/styles.module.scss +102 -0
- package/src/components/CardList/utils.ts +35 -0
- package/src/components/CardMarketplace/CardMarketplace.tsx +112 -0
- package/src/components/CardMarketplace/index.ts +1 -0
- package/src/components/CardMarketplace/styles.module.scss +73 -0
- package/src/components/CardMedia/CardMedia.tsx +123 -0
- package/src/components/CardMedia/index.ts +1 -0
- package/src/components/CardMedia/styles.module.scss +157 -0
- package/src/components/CardMedia/types.ts +3 -0
- package/src/components/CardMedia/utils.ts +23 -0
- package/src/components/CardPartner/CardPartner.tsx +90 -0
- package/src/components/CardPartner/index.ts +1 -0
- package/src/components/CardPartner/styles.module.scss +60 -0
- package/src/components/CardProduct/CardProduct.tsx +123 -0
- package/src/components/CardProduct/constants.ts +11 -0
- package/src/components/CardProduct/index.ts +1 -0
- package/src/components/CardProduct/styles.module.scss +73 -0
- package/src/components/index.ts +8 -0
- package/src/components/utils.ts +15 -0
- package/src/constants.ts +1 -0
- package/src/helperComponents/Icon/Icon.tsx +31 -0
- package/src/helperComponents/Icon/index.ts +1 -0
- package/src/helperComponents/Icon/styles.module.scss +49 -0
- package/src/hooks.ts +40 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { AnchorHTMLAttributes, MouseEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
import { WatchSVG } from '@cloud-ru/uikit-product-icons';
|
|
4
|
+
import { extractSupportProps, WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
|
|
5
|
+
import { Tag, TagProps } from '@snack-uikit/tag';
|
|
6
|
+
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
7
|
+
import { Typography } from '@snack-uikit/typography';
|
|
8
|
+
|
|
9
|
+
import { useCardInteractions } from '../../hooks';
|
|
10
|
+
import styles from './styles.module.scss';
|
|
11
|
+
import { TitleTag } from './types';
|
|
12
|
+
import { getTypographySize } from './utils';
|
|
13
|
+
|
|
14
|
+
export type CardMediaProps = WithSupportProps<
|
|
15
|
+
WithLayoutType<{
|
|
16
|
+
/** Заголовок карточки*/
|
|
17
|
+
title: string;
|
|
18
|
+
/** Тег заголовка */
|
|
19
|
+
titleTag: TitleTag;
|
|
20
|
+
/** Размер карточки */
|
|
21
|
+
size: 's' | 'm' | 'l';
|
|
22
|
+
/** Ссылка на картинку */
|
|
23
|
+
img: string;
|
|
24
|
+
/** Тег */
|
|
25
|
+
tag: Pick<TagProps, 'label' | 'appearance'>;
|
|
26
|
+
/** Время чтения */
|
|
27
|
+
time?: string;
|
|
28
|
+
/** Дата публикации */
|
|
29
|
+
date: string;
|
|
30
|
+
/** Ссылка */
|
|
31
|
+
href: string;
|
|
32
|
+
/** HTML-аттрибут target */
|
|
33
|
+
target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
|
|
34
|
+
/** Колбек клика*/
|
|
35
|
+
onClick?(e?: MouseEvent<HTMLDivElement | HTMLAnchorElement>): void;
|
|
36
|
+
/**
|
|
37
|
+
* Является ли деактивированным
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
}>
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
export function CardMedia({
|
|
45
|
+
title,
|
|
46
|
+
titleTag = 'h2',
|
|
47
|
+
size = 's',
|
|
48
|
+
img,
|
|
49
|
+
tag,
|
|
50
|
+
time,
|
|
51
|
+
href,
|
|
52
|
+
disabled,
|
|
53
|
+
date,
|
|
54
|
+
target = '_self',
|
|
55
|
+
onClick,
|
|
56
|
+
'data-test-id': dataTestId = 'card-media',
|
|
57
|
+
layoutType,
|
|
58
|
+
...rest
|
|
59
|
+
}: CardMediaProps) {
|
|
60
|
+
const { anchorRef, handleLinkClick, handleCardKeyDown } = useCardInteractions({
|
|
61
|
+
href,
|
|
62
|
+
disabled,
|
|
63
|
+
onClick,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
68
|
+
<div
|
|
69
|
+
{...extractSupportProps(rest)}
|
|
70
|
+
onKeyDown={handleCardKeyDown}
|
|
71
|
+
data-test-id={dataTestId}
|
|
72
|
+
data-size={size}
|
|
73
|
+
className={styles.root}
|
|
74
|
+
data-disabled={disabled || undefined}
|
|
75
|
+
data-layout-type={layoutType}
|
|
76
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
77
|
+
tabIndex={0}
|
|
78
|
+
>
|
|
79
|
+
<div className={styles.imgWrapper}>
|
|
80
|
+
<img className={styles.img} src={img} alt='card-media-img' />
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div className={styles.header}>
|
|
84
|
+
<div className={styles.left}>
|
|
85
|
+
<Tag size='xs' {...tag} />
|
|
86
|
+
|
|
87
|
+
{time && (
|
|
88
|
+
<span className={styles.time}>
|
|
89
|
+
<WatchSVG className={styles.icon} size={16} />
|
|
90
|
+
<Typography.SansBodyM className={styles.minutes}>{`${time} мин`}</Typography.SansBodyM>
|
|
91
|
+
</span>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{size !== 's' && <Typography.SansBodyM className={styles.date}>{date}</Typography.SansBodyM>}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<Typography
|
|
99
|
+
tag={titleTag}
|
|
100
|
+
family='sans'
|
|
101
|
+
purpose='title'
|
|
102
|
+
size={getTypographySize(layoutType, size)}
|
|
103
|
+
className={styles.title}
|
|
104
|
+
data-test-id={`${dataTestId}__title`}
|
|
105
|
+
>
|
|
106
|
+
{href ? (
|
|
107
|
+
<a
|
|
108
|
+
tabIndex={-1}
|
|
109
|
+
ref={anchorRef}
|
|
110
|
+
href={href}
|
|
111
|
+
target={target}
|
|
112
|
+
onClick={handleLinkClick}
|
|
113
|
+
data-test-id={`${dataTestId}__title-link`}
|
|
114
|
+
>
|
|
115
|
+
<TruncateString text={title} maxLines={4} />
|
|
116
|
+
</a>
|
|
117
|
+
) : (
|
|
118
|
+
title
|
|
119
|
+
)}
|
|
120
|
+
</Typography>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CardMedia';
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
|
|
2
|
+
|
|
3
|
+
$transition: 0.3s;
|
|
4
|
+
$scale: 1.05;
|
|
5
|
+
|
|
6
|
+
.title {
|
|
7
|
+
color: ste.$sys-neutral-text-main;
|
|
8
|
+
|
|
9
|
+
a {
|
|
10
|
+
color: ste.$sys-neutral-text-main;
|
|
11
|
+
text-decoration: none;
|
|
12
|
+
|
|
13
|
+
transition: color $transition ease-in-out;
|
|
14
|
+
|
|
15
|
+
&::before {
|
|
16
|
+
content: '';
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: 0;
|
|
19
|
+
left: 0;
|
|
20
|
+
right: 0;
|
|
21
|
+
bottom: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
color: ste.$sys-primary-accent-hovered;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.img {
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: 100%;
|
|
33
|
+
object-fit: cover;
|
|
34
|
+
object-position: center;
|
|
35
|
+
|
|
36
|
+
transition: all $transition;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.imgWrapper {
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
height: auto;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.header {
|
|
45
|
+
display: flex;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
|
|
48
|
+
color: ste.$sys-neutral-text-light;
|
|
49
|
+
|
|
50
|
+
.left {
|
|
51
|
+
display: flex;
|
|
52
|
+
gap: 12px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.icon {
|
|
56
|
+
color: ste.$sys-neutral-accent-default;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.time,
|
|
60
|
+
.date {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
|
|
64
|
+
gap: 4px;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.root {
|
|
69
|
+
position: relative;
|
|
70
|
+
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
|
|
74
|
+
gap: 16px;
|
|
75
|
+
|
|
76
|
+
&:focus-visible {
|
|
77
|
+
@include ste.outline-var(ste.$container-focused-m);
|
|
78
|
+
outline-color: ste.$sys-primary-decor-activated;
|
|
79
|
+
outline-offset: 2px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&:hover,
|
|
83
|
+
&:focus-visible {
|
|
84
|
+
.img {
|
|
85
|
+
scale: $scale;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&[data-disabled='true'] {
|
|
90
|
+
pointer-events: none;
|
|
91
|
+
|
|
92
|
+
a {
|
|
93
|
+
color: ste.$sys-neutral-text-disabled;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.img {
|
|
97
|
+
opacity: 0.5;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.title {
|
|
101
|
+
color: ste.$sys-neutral-text-disabled;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&[data-size='s'] {
|
|
106
|
+
gap: 8px;
|
|
107
|
+
|
|
108
|
+
.imgWrapper {
|
|
109
|
+
height: 138px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.left {
|
|
113
|
+
width: 100%;
|
|
114
|
+
justify-content: space-between;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&[data-size='m'] {
|
|
119
|
+
.imgWrapper {
|
|
120
|
+
aspect-ratio: 16 / 11;
|
|
121
|
+
max-height: 264px;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
&[data-size='l'] {
|
|
126
|
+
.imgWrapper {
|
|
127
|
+
max-height: 350px;
|
|
128
|
+
aspect-ratio: 16 / 7;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&[data-size='m'][data-layout-type='tablet'] {
|
|
133
|
+
.imgWrapper {
|
|
134
|
+
aspect-ratio: 164 / 113;
|
|
135
|
+
max-height: 226px;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&[data-size='m'][data-layout-type='mobile'] {
|
|
140
|
+
.imgWrapper {
|
|
141
|
+
height: 226px;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
&[data-size='l'][data-layout-type='tablet'] {
|
|
146
|
+
.imgWrapper {
|
|
147
|
+
aspect-ratio: 16 / 7;
|
|
148
|
+
max-height: 308px;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&[data-size='l'][data-layout-type='mobile'] {
|
|
153
|
+
.imgWrapper {
|
|
154
|
+
height: 226px;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { LayoutType } from '@cloud-ru/uikit-product-utils';
|
|
2
|
+
import { TypographyProps } from '@snack-uikit/typography';
|
|
3
|
+
|
|
4
|
+
export const getTypographySize = (layoutType: LayoutType, size?: 's' | 'm' | 'l'): TypographyProps['size'] => {
|
|
5
|
+
if (size === 's') {
|
|
6
|
+
switch (layoutType) {
|
|
7
|
+
case 'tablet':
|
|
8
|
+
case 'mobile':
|
|
9
|
+
return 's';
|
|
10
|
+
|
|
11
|
+
default:
|
|
12
|
+
return 'm';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
switch (layoutType) {
|
|
16
|
+
case 'tablet':
|
|
17
|
+
case 'mobile':
|
|
18
|
+
return 'm';
|
|
19
|
+
|
|
20
|
+
default:
|
|
21
|
+
return 'l';
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { AnchorHTMLAttributes, MouseEvent } from 'react';
|
|
3
|
+
|
|
4
|
+
import { RichText } from '@cloud-ru/uikit-product-site-rich-text';
|
|
5
|
+
import { extractSupportProps, WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
|
|
6
|
+
import { Card } from '@snack-uikit/card';
|
|
7
|
+
|
|
8
|
+
import { useCardInteractions } from '../../hooks';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
export type CardPartnerProps = WithSupportProps<
|
|
12
|
+
WithLayoutType<{
|
|
13
|
+
/** Описание карточки */
|
|
14
|
+
description: string;
|
|
15
|
+
/** Ссылка */
|
|
16
|
+
href?: string;
|
|
17
|
+
/** HTML-аттрибут target */
|
|
18
|
+
target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
|
|
19
|
+
/** Колбек клика*/
|
|
20
|
+
onClick?(e?: MouseEvent<HTMLDivElement | HTMLAnchorElement>): void;
|
|
21
|
+
/** Ссылка на логотип */
|
|
22
|
+
logo: {
|
|
23
|
+
src: string;
|
|
24
|
+
alt: string;
|
|
25
|
+
};
|
|
26
|
+
/* Включение обводки */
|
|
27
|
+
outline?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Является ли деактивированным
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
/** CSS-класс */
|
|
34
|
+
className?: string;
|
|
35
|
+
}>
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
export function CardPartner({
|
|
39
|
+
description,
|
|
40
|
+
logo,
|
|
41
|
+
href,
|
|
42
|
+
target = '_blank',
|
|
43
|
+
onClick,
|
|
44
|
+
disabled = false,
|
|
45
|
+
className,
|
|
46
|
+
layoutType,
|
|
47
|
+
outline,
|
|
48
|
+
'data-test-id': dataTestId = 'card-partner',
|
|
49
|
+
...rest
|
|
50
|
+
}: CardPartnerProps) {
|
|
51
|
+
const { anchorRef, handleCardKeyDown, handleCardClick, handleLinkClick } = useCardInteractions({
|
|
52
|
+
href,
|
|
53
|
+
onClick,
|
|
54
|
+
disabled,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Card
|
|
59
|
+
{...extractSupportProps(rest)}
|
|
60
|
+
size='m'
|
|
61
|
+
className={cn(styles.card, className)}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
onClick={handleCardClick}
|
|
64
|
+
onKeyDown={handleCardKeyDown}
|
|
65
|
+
outline={outline}
|
|
66
|
+
data-test-id={dataTestId}
|
|
67
|
+
>
|
|
68
|
+
<div className={styles.wrapper} data-layout-type={layoutType}>
|
|
69
|
+
<div className={styles.logo}>
|
|
70
|
+
{href ? (
|
|
71
|
+
<a
|
|
72
|
+
tabIndex={-1}
|
|
73
|
+
ref={anchorRef}
|
|
74
|
+
href={href}
|
|
75
|
+
target={target}
|
|
76
|
+
onClick={handleLinkClick}
|
|
77
|
+
data-test-id={`${dataTestId}__link`}
|
|
78
|
+
>
|
|
79
|
+
<img src={logo.src} alt={logo.alt} data-test-id={`${dataTestId}__logo`} />
|
|
80
|
+
</a>
|
|
81
|
+
) : (
|
|
82
|
+
<img src={logo.src} alt={logo.alt} data-test-id={`${dataTestId}__logo`} />
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<RichText richText={description} className={styles.description} data-test-id={`${dataTestId}__description`} />
|
|
87
|
+
</div>
|
|
88
|
+
</Card>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CardPartner';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
|
|
2
|
+
|
|
3
|
+
.card {
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.description {
|
|
8
|
+
@include ste.composite-var(ste.$sans-body-l);
|
|
9
|
+
|
|
10
|
+
color: ste.$sys-neutral-text-main;
|
|
11
|
+
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
display: -webkit-box;
|
|
14
|
+
-webkit-box-orient: vertical;
|
|
15
|
+
-webkit-line-clamp: 2;
|
|
16
|
+
max-width: 100%;
|
|
17
|
+
text-overflow: ellipsis;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.wrapper {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: flex-start;
|
|
24
|
+
gap: 24px;
|
|
25
|
+
|
|
26
|
+
&[data-layout-type='mobile'] {
|
|
27
|
+
.description {
|
|
28
|
+
-webkit-line-clamp: 4;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.logo {
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
height: 40px;
|
|
36
|
+
width: 144px;
|
|
37
|
+
|
|
38
|
+
a {
|
|
39
|
+
height: 100%;
|
|
40
|
+
width: 100%;
|
|
41
|
+
display: block;
|
|
42
|
+
text-decoration: none;
|
|
43
|
+
|
|
44
|
+
&::before {
|
|
45
|
+
content: '';
|
|
46
|
+
position: absolute;
|
|
47
|
+
top: 0;
|
|
48
|
+
left: 0;
|
|
49
|
+
right: 0;
|
|
50
|
+
bottom: 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
img {
|
|
55
|
+
height: 100%;
|
|
56
|
+
width: auto;
|
|
57
|
+
max-width: 100%;
|
|
58
|
+
object-fit: contain;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { AnchorHTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
import { ArrowLinksSVG } from '@cloud-ru/uikit-product-icons';
|
|
4
|
+
import { TagSpecial, TagSpecialProps } from '@cloud-ru/uikit-product-site-tag';
|
|
5
|
+
import { extractSupportProps, WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
|
|
6
|
+
import { ButtonFunction } from '@snack-uikit/button';
|
|
7
|
+
import { Card, CardProps } from '@snack-uikit/card';
|
|
8
|
+
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
9
|
+
|
|
10
|
+
import { Icon, IconProps } from '../../helperComponents/Icon';
|
|
11
|
+
import { useCardInteractions } from '../../hooks';
|
|
12
|
+
import { MapLayoutToTitleTypography } from './constants';
|
|
13
|
+
import styles from './styles.module.scss';
|
|
14
|
+
|
|
15
|
+
export type CardProductProps = WithSupportProps<
|
|
16
|
+
WithLayoutType<{
|
|
17
|
+
title: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
showIcon?: boolean;
|
|
20
|
+
icon?: IconProps['icon'];
|
|
21
|
+
href?: string;
|
|
22
|
+
target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
onClick?: CardProps['onClick'];
|
|
25
|
+
tags?: TagSpecialProps[];
|
|
26
|
+
className?: string;
|
|
27
|
+
outline?: boolean;
|
|
28
|
+
showBadge?: boolean;
|
|
29
|
+
}>
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
export function CardProduct({
|
|
33
|
+
className,
|
|
34
|
+
layoutType,
|
|
35
|
+
icon,
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
href,
|
|
39
|
+
target,
|
|
40
|
+
onClick,
|
|
41
|
+
disabled,
|
|
42
|
+
tags,
|
|
43
|
+
outline,
|
|
44
|
+
showIcon = true,
|
|
45
|
+
showBadge = false,
|
|
46
|
+
'data-test-id': dataTestId = 'card-product',
|
|
47
|
+
...rest
|
|
48
|
+
}: CardProductProps) {
|
|
49
|
+
const Title = MapLayoutToTitleTypography[layoutType];
|
|
50
|
+
|
|
51
|
+
const { anchorRef, handleCardKeyDown, handleCardClick, handleLinkClick } = useCardInteractions({
|
|
52
|
+
href,
|
|
53
|
+
onClick,
|
|
54
|
+
disabled,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Card
|
|
59
|
+
disabled={disabled}
|
|
60
|
+
className={className}
|
|
61
|
+
size='m'
|
|
62
|
+
data-layout-type={layoutType}
|
|
63
|
+
data-test-id={dataTestId}
|
|
64
|
+
onClick={handleCardClick}
|
|
65
|
+
onKeyDown={handleCardKeyDown}
|
|
66
|
+
outline={outline}
|
|
67
|
+
functionBadge={
|
|
68
|
+
showBadge ? (
|
|
69
|
+
<div className={styles.cardFunctionBadgeWrapper}>
|
|
70
|
+
<ButtonFunction
|
|
71
|
+
icon={<ArrowLinksSVG className={styles.badgeArrow} />}
|
|
72
|
+
appearance='primary'
|
|
73
|
+
onClick={handleCardClick}
|
|
74
|
+
href={href}
|
|
75
|
+
size='s'
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
) : undefined
|
|
79
|
+
}
|
|
80
|
+
{...extractSupportProps(rest)}
|
|
81
|
+
>
|
|
82
|
+
<div className={styles.wrapper}>
|
|
83
|
+
{showIcon && <Icon icon={icon} size='m' data-test-id={`${dataTestId}__icon`} decor />}
|
|
84
|
+
|
|
85
|
+
<div className={styles.content}>
|
|
86
|
+
<Title data-test-id={`${dataTestId}__title`} className={styles.title}>
|
|
87
|
+
{href ? (
|
|
88
|
+
<a
|
|
89
|
+
tabIndex={-1}
|
|
90
|
+
ref={anchorRef}
|
|
91
|
+
href={href}
|
|
92
|
+
target={target}
|
|
93
|
+
onClick={handleLinkClick}
|
|
94
|
+
data-test-id={`${dataTestId}__title-link`}
|
|
95
|
+
>
|
|
96
|
+
{title}
|
|
97
|
+
</a>
|
|
98
|
+
) : (
|
|
99
|
+
title
|
|
100
|
+
)}
|
|
101
|
+
</Title>
|
|
102
|
+
|
|
103
|
+
{description && (
|
|
104
|
+
<TruncateString
|
|
105
|
+
data-test-id={`${dataTestId}__description`}
|
|
106
|
+
text={description}
|
|
107
|
+
className={styles.description}
|
|
108
|
+
maxLines={3}
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{tags && tags.length > 0 && (
|
|
113
|
+
<div className={styles.tagList}>
|
|
114
|
+
{tags.map((tag, index) => (
|
|
115
|
+
<TagSpecial data-test-id={`${dataTestId}__tag-${index + 1}`} key={index} {...tag} />
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</Card>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LAYOUT_TYPE, LayoutType } from '@cloud-ru/uikit-product-utils';
|
|
4
|
+
import { Typography } from '@snack-uikit/typography';
|
|
5
|
+
|
|
6
|
+
export const MapLayoutToTitleTypography = {
|
|
7
|
+
[LAYOUT_TYPE.Desktop]: Typography.SansTitleM,
|
|
8
|
+
[LAYOUT_TYPE.DesktopSmall]: Typography.SansTitleM,
|
|
9
|
+
[LAYOUT_TYPE.Mobile]: Typography.SansLabelL,
|
|
10
|
+
[LAYOUT_TYPE.Tablet]: Typography.SansLabelL,
|
|
11
|
+
} as const satisfies Record<LayoutType, ComponentType>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CardProduct';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
|
|
2
|
+
|
|
3
|
+
.wrapper {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: row;
|
|
6
|
+
gap: 16px;
|
|
7
|
+
|
|
8
|
+
[data-layout-type='mobile'] &,
|
|
9
|
+
[data-layout-type='tablet'] & {
|
|
10
|
+
gap: 12px;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.content {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
padding-top: 6px;
|
|
18
|
+
align-items: flex-start;
|
|
19
|
+
gap: 4px;
|
|
20
|
+
flex: 1 0 0;
|
|
21
|
+
|
|
22
|
+
a {
|
|
23
|
+
color: ste.$sys-neutral-text-main;
|
|
24
|
+
text-decoration: none;
|
|
25
|
+
|
|
26
|
+
&::before {
|
|
27
|
+
content: '';
|
|
28
|
+
position: absolute;
|
|
29
|
+
top: 0;
|
|
30
|
+
left: 0;
|
|
31
|
+
right: 0;
|
|
32
|
+
bottom: 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[data-layout-type='mobile'] &,
|
|
37
|
+
[data-layout-type='tablet'] & {
|
|
38
|
+
gap: 0;
|
|
39
|
+
padding-top: 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.title {
|
|
44
|
+
color: ste.$sys-neutral-text-main;
|
|
45
|
+
word-break: break-word;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.description {
|
|
49
|
+
@include ste.composite-var(ste.$sans-body-m);
|
|
50
|
+
|
|
51
|
+
color: ste.$sys-neutral-text-support;
|
|
52
|
+
word-break: break-word;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.tagList {
|
|
56
|
+
display: flex;
|
|
57
|
+
padding-top: 6px;
|
|
58
|
+
padding-bottom: 6px;
|
|
59
|
+
align-items: flex-start;
|
|
60
|
+
align-content: flex-start;
|
|
61
|
+
gap: 4px;
|
|
62
|
+
align-self: stretch;
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
|
|
65
|
+
[data-layout-type='mobile'] &,
|
|
66
|
+
[data-layout-type='tablet'] & {
|
|
67
|
+
padding-bottom: 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.badgeArrow {
|
|
72
|
+
color: ste.$sys-neutral-text-main;
|
|
73
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LayoutType } from '@cloud-ru/uikit-product-utils';
|
|
2
|
+
import { TypographyProps } from '@snack-uikit/typography';
|
|
3
|
+
|
|
4
|
+
export function noop() {}
|
|
5
|
+
|
|
6
|
+
export const getTypographySize = (layoutType: LayoutType): TypographyProps['size'] => {
|
|
7
|
+
switch (layoutType) {
|
|
8
|
+
case 'tablet':
|
|
9
|
+
case 'mobile':
|
|
10
|
+
return 'm';
|
|
11
|
+
|
|
12
|
+
default:
|
|
13
|
+
return 'l';
|
|
14
|
+
}
|
|
15
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TRIGGER_CLICK_KEY_CODES = ['Enter', 'Space', ' '];
|