@snack-uikit/tag 0.4.10-preview-104ad22c.0

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 (78) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/LICENSE +201 -0
  3. package/README.md +31 -0
  4. package/dist/components/Tag/Tag.d.ts +20 -0
  5. package/dist/components/Tag/Tag.js +24 -0
  6. package/dist/components/Tag/constants.d.ts +4 -0
  7. package/dist/components/Tag/constants.js +5 -0
  8. package/dist/components/Tag/index.d.ts +1 -0
  9. package/dist/components/Tag/index.js +1 -0
  10. package/dist/components/Tag/styles.module.css +270 -0
  11. package/dist/components/TagRow/TagRow.d.ts +22 -0
  12. package/dist/components/TagRow/TagRow.js +25 -0
  13. package/dist/components/TagRow/constants.d.ts +5 -0
  14. package/dist/components/TagRow/constants.js +5 -0
  15. package/dist/components/TagRow/index.d.ts +1 -0
  16. package/dist/components/TagRow/index.js +1 -0
  17. package/dist/components/TagRow/utils.d.ts +2 -0
  18. package/dist/components/TagRow/utils.js +13 -0
  19. package/dist/components/index.d.ts +2 -0
  20. package/dist/components/index.js +2 -0
  21. package/dist/constants.d.ts +15 -0
  22. package/dist/constants.js +17 -0
  23. package/dist/helperComponents/TagList/TagList.d.ts +12 -0
  24. package/dist/helperComponents/TagList/TagList.js +12 -0
  25. package/dist/helperComponents/TagList/index.d.ts +1 -0
  26. package/dist/helperComponents/TagList/index.js +1 -0
  27. package/dist/helperComponents/TagMore/TagMore.d.ts +12 -0
  28. package/dist/helperComponents/TagMore/TagMore.js +10 -0
  29. package/dist/helperComponents/TagMore/index.d.ts +1 -0
  30. package/dist/helperComponents/TagMore/index.js +1 -0
  31. package/dist/helperComponents/TagMore/styles.module.css +69 -0
  32. package/dist/helperComponents/TagRowSimple/TagRowSimple.d.ts +12 -0
  33. package/dist/helperComponents/TagRowSimple/TagRowSimple.js +20 -0
  34. package/dist/helperComponents/TagRowSimple/index.d.ts +1 -0
  35. package/dist/helperComponents/TagRowSimple/index.js +1 -0
  36. package/dist/helperComponents/TagRowSimple/styles.module.css +10 -0
  37. package/dist/helperComponents/TagRowTruncated/TagRowTruncated.d.ts +13 -0
  38. package/dist/helperComponents/TagRowTruncated/TagRowTruncated.js +87 -0
  39. package/dist/helperComponents/TagRowTruncated/hooks.d.ts +3 -0
  40. package/dist/helperComponents/TagRowTruncated/hooks.js +17 -0
  41. package/dist/helperComponents/TagRowTruncated/index.d.ts +1 -0
  42. package/dist/helperComponents/TagRowTruncated/index.js +1 -0
  43. package/dist/helperComponents/TagRowTruncated/styles.module.css +47 -0
  44. package/dist/helperComponents/TagRowTruncated/utils.d.ts +2 -0
  45. package/dist/helperComponents/TagRowTruncated/utils.js +4 -0
  46. package/dist/helperComponents/index.d.ts +2 -0
  47. package/dist/helperComponents/index.js +2 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +1 -0
  50. package/dist/types.d.ts +9 -0
  51. package/dist/types.js +1 -0
  52. package/package.json +42 -0
  53. package/src/components/Tag/Tag.tsx +55 -0
  54. package/src/components/Tag/constants.ts +6 -0
  55. package/src/components/Tag/index.ts +1 -0
  56. package/src/components/Tag/styles.module.scss +175 -0
  57. package/src/components/TagRow/TagRow.tsx +34 -0
  58. package/src/components/TagRow/constants.ts +5 -0
  59. package/src/components/TagRow/index.ts +1 -0
  60. package/src/components/TagRow/utils.ts +16 -0
  61. package/src/components/index.ts +2 -0
  62. package/src/constants.ts +16 -0
  63. package/src/helperComponents/TagList/TagList.tsx +32 -0
  64. package/src/helperComponents/TagList/index.ts +1 -0
  65. package/src/helperComponents/TagMore/TagMore.tsx +54 -0
  66. package/src/helperComponents/TagMore/index.ts +1 -0
  67. package/src/helperComponents/TagMore/styles.module.scss +61 -0
  68. package/src/helperComponents/TagRowSimple/TagRowSimple.tsx +25 -0
  69. package/src/helperComponents/TagRowSimple/index.ts +1 -0
  70. package/src/helperComponents/TagRowSimple/styles.module.scss +14 -0
  71. package/src/helperComponents/TagRowTruncated/TagRowTruncated.tsx +127 -0
  72. package/src/helperComponents/TagRowTruncated/hooks.ts +23 -0
  73. package/src/helperComponents/TagRowTruncated/index.ts +1 -0
  74. package/src/helperComponents/TagRowTruncated/styles.module.scss +57 -0
  75. package/src/helperComponents/TagRowTruncated/utils.ts +7 -0
  76. package/src/helperComponents/index.ts +2 -0
  77. package/src/index.ts +1 -0
  78. package/src/types.ts +11 -0
