@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +690 -0
  2. package/LICENSE +201 -0
  3. package/README.md +8 -0
  4. package/package.json +51 -0
  5. package/src/components/CardBasic/CardBasic.tsx +74 -0
  6. package/src/components/CardBasic/index.ts +1 -0
  7. package/src/components/CardBasic/styles.module.scss +45 -0
  8. package/src/components/CardBasic/utils.ts +14 -0
  9. package/src/components/CardCase/CardCase.tsx +123 -0
  10. package/src/components/CardCase/index.ts +1 -0
  11. package/src/components/CardCase/styles.module.scss +68 -0
  12. package/src/components/CardInfo/CardInfo.tsx +120 -0
  13. package/src/components/CardInfo/index.ts +1 -0
  14. package/src/components/CardInfo/styles.module.scss +52 -0
  15. package/src/components/CardInfo/utilsTypes.ts +3 -0
  16. package/src/components/CardList/CardList.tsx +90 -0
  17. package/src/components/CardList/index.ts +1 -0
  18. package/src/components/CardList/styles.module.scss +102 -0
  19. package/src/components/CardList/utils.ts +35 -0
  20. package/src/components/CardMarketplace/CardMarketplace.tsx +112 -0
  21. package/src/components/CardMarketplace/index.ts +1 -0
  22. package/src/components/CardMarketplace/styles.module.scss +73 -0
  23. package/src/components/CardMedia/CardMedia.tsx +123 -0
  24. package/src/components/CardMedia/index.ts +1 -0
  25. package/src/components/CardMedia/styles.module.scss +157 -0
  26. package/src/components/CardMedia/types.ts +3 -0
  27. package/src/components/CardMedia/utils.ts +23 -0
  28. package/src/components/CardPartner/CardPartner.tsx +90 -0
  29. package/src/components/CardPartner/index.ts +1 -0
  30. package/src/components/CardPartner/styles.module.scss +60 -0
  31. package/src/components/CardProduct/CardProduct.tsx +123 -0
  32. package/src/components/CardProduct/constants.ts +11 -0
  33. package/src/components/CardProduct/index.ts +1 -0
  34. package/src/components/CardProduct/styles.module.scss +73 -0
  35. package/src/components/index.ts +8 -0
  36. package/src/components/utils.ts +15 -0
  37. package/src/constants.ts +1 -0
  38. package/src/helperComponents/Icon/Icon.tsx +31 -0
  39. package/src/helperComponents/Icon/index.ts +1 -0
  40. package/src/helperComponents/Icon/styles.module.scss +49 -0
  41. package/src/hooks.ts +40 -0
  42. package/src/index.ts +3 -0