@@ -0,0 +1,87 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import cn from 'classnames';
14
+ import { useEffect, useRef, useState } from 'react';
15
+ import { extractSupportProps } from '@snack-uikit/utils';
16
+ import { TAG_ROW_TEST_IDS } from '../../components/TagRow/constants';
17
+ import { TagList } from '../TagList';
18
+ import { TagMore } from '../TagMore';
19
+ import { useResizeObserver } from './hooks';
20
+ import styles from './styles.module.css';
21
+ import { getGapWidth } from './utils';
22
+ export function TagRowTruncated(_a) {
23
+ var { items, rowLimit, size, moreButtonLabel = '', className, onItemRemove } = _a, rest = __rest(_a, ["items", "rowLimit", "size", "moreButtonLabel", "className", "onItemRemove"]);
24
+ const [visibleTags, setVisibleTags] = useState([]);
25
+ const [hiddenTags, setHiddenTags] = useState([]);
26
+ const hiddenRowElementRef = useRef(null);
27
+ const hiddenMoreButtonRef = useRef(null);
28
+ const hiddenMoreButtonWrapperRef = useRef(null);
29
+ const [firstTagElement, setFirstTagElement] = useState(null);
30
+ const tagsMapRef = useRef(new Map());
31
+ const { width: maxWidth } = useResizeObserver(hiddenRowElementRef.current);
32
+ const { width: firstVisibleTagWidth } = useResizeObserver(firstTagElement);
33
+ function setTagElement(tag, index) {
34
+ return (tagElement) => {
35
+ if (index === 0 && tagElement) {
36
+ setFirstTagElement(tagElement);
37
+ }
38
+ if (tagElement === null) {
39
+ tagsMapRef.current.delete(tag);
40
+ }
41
+ else {
42
+ tagsMapRef.current.set(tag, tagElement);
43
+ }
44
+ };
45
+ }
46
+ const gapWidth = getGapWidth(hiddenRowElementRef);
47
+ useEffect(() => {
48
+ var _a;
49
+ if (maxWidth < 1) {
50
+ return;
51
+ }
52
+ const newVisibleTags = [];
53
+ const newHiddenTags = [];
54
+ const moreButtonWidth = ((_a = hiddenMoreButtonRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
55
+ let currentRowWidth = 0;
56
+ let currentRowCount = 1;
57
+ tagsMapRef.current.forEach((tagElement, tagRowItem) => {
58
+ const tagWidth = (tagElement === null || tagElement === void 0 ? void 0 : tagElement.offsetWidth) || 0;
59
+ const sumRowWidth = currentRowWidth + tagWidth + gapWidth;
60
+ if (currentRowCount > rowLimit) {
61
+ newHiddenTags.push(tagRowItem);
62
+ return;
63
+ }
64
+ if (currentRowCount === rowLimit) {
65
+ if (sumRowWidth + moreButtonWidth > maxWidth) {
66
+ currentRowCount++;
67
+ newHiddenTags.push(tagRowItem);
68
+ return;
69
+ }
70
+ currentRowWidth = sumRowWidth;
71
+ newVisibleTags.push(tagRowItem);
72
+ return;
73
+ }
74
+ if (sumRowWidth > maxWidth) {
75
+ currentRowWidth = tagWidth + gapWidth;
76
+ currentRowCount++;
77
+ newVisibleTags.push(tagRowItem);
78
+ return;
79
+ }
80
+ currentRowWidth = sumRowWidth;
81
+ newVisibleTags.push(tagRowItem);
82
+ });
83
+ setVisibleTags(newVisibleTags);
84
+ setHiddenTags(newHiddenTags);
85
+ }, [items, rowLimit, maxWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
86
+ return (_jsxs("div", Object.assign({ className: cn(styles.wrapper, className) }, extractSupportProps(rest), { "data-size": size }, { children: [_jsx("div", Object.assign({ className: styles.hiddenRow, ref: hiddenRowElementRef, "data-size": size }, { children: _jsx(TagList, { items: items, size: size, onItemRemove: onItemRemove, setTagRef: setTagElement }) })), _jsx("div", Object.assign({ className: styles.hiddenMoreButton, ref: hiddenMoreButtonWrapperRef }, { children: _jsx(TagMore, { items: items, text: moreButtonLabel, size: size, buttonRef: hiddenMoreButtonRef }) })), _jsxs("div", Object.assign({ className: styles.visibleRow, "data-size": size, "data-test-id": TAG_ROW_TEST_IDS.visibleTagsWrapper }, { children: [_jsx(TagList, { items: visibleTags, size: size, onItemRemove: onItemRemove }), hiddenTags.length > 0 && (_jsx(TagMore, { items: hiddenTags, text: moreButtonLabel, size: size, onItemRemove: onItemRemove }))] }))] })));
87
+ }
@@ -0,0 +1,3 @@
1
+ export declare const useResizeObserver: (element: HTMLElement | null) => {
2
+ width: number;
3
+ };
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from 'react';
2
+ export const useResizeObserver = (element) => {
3
+ const [width, setWidth] = useState(0);
4
+ useEffect(() => {
5
+ if (!element) {
6
+ return;
7
+ }
8
+ const resizeObserver = new ResizeObserver(() => {
9
+ if (element.offsetWidth !== width) {
10
+ setWidth(element.offsetWidth);
11
+ }
12
+ });
13
+ resizeObserver.observe(element);
14
+ return () => resizeObserver.disconnect();
15
+ }, [element, width]);
16
+ return { width };
17
+ };
@@ -0,0 +1 @@
1
+ export * from './TagRowTruncated';
@@ -0,0 +1 @@
1
+ export * from './TagRowTruncated';
@@ -0,0 +1,47 @@
1
+ .wrapper{
2
+ position:relative;
3
+ display:flex;
4
+ flex-wrap:wrap;
5
+ width:100%;
6
+ max-width:100%;
7
+ }
8
+ .wrapper[data-size=xs]{
9
+ gap:var(--space-tag-row-xs-gap, 2px);
10
+ }
11
+ .wrapper[data-size=s]{
12
+ gap:var(--space-tag-row-s-gap, 2px);
13
+ }
14
+
15
+ .visibleRow{
16
+ display:flex;
17
+ flex-wrap:wrap;
18
+ max-width:100%;
19
+ }
20
+ .visibleRow[data-size=xs]{
21
+ gap:var(--space-tag-row-xs-gap, 2px);
22
+ }
23
+ .visibleRow[data-size=s]{
24
+ gap:var(--space-tag-row-s-gap, 2px);
25
+ }
26
+
27
+ .hiddenRow{
28
+ position:absolute;
29
+ top:0;
30
+ left:0;
31
+ display:flex;
32
+ flex-wrap:wrap;
33
+ width:100%;
34
+ visibility:hidden;
35
+ }
36
+ .hiddenRow[data-size=xs]{
37
+ gap:var(--space-tag-row-xs-gap, 2px);
38
+ }
39
+ .hiddenRow[data-size=s]{
40
+ gap:var(--space-tag-row-s-gap, 2px);
41
+ }
42
+
43
+ .hiddenMoreButton{
44
+ position:absolute;
45
+ left:0;
46
+ visibility:hidden;
47
+ }
@@ -0,0 +1,2 @@
1
+ import { RefObject } from 'react';
2
+ export declare const getGapWidth: (elementRef: RefObject<HTMLDivElement>) => number;
@@ -0,0 +1,4 @@
1
+ export const getGapWidth = (elementRef) => {
2
+ const cssValue = elementRef.current ? getComputedStyle(elementRef.current, null).getPropertyValue('gap') : '0px';
3
+ return parseInt(cssValue);
4
+ };
@@ -0,0 +1,2 @@
1
+ export * from './TagRowTruncated';
2
+ export * from './TagRowSimple';
@@ -0,0 +1,2 @@
1
+ export * from './TagRowTruncated';
2
+ export * from './TagRowSimple';
@@ -0,0 +1 @@
1
+ export * from './components';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './components';
@@ -0,0 +1,9 @@
1
+ import { Appearance } from './constants';
2
+ export type TagRowItem = {
3
+ label: string;
4
+ color?: Appearance;
5
+ };
6
+ export type TagRowItemInner = {
7
+ label: string;
8
+ color: Appearance;
9
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@snack-uikit/tag",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "title": "Tag",
7
+ "version": "0.4.10-preview-104ad22c.0",
8
+ "sideEffects": [
9
+ "*.css",
10
+ "*.woff",
11
+ "*.woff2"
12
+ ],
13
+ "description": "",
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "homepage": "https://git.sbercloud.tech/sbercloud-ui/tokens-design-system/snack-uikit/-/tree/master/packages/tag",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://git.sbercloud.tech/sbercloud-ui/tokens-design-system/snack-uikit.git",
20
+ "directory": "packages/tag"
21
+ },
22
+ "author": "Дмитрий Безматерных <dobezmaternih@cloud.ru>",
23
+ "contributors": [
24
+ "Дмитрий Безматерных <dobezmaternih@cloud.ru>"
25
+ ],
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "./CHANGELOG.md",
30
+ "./LICENSE"
31
+ ],
32
+ "license": "Apache-2.0",
33
+ "scripts": {},
34
+ "dependencies": {
35
+ "@snack-uikit/icons": "0.18.2-preview-104ad22c.0",
36
+ "@snack-uikit/popover-private": "0.10.1-preview-104ad22c.0",
37
+ "@snack-uikit/scroll": "0.3.5-preview-104ad22c.0",
38
+ "@snack-uikit/utils": "2.0.2-preview-104ad22c.0",
39
+ "classnames": "2.3.2"
40
+ },
41
+ "gitHead": "580e0d5c22d04d609bcdf7cb0873114f9fce5f66"
42
+ }
@@ -0,0 +1,55 @@
1
+ import cn from 'classnames';
2
+ import { MouseEventHandler } from 'react';
3
+
4
+ import { CrossSVG } from '@snack-uikit/icons';
5
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
6
+
7
+ import { Appearance, Size } from '../../constants';
8
+ import { IconSize } from './constants';
9
+ import styles from './styles.module.scss';
10
+
11
+ export type TagProps = WithSupportProps<{
12
+ /** Текст */
13
+ label: string;
14
+ /** Размер */
15
+ size?: Size;
16
+ /** Внешний вид */
17
+ appearance?: Appearance;
18
+ /** Коллбэк на удаление */
19
+ onDelete?: MouseEventHandler<HTMLButtonElement>;
20
+ /** CSS-класс */
21
+ className?: string;
22
+ }>;
23
+
24
+ export function Tag({
25
+ label,
26
+ size = Size.Xs,
27
+ appearance = Appearance.Neutral,
28
+ onDelete,
29
+ className,
30
+ ...rest
31
+ }: TagProps) {
32
+ return (
33
+ <span
34
+ {...extractSupportProps(rest)}
35
+ className={cn(styles.tag, className)}
36
+ data-size={size}
37
+ data-appearance={appearance}
38
+ data-removable={Boolean(onDelete)}
39
+ >
40
+ <span className={styles.label}>{label}</span>
41
+ {onDelete && (
42
+ <button type='button' className={styles.tagButton} onClick={onDelete} data-test-id='tag-remove-button'>
43
+ {size === Size.Xs ? (
44
+ <CrossSVG size={IconSize[size]} className={styles.icon} />
45
+ ) : (
46
+ <CrossSVG size={IconSize[size]} className={styles.icon} />
47
+ )}
48
+ </button>
49
+ )}
50
+ </span>
51
+ );
52
+ }
53
+
54
+ Tag.sizes = Size;
55
+ Tag.appearances = Appearance;
@@ -0,0 +1,6 @@
1
+ import { Size } from '../../constants';
2
+
3
+ export const IconSize = {
4
+ [Size.Xs]: 16,
5
+ [Size.S]: 24,
6
+ };
@@ -0,0 +1 @@
1
+ export * from './Tag';
@@ -0,0 +1,175 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-tag';
2
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
+
4
+ $sizes: 'xs', 's';
5
+
6
+ $label-typography: (
7
+ 'xs': $sans-label-s,
8
+ 's': $sans-label-m
9
+ );
10
+
11
+ $appearances: (
12
+ 'neutral': (
13
+ 'container': $sys-neutral-decor-default,
14
+ 'container-disabled': $sys-neutral-decor-disabled,
15
+ 'label': $sys-neutral-text-support,
16
+ 'icon': $sys-neutral-text-light,
17
+ 'icon-active': $sys-neutral-text-main,
18
+ 'button-hovered': $sys-neutral-decor-hovered,
19
+ 'button-active': $sys-neutral-decor-activated,
20
+ ),
21
+ 'primary': (
22
+ 'container': $sys-primary-decor-default,
23
+ 'container-disabled': $sys-primary-decor-disabled,
24
+ 'label': $sys-primary-text-support,
25
+ 'icon': $sys-primary-text-light,
26
+ 'icon-active': $sys-primary-text-main,
27
+ 'button-hovered': $sys-primary-decor-hovered,
28
+ 'button-active': $sys-primary-decor-activated,
29
+ ),
30
+ 'red': (
31
+ 'container': $sys-red-decor-default,
32
+ 'container-disabled': $sys-red-decor-disabled,
33
+ 'label': $sys-red-text-support,
34
+ 'icon': $sys-red-text-light,
35
+ 'icon-active': $sys-red-text-main,
36
+ 'button-hovered': $sys-red-decor-hovered,
37
+ 'button-active': $sys-red-decor-activated,
38
+ ),
39
+ 'orange': (
40
+ 'container': $sys-orange-decor-default,
41
+ 'container-disabled': $sys-orange-decor-disabled,
42
+ 'label': $sys-orange-text-support,
43
+ 'icon': $sys-orange-text-light,
44
+ 'icon-active': $sys-orange-text-main,
45
+ 'button-hovered': $sys-orange-decor-hovered,
46
+ 'button-active': $sys-orange-decor-activated,
47
+ ),
48
+ 'yellow': (
49
+ 'container': $sys-yellow-decor-default,
50
+ 'container-disabled': $sys-yellow-decor-disabled,
51
+ 'label': $sys-yellow-text-support,
52
+ 'icon': $sys-yellow-text-light,
53
+ 'icon-active': $sys-yellow-text-main,
54
+ 'button-hovered': $sys-yellow-decor-hovered,
55
+ 'button-active': $sys-yellow-decor-activated,
56
+ ),
57
+ 'green': (
58
+ 'container': $sys-green-decor-default,
59
+ 'container-disabled': $sys-green-decor-disabled,
60
+ 'label': $sys-green-text-support,
61
+ 'icon': $sys-green-text-light,
62
+ 'icon-active': $sys-green-text-main,
63
+ 'button-hovered': $sys-green-decor-hovered,
64
+ 'button-active': $sys-green-decor-activated,
65
+ ),
66
+ 'blue': (
67
+ 'container': $sys-blue-decor-default,
68
+ 'container-disabled': $sys-blue-decor-disabled,
69
+ 'label': $sys-blue-text-support,
70
+ 'icon': $sys-blue-text-light,
71
+ 'icon-active': $sys-blue-text-main,
72
+ 'button-hovered': $sys-blue-decor-hovered,
73
+ 'button-active': $sys-blue-decor-activated,
74
+ ),
75
+ 'violet': (
76
+ 'container': $sys-violet-decor-default,
77
+ 'container-disabled': $sys-violet-decor-disabled,
78
+ 'label': $sys-violet-text-support,
79
+ 'icon': $sys-violet-text-light,
80
+ 'icon-active': $sys-violet-text-main,
81
+ 'button-hovered': $sys-violet-decor-hovered,
82
+ 'button-active': $sys-violet-decor-activated,
83
+ ),
84
+ 'pink': (
85
+ 'container': $sys-pink-decor-default,
86
+ 'container-disabled': $sys-pink-decor-disabled,
87
+ 'label': $sys-pink-text-support,
88
+ 'icon': $sys-pink-text-light,
89
+ 'icon-active': $sys-pink-text-main,
90
+ 'button-hovered': $sys-pink-decor-hovered,
91
+ 'button-active': $sys-pink-decor-activated,
92
+ ),
93
+ );
94
+
95
+ .tag {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ box-sizing: border-box;
99
+
100
+ .tagButton {
101
+ cursor: pointer;
102
+
103
+ position: relative;
104
+
105
+ box-sizing: border-box;
106
+ margin: 0;
107
+ padding: 0;
108
+
109
+ background: none;
110
+ border: none;
111
+
112
+ &:focus {
113
+ @include outline-inside-var($container-focused-s);
114
+ }
115
+
116
+ &:active {
117
+ outline: none;
118
+ }
119
+ }
120
+
121
+ .icon {
122
+ position: absolute;
123
+ top: 50%;
124
+ left: 50%;
125
+ transform: translate(-50%, -50%);
126
+ }
127
+
128
+ @each $size in $sizes {
129
+ &[data-size=#{$size}] {
130
+ @include composite-var($tag, 'container', $size, 'label-only');
131
+
132
+ &[data-removable="true"] {
133
+ @include composite-var($tag, 'container', $size, 'button-after');
134
+ }
135
+
136
+ .label {
137
+ @include composite-var($label-typography, $size);
138
+ }
139
+
140
+ .tagButton {
141
+ @include composite-var($tag-button, 'container', $size, 'icon-only');
142
+ }
143
+ }
144
+ }
145
+
146
+ @each $appearanceKey, $appearance in $appearances {
147
+ &[data-appearance=#{$appearanceKey}] {
148
+ background-color: simple-var($appearance, 'container');
149
+
150
+ &[data-removable="false"] {
151
+ background-color: simple-var($appearance, 'container-disabled');
152
+ }
153
+
154
+ .label {
155
+ color: simple-var($appearance, 'label');
156
+ }
157
+
158
+ .tagButton {
159
+ color: simple-var($appearance, 'icon');
160
+
161
+ &:focus, &:hover, &:active {
162
+ color: simple-var($appearance, 'icon-active');
163
+ }
164
+
165
+ &:focus, &:hover {
166
+ background-color: simple-var($appearance, 'button-hovered');
167
+ }
168
+
169
+ &:active {
170
+ background-color: simple-var($appearance, 'button-active');
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,34 @@
1
+ import { WithSupportProps } from '@snack-uikit/utils';
2
+
3
+ import { Appearance, Size } from '../../constants';
4
+ import { TagRowSimple, TagRowTruncated } from '../../helperComponents';
5
+ import { TagRowItem } from '../../types';
6
+ import { mapTagRowItem } from './utils';
7
+
8
+ export type TagRowProps = WithSupportProps<{
9
+ /** Массив тэгов */
10
+ items: TagRowItem[];
11
+ /** Лимит количества рядов тэгов. При достижении лимита тэги скрываются под кнопкой. Отсутствие лимита или значение равное нулю отобразит весь список */
12
+ rowLimit?: number;
13
+ /** Размер */
14
+ size?: Size;
15
+ /** Текст кнопки, при наведении на которую отображается список скрытых тэгов */
16
+ moreButtonLabel?: string;
17
+ /** CSS-класс */
18
+ className?: string;
19
+ /** Коллбэк на клик по удалению тэга */
20
+ onItemRemove?(item: string): void;
21
+ }>;
22
+
23
+ export function TagRow({ items, rowLimit, size = Size.Xs, ...props }: TagRowProps) {
24
+ const coloredItems = items.map(mapTagRowItem);
25
+
26
+ if (rowLimit) {
27
+ return <TagRowTruncated items={coloredItems} rowLimit={rowLimit} size={size} {...props} />;
28
+ }
29
+
30
+ return <TagRowSimple items={coloredItems} size={size} {...props} />;
31
+ }
32
+
33
+ TagRow.sizes = Size;
34
+ TagRow.appearances = Appearance;
@@ -0,0 +1,5 @@
1
+ export const TAG_ROW_TEST_IDS = {
2
+ moreButton: 'tag-row__more-button',
3
+ visibleTagsWrapper: 'tag-row__visible-row',
4
+ droplistTagsWrapper: 'tag-row__droplist-contents',
5
+ };
@@ -0,0 +1 @@
1
+ export * from './TagRow';
@@ -0,0 +1,16 @@
1
+ import { Appearance } from '../../constants';
2
+ import { TagRowItem, TagRowItemInner } from '../../types';
3
+
4
+ export const mapTagRowItem = ({ label, color }: TagRowItem): TagRowItemInner => {
5
+ if (color) {
6
+ return {
7
+ label,
8
+ color,
9
+ };
10
+ }
11
+
12
+ return {
13
+ label,
14
+ color: Appearance.Neutral,
15
+ };
16
+ };
@@ -0,0 +1,2 @@
1
+ export * from './Tag';
2
+ export * from './TagRow';
@@ -0,0 +1,16 @@
1
+ export enum Appearance {
2
+ Neutral = 'neutral',
3
+ Primary = 'primary',
4
+ Red = 'red',
5
+ Orange = 'orange',
6
+ Yellow = 'yellow',
7
+ Green = 'green',
8
+ Blue = 'blue',
9
+ Violet = 'violet',
10
+ Pink = 'pink',
11
+ }
12
+
13
+ export enum Size {
14
+ Xs = 'xs',
15
+ S = 's',
16
+ }
@@ -0,0 +1,32 @@
1
+ import { Ref } from 'react';
2
+
3
+ import { Tag } from '../../components';
4
+ import { Size } from '../../constants';
5
+ import { TagRowItemInner } from '../../types';
6
+
7
+ type SetTagRef = (item: TagRowItemInner, index: number) => Ref<HTMLDivElement>;
8
+
9
+ type TagListProps = {
10
+ items: TagRowItemInner[];
11
+ size?: Size;
12
+ onItemRemove?(item: string): void;
13
+ setTagRef?: SetTagRef;
14
+ };
15
+
16
+ type OnDeleteHandler = () => void;
17
+
18
+ function renderTag(size: Size, handleRemoveItem?: (item: TagRowItemInner) => OnDeleteHandler, setRef?: SetTagRef) {
19
+ return function TagRowItem(item: TagRowItemInner, index: number) {
20
+ return (
21
+ <div key={item.label} ref={setRef?.(item, index)}>
22
+ <Tag label={item.label} appearance={item.color} size={size} onDelete={handleRemoveItem?.(item)} />
23
+ </div>
24
+ );
25
+ };
26
+ }
27
+
28
+ export function TagList({ items, size = Size.Xs, onItemRemove, setTagRef }: TagListProps) {
29
+ const handleRemoveItem = onItemRemove ? (item: TagRowItemInner) => () => onItemRemove(item.label) : undefined;
30
+
31
+ return <>{items.map(renderTag(size, handleRemoveItem, setTagRef))}</>;
32
+ }
@@ -0,0 +1 @@
1
+ export * from './TagList';
@@ -0,0 +1,54 @@
1
+ import { Ref } from 'react';
2
+
3
+ import { PopoverPrivate } from '@snack-uikit/popover-private';
4
+ import { Scroll } from '@snack-uikit/scroll';
5
+
6
+ import { TAG_ROW_TEST_IDS } from '../../components/TagRow/constants';
7
+ import { Size } from '../../constants';
8
+ import { TagRowItemInner } from '../../types';
9
+ import { TagRowSimple } from '../TagRowSimple';
10
+ import styles from './styles.module.scss';
11
+
12
+ type TagMoreProps = {
13
+ items: TagRowItemInner[];
14
+ size: Size;
15
+ text?: string;
16
+ buttonRef?: Ref<HTMLButtonElement>;
17
+ onItemRemove?(item: string): void;
18
+ };
19
+
20
+ export function TagMore({ items, text = '', size = Size.Xs, buttonRef, onItemRemove }: TagMoreProps) {
21
+ return (
22
+ <PopoverPrivate
23
+ placement={PopoverPrivate.placements.BottomEnd}
24
+ trigger={PopoverPrivate.triggers.HoverAndFocusVisible}
25
+ triggerClassName={styles.triggerClassName}
26
+ popoverContent={
27
+ <div className={styles.tagRowDroplistContainer} data-size={size}>
28
+ <Scroll
29
+ className={styles.tagRowDroplistScroll}
30
+ size={Scroll.sizes.S}
31
+ barHideStrategy={Scroll.barHideStrategies.Move}
32
+ resize={Scroll.resizes.None}
33
+ >
34
+ <TagRowSimple
35
+ items={items}
36
+ size={size}
37
+ onItemRemove={onItemRemove}
38
+ data-test-id={TAG_ROW_TEST_IDS.droplistTagsWrapper}
39
+ />
40
+ </Scroll>
41
+ </div>
42
+ }
43
+ hasArrow={false}
44
+ >
45
+ <button
46
+ type='button'
47
+ className={styles.button}
48
+ ref={buttonRef}
49
+ data-size={size}
50
+ data-test-id={TAG_ROW_TEST_IDS.moreButton}
51
+ >{`${text}${items.length}`}</button>
52
+ </PopoverPrivate>
53
+ );
54
+ }
@@ -0,0 +1 @@
1
+ export * from './TagMore';