@@ -0,0 +1,120 @@
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 { TagSpecial, TagSpecialProps } from '@cloud-ru/uikit-product-site-tag';
6
+ import { extractSupportProps, WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
7
+ import { Card } from '@snack-uikit/card';
8
+ import { Typography } from '@snack-uikit/typography';
9
+
10
+ import { Icon, IconProps } from '../../helperComponents/Icon';
11
+ import { useCardInteractions } from '../../hooks';
12
+ import { getTypographySize } from '../utils';
13
+ import styles from './styles.module.scss';
14
+
15
+ export type CardInfoProps = WithSupportProps<
16
+ WithLayoutType<{
17
+ /** Заголовок карточки*/
18
+ title: string;
19
+ /** Описание карточки */
20
+ description?: string;
21
+ /** Ссылка */
22
+ href?: string;
23
+ /** HTML-аттрибут target */
24
+ target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
25
+ /** Колбек клика*/
26
+ onClick?(e?: MouseEvent<HTMLDivElement | HTMLAnchorElement>): void;
27
+ /** Слот под иконку/кастомную ноду */
28
+ icon?: IconProps['icon'];
29
+ /** Тег */
30
+ tag?: Pick<TagSpecialProps, 'text' | 'appearance'>;
31
+ /**
32
+ * Является ли деактивированным
33
+ * @default false
34
+ */
35
+ disabled?: boolean;
36
+ /** Наличие обводки */
37
+ outline?: boolean;
38
+ /** CSS-класс */
39
+ className?: string;
40
+ }>
41
+ >;
42
+
43
+ export function CardInfo({
44
+ title,
45
+ description,
46
+ icon,
47
+ href,
48
+ target = '_blank',
49
+ onClick,
50
+ tag,
51
+ disabled = false,
52
+ className,
53
+ layoutType,
54
+ outline,
55
+ 'data-test-id': dataTestId = 'card-info',
56
+ ...rest
57
+ }: CardInfoProps) {
58
+ const { anchorRef, handleLinkClick, handleCardClick, handleCardKeyDown } = useCardInteractions({
59
+ href,
60
+ disabled,
61
+ onClick,
62
+ });
63
+
64
+ return (
65
+ <Card
66
+ {...extractSupportProps(rest)}
67
+ size='m'
68
+ className={cn(styles.card, className)}
69
+ disabled={disabled}
70
+ outline={outline}
71
+ onClick={handleCardClick}
72
+ onKeyDown={handleCardKeyDown}
73
+ data-test-id={dataTestId}
74
+ >
75
+ <div data-layout-type={layoutType} className={styles.wrapper}>
76
+ {tag && <TagSpecial {...tag} className={styles.tag} data-test-id={`${dataTestId}__tag`} />}
77
+
78
+ {icon && <Icon size='m' data-test-id={`${dataTestId}__icon`} icon={icon} />}
79
+
80
+ <div className={styles.content}>
81
+ <Typography
82
+ tag='h3'
83
+ family='sans'
84
+ purpose='title'
85
+ size={getTypographySize(layoutType)}
86
+ className={styles.title}
87
+ data-test-id={`${dataTestId}__title`}
88
+ >
89
+ {href ? (
90
+ <a
91
+ tabIndex={-1}
92
+ ref={anchorRef}
93
+ href={href}
94
+ target={target}
95
+ onClick={handleLinkClick}
96
+ data-test-id={`${dataTestId}__title-link`}
97
+ >
98
+ <RichText richText={title} />
99
+ </a>
100
+ ) : (
101
+ <RichText richText={title} />
102
+ )}
103
+ </Typography>
104
+
105
+ {description && (
106
+ <Typography
107
+ tag='div'
108
+ family='sans'
109
+ purpose='body'
110
+ size={getTypographySize(layoutType)}
111
+ data-test-id={`${dataTestId}__description`}
112
+ >
113
+ <RichText className={styles.description} richText={description} />
114
+ </Typography>
115
+ )}
116
+ </div>
117
+ </div>
118
+ </Card>
119
+ );
120
+ }
@@ -0,0 +1 @@
1
+ export * from './CardInfo';
@@ -0,0 +1,52 @@
1
+ @use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
2
+
3
+ .card {
4
+ position: relative;
5
+ border-radius: 0;
6
+ }
7
+
8
+ .wrapper {
9
+ display: flex;
10
+ flex-direction: row;
11
+ gap: ste.$dimension-2m;
12
+
13
+ .content {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: ste.$dimension-050m;
17
+
18
+ .title {
19
+ color: ste.$sys-neutral-text-main;
20
+
21
+ a {
22
+ color: ste.$sys-neutral-text-main;
23
+ text-decoration: none;
24
+
25
+ &::before {
26
+ content: '';
27
+ position: absolute;
28
+ top: 0;
29
+ left: 0;
30
+ right: 0;
31
+ bottom: 0;
32
+ }
33
+ }
34
+ }
35
+
36
+ .description {
37
+ color: ste.$sys-neutral-text-support;
38
+ }
39
+ }
40
+
41
+ .tag {
42
+ position: absolute;
43
+ right: ste.$dimension-050m;
44
+ top: ste.$dimension-050m;
45
+ }
46
+
47
+ &[data-layout-type='tablet'],
48
+ &[data-layout-type='mobile'] {
49
+ flex-direction: column;
50
+ gap: 12px;
51
+ }
52
+ }
@@ -0,0 +1,3 @@
1
+ export type BetterOmit<Type, Prop> = {
2
+ [Property in keyof Type as Exclude<Property, Prop>]: Type[Property];
3
+ };
@@ -0,0 +1,90 @@
1
+ import cn from 'classnames';
2
+ import { AnchorHTMLAttributes, MouseEvent } from 'react';
3
+
4
+ import { TagSpecial, TagSpecialProps } from '@cloud-ru/uikit-product-site-tag';
5
+ import { extractSupportProps, WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
6
+ import { TruncateString } from '@snack-uikit/truncate-string';
7
+ import { Typography } from '@snack-uikit/typography';
8
+
9
+ import styles from './styles.module.scss';
10
+ import { getTitleTruncateMaxLines, getTitleTypographyProps } from './utils';
11
+
12
+ export type CardListProps = WithSupportProps<
13
+ WithLayoutType<{
14
+ /** Изображение */
15
+ img: string;
16
+ /** Теги */
17
+ tags: Omit<TagSpecialProps, 'size' | 'className'>[];
18
+ /** Ссылка */
19
+ href: string;
20
+ /** HTML-аттрибут target */
21
+ target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
22
+ /** Колбек клика*/
23
+ onClick?(e: MouseEvent<HTMLAnchorElement>): void;
24
+ /**
25
+ * Является ли деактивированным
26
+ * @default false
27
+ */
28
+ disabled?: boolean;
29
+ /** CSS-класс */
30
+ className?: string;
31
+ /** Заголовок */
32
+ title: string;
33
+ /** Дата */
34
+ labelDate?: string;
35
+ }>
36
+ >;
37
+
38
+ export function CardList({
39
+ disabled,
40
+ layoutType,
41
+ img,
42
+ title,
43
+ className,
44
+ labelDate,
45
+ href,
46
+ onClick,
47
+ tags,
48
+ target,
49
+ 'data-test-id': dataTestId = 'card-list',
50
+ ...rest
51
+ }: CardListProps) {
52
+ return (
53
+ <a
54
+ {...extractSupportProps(rest)}
55
+ href={href}
56
+ target={target}
57
+ className={cn(styles.wrapper, className)}
58
+ aria-disabled={disabled || undefined}
59
+ tabIndex={disabled ? -1 : 0}
60
+ onClick={disabled ? undefined : onClick}
61
+ data-layout-type={layoutType}
62
+ >
63
+ <div className={styles.imgWrapper} data-test-id={`${dataTestId}-imgWrapper`}>
64
+ <img className={styles.img} src={img} alt='card_img' data-test-id={`${dataTestId}-img`} />
65
+ </div>
66
+
67
+ <div className={styles.content}>
68
+ <div className={styles.tagRow}>
69
+ {tags.map((tag, idx) => (
70
+ <TagSpecial {...tag} key={idx} />
71
+ ))}
72
+ </div>
73
+
74
+ <Typography className={styles.title} tag='h3' family='sans' {...getTitleTypographyProps({ layoutType })}>
75
+ <TruncateString
76
+ text={title}
77
+ maxLines={getTitleTruncateMaxLines({ layoutType })}
78
+ data-test-id={`${dataTestId}-title`}
79
+ />
80
+ </Typography>
81
+
82
+ {labelDate && (
83
+ <Typography.SansBodyS className={styles.labelDate} data-test-id={`${dataTestId}-time`}>
84
+ {labelDate}
85
+ </Typography.SansBodyS>
86
+ )}
87
+ </div>
88
+ </a>
89
+ );
90
+ }
@@ -0,0 +1 @@
1
+ export * from './CardList';
@@ -0,0 +1,102 @@
1
+ @use '@sbercloud/figma-tokens-web/build/scss/styles-theme-variables' as var;
2
+ @use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as element;
3
+
4
+ $transition: 0.3s;
5
+ $scale: 1.05;
6
+
7
+ .title {
8
+ flex: 1 0 auto;
9
+
10
+ color: var.$sys-neutral-text-main;
11
+ }
12
+
13
+ .img,
14
+ .title {
15
+ transition: all $transition;
16
+ }
17
+
18
+ .imgWrapper {
19
+ .img {
20
+ height: 100%;
21
+ width: 100%;
22
+ }
23
+
24
+ aspect-ratio: 1.6;
25
+ height: 176px;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .wrapper {
30
+ display: grid;
31
+ grid-template-columns: auto 1fr;
32
+ gap: var.$dimension-2m;
33
+ text-decoration: none;
34
+
35
+ &[data-layout-type='tablet'] {
36
+ .imgWrapper {
37
+ height: 160px;
38
+ }
39
+ }
40
+
41
+ &[data-layout-type='mobile'] {
42
+ .imgWrapper {
43
+ aspect-ratio: unset;
44
+ width: 100%;
45
+ height: auto;
46
+ }
47
+
48
+ grid-template-columns: 1fr;
49
+ }
50
+
51
+ &[aria-disabled] {
52
+ pointer-events: none;
53
+ cursor: not-allowed;
54
+
55
+ .title {
56
+ color: var.$sys-neutral-text-disabled;
57
+ }
58
+
59
+ .imgWrapper {
60
+ opacity: 0.5;
61
+ }
62
+ }
63
+
64
+ &:not([aria-disabled]) {
65
+ &:hover {
66
+ .title {
67
+ color: var.$sys-primary-accent-hovered;
68
+ }
69
+ }
70
+
71
+ &:focus-visible {
72
+ @include element.outline-var(element.$container-focused-m);
73
+
74
+ outline-color: var.$sys-primary-decor-activated;
75
+ }
76
+
77
+ &:hover,
78
+ &:focus-visible {
79
+ .img {
80
+ scale: $scale;
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ .tagRow {
87
+ display: flex;
88
+ justify-content: flex-start;
89
+ gap: var.$dimension-1m;
90
+
91
+ flex-wrap: wrap;
92
+ }
93
+
94
+ .content {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: var.$dimension-2m;
98
+ }
99
+
100
+ .labelDate {
101
+ color: var.$sys-neutral-text-light;
102
+ }
@@ -0,0 +1,35 @@
1
+ import { LAYOUT_TYPE, WithLayoutType } from '@cloud-ru/uikit-product-utils';
2
+ import { PURPOSE, SIZE, TypographyProps } from '@snack-uikit/typography';
3
+
4
+ type Props = WithLayoutType<unknown>;
5
+
6
+ export const getTitleTypographyProps = ({ layoutType }: Props): Pick<TypographyProps, 'size' | 'purpose'> => {
7
+ switch (layoutType) {
8
+ case LAYOUT_TYPE.Mobile:
9
+ case LAYOUT_TYPE.Tablet:
10
+ return {
11
+ purpose: PURPOSE.Title,
12
+ size: SIZE.L,
13
+ };
14
+ case LAYOUT_TYPE.Desktop:
15
+ case LAYOUT_TYPE.DesktopSmall:
16
+ default:
17
+ return {
18
+ purpose: PURPOSE.Headline,
19
+ size: SIZE.S,
20
+ };
21
+ }
22
+ };
23
+
24
+ export const getTitleTruncateMaxLines = ({ layoutType }: Props): number => {
25
+ switch (layoutType) {
26
+ case LAYOUT_TYPE.Mobile:
27
+ return 5;
28
+ case LAYOUT_TYPE.Tablet:
29
+ return 3;
30
+ case LAYOUT_TYPE.Desktop:
31
+ case LAYOUT_TYPE.DesktopSmall:
32
+ default:
33
+ return 2;
34
+ }
35
+ };
@@ -0,0 +1,112 @@
1
+ import cn from 'classnames';
2
+ import { AnchorHTMLAttributes, MouseEvent } from 'react';
3
+
4
+ import { TagSpecial, TagSpecialProps } from '@cloud-ru/uikit-product-site-tag';
5
+ import { extractSupportProps, WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
6
+ import { Card } from '@snack-uikit/card';
7
+ import { TruncateString } from '@snack-uikit/truncate-string';
8
+ import { Typography } from '@snack-uikit/typography';
9
+
10
+ import { useCardInteractions } from '../../hooks';
11
+ import { getTypographySize } from '../utils';
12
+ import styles from './styles.module.scss';
13
+
14
+ export type CardMarketplaceProps = WithSupportProps<
15
+ WithLayoutType<{
16
+ /** Заголовок карточки*/
17
+ title: string;
18
+ /** Описание карточки */
19
+ description?: string;
20
+ /** Ссылка */
21
+ href: string;
22
+ /** HTML-аттрибут target */
23
+ target?: AnchorHTMLAttributes<HTMLAnchorElement>['target'];
24
+ /** Колбек клика*/
25
+ onClick?(e?: MouseEvent<HTMLDivElement | HTMLAnchorElement>): void;
26
+ /** Ссылка на изображение */
27
+ logo: {
28
+ src: string;
29
+ alt?: string;
30
+ };
31
+ /** Тег */
32
+ tag?: Omit<TagSpecialProps, 'className'>;
33
+ /**
34
+ * Является ли деактивированным
35
+ * @default false
36
+ */
37
+ disabled?: boolean;
38
+ /** CSS-класс */
39
+ className?: string;
40
+ }>
41
+ >;
42
+
43
+ export function CardMarketplace({
44
+ title,
45
+ description,
46
+ logo,
47
+ href,
48
+ target = '_blank',
49
+ onClick,
50
+ tag,
51
+ disabled = false,
52
+ className,
53
+ layoutType,
54
+ 'data-test-id': dataTestId = 'card-marketplace',
55
+ ...rest
56
+ }: CardMarketplaceProps) {
57
+ const { anchorRef, handleCardKeyDown, handleCardClick, handleLinkClick } = useCardInteractions({
58
+ href,
59
+ onClick,
60
+ disabled,
61
+ });
62
+
63
+ return (
64
+ <Card
65
+ {...extractSupportProps(rest)}
66
+ size='m'
67
+ className={cn(styles.card, className)}
68
+ disabled={disabled}
69
+ onClick={handleCardClick}
70
+ onKeyDown={handleCardKeyDown}
71
+ data-test-id={dataTestId}
72
+ outline
73
+ >
74
+ <div data-layout-type={layoutType} className={styles.wrapper}>
75
+ <img alt={logo.alt ?? title} data-test-id={`${dataTestId}__logo`} src={logo.src} className={styles.logo} />
76
+
77
+ <div className={styles.content}>
78
+ <Typography
79
+ tag='h3'
80
+ family='sans'
81
+ purpose='title'
82
+ size={getTypographySize(layoutType)}
83
+ className={styles.title}
84
+ data-test-id={`${dataTestId}__title`}
85
+ >
86
+ <a
87
+ tabIndex={-1}
88
+ ref={anchorRef}
89
+ href={href}
90
+ target={target}
91
+ onClick={handleLinkClick}
92
+ data-test-id={`${dataTestId}__title-link`}
93
+ >
94
+ <TruncateString text={title} maxLines={2} />
95
+ </a>
96
+ </Typography>
97
+
98
+ {description && (
99
+ <TruncateString
100
+ className={styles.description}
101
+ text={description}
102
+ maxLines={4}
103
+ data-test-id={`${dataTestId}__description`}
104
+ />
105
+ )}
106
+ </div>
107
+
108
+ {tag && <TagSpecial data-test-id={`${dataTestId}__tag`} className={styles.tag} {...tag} />}
109
+ </div>
110
+ </Card>
111
+ );
112
+ }
@@ -0,0 +1 @@
1
+ export * from './CardMarketplace';
@@ -0,0 +1,73 @@
1
+ @use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-card' as ste;
2
+
3
+ .card {
4
+ position: relative;
5
+ }
6
+
7
+ .wrapper {
8
+ display: flex;
9
+ flex-direction: column;
10
+ align-items: flex-start;
11
+ gap: 20px;
12
+
13
+ &[data-layout-type=mobile],
14
+ &[data-layout-type=tablet] {
15
+ gap: 12px;
16
+ }
17
+ }
18
+
19
+ .content {
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: flex-start;
23
+ gap: 8px;
24
+ width: 100%;
25
+
26
+ [data-layout-type=mobile] &,
27
+ [data-layout-type=tablet] & {
28
+ gap: 4px;
29
+ }
30
+ }
31
+
32
+
33
+ .title {
34
+ color: ste.$sys-neutral-text-main;
35
+
36
+ a {
37
+ color: inherit;
38
+ text-decoration: none;
39
+
40
+ &::before {
41
+ content: '';
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ right: 0;
46
+ bottom: 0;
47
+ }
48
+ }
49
+ }
50
+
51
+ .description {
52
+ @include ste.composite-var(ste.$sans-body-l);
53
+
54
+ color: ste.$sys-neutral-text-support;
55
+
56
+ [data-layout-type=mobile] &,
57
+ [data-layout-type=tablet] & {
58
+ @include ste.composite-var(ste.$sans-body-m);
59
+ }
60
+ }
61
+
62
+ .tag {
63
+ position: absolute;
64
+ right: ste.simple-var(ste.$card-content-m-container, 'padding');
65
+ top: ste.simple-var(ste.$card-content-m-container, 'padding');
66
+ }
67
+
68
+ .logo {
69
+ flex-shrink: 0;
70
+ height: 40px;
71
+ width: 40px;
72
+ object-fit: contain;
73
+ }