@snack-uikit/tag 0.15.19 → 0.15.20
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 +11 -0
- package/dist/cjs/helperComponents/TagList/TagList.d.ts +4 -2
- package/dist/cjs/helperComponents/TagList/TagList.js +5 -3
- package/dist/cjs/helperComponents/TagList/styles.module.css +1 -0
- package/dist/cjs/helperComponents/TagRowTruncated/TagRowTruncated.js +33 -8
- package/dist/cjs/helperComponents/TagRowTruncated/styles.module.css +5 -0
- package/dist/esm/helperComponents/TagList/TagList.d.ts +4 -2
- package/dist/esm/helperComponents/TagList/TagList.js +4 -4
- package/dist/esm/helperComponents/TagList/styles.module.css +1 -0
- package/dist/esm/helperComponents/TagRowTruncated/TagRowTruncated.js +26 -6
- package/dist/esm/helperComponents/TagRowTruncated/styles.module.css +5 -0
- package/package.json +2 -2
- package/src/helperComponents/TagList/TagList.tsx +17 -5
- package/src/helperComponents/TagList/styles.module.scss +1 -0
- package/src/helperComponents/TagRowTruncated/TagRowTruncated.tsx +40 -10
- package/src/helperComponents/TagRowTruncated/styles.module.scss +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## 0.15.20 (2026-05-28)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **FF-8536:** fix first long tag is now visible ([263f6b9](https://github.com/cloud-ru-tech/snack-uikit/commit/263f6b9dbfcb001ec2a79e82a11bdd9c0f080b50))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## 0.15.19 (2026-05-18)
|
|
7
18
|
|
|
8
19
|
### Only dependencies have been changed
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Ref } from 'react';
|
|
1
|
+
import { type CSSProperties, type Ref } from 'react';
|
|
2
2
|
import { Size, TagRowItemInner } from '../../types';
|
|
3
3
|
type SetTagRef = (item: TagRowItemInner, index: number) => Ref<HTMLDivElement>;
|
|
4
|
+
type GetTagStyle = (item: TagRowItemInner, index: number) => CSSProperties | undefined;
|
|
4
5
|
type TagListProps = {
|
|
5
6
|
items: TagRowItemInner[];
|
|
6
7
|
size?: Size;
|
|
7
8
|
onItemRemove?(item: string): void;
|
|
8
9
|
setTagRef?: SetTagRef;
|
|
10
|
+
getTagStyle?: GetTagStyle;
|
|
9
11
|
};
|
|
10
|
-
export declare function TagList({ items, size, onItemRemove, setTagRef }: TagListProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function TagList({ items, size, onItemRemove, setTagRef, getTagStyle }: TagListProps): import("react/jsx-runtime").JSX.Element;
|
|
11
13
|
export {};
|
|
@@ -13,12 +13,13 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
13
13
|
const components_1 = require("../../components");
|
|
14
14
|
const constants_1 = require("../../constants");
|
|
15
15
|
const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
|
|
16
|
-
function renderTag(size, handleRemoveItem, setRef) {
|
|
16
|
+
function renderTag(size, handleRemoveItem, setRef, getStyle) {
|
|
17
17
|
return function TagRowItem(item, index) {
|
|
18
18
|
var _a;
|
|
19
19
|
return (0, jsx_runtime_1.jsx)("div", {
|
|
20
20
|
ref: setRef === null || setRef === void 0 ? void 0 : setRef(item, index),
|
|
21
21
|
className: styles_module_scss_1.default.tagWrapper,
|
|
22
|
+
style: getStyle === null || getStyle === void 0 ? void 0 : getStyle(item, index),
|
|
22
23
|
children: (0, jsx_runtime_1.jsx)(components_1.Tag, Object.assign({
|
|
23
24
|
size: size,
|
|
24
25
|
onDelete: handleRemoveItem === null || handleRemoveItem === void 0 ? void 0 : handleRemoveItem(item)
|
|
@@ -31,10 +32,11 @@ function TagList(_ref) {
|
|
|
31
32
|
items,
|
|
32
33
|
size = constants_1.SIZE.Xs,
|
|
33
34
|
onItemRemove,
|
|
34
|
-
setTagRef
|
|
35
|
+
setTagRef,
|
|
36
|
+
getTagStyle
|
|
35
37
|
} = _ref;
|
|
36
38
|
const handleRemoveItem = onItemRemove ? item => () => onItemRemove(item.label) : undefined;
|
|
37
39
|
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {
|
|
38
|
-
children: items.map(renderTag(size, handleRemoveItem, setTagRef))
|
|
40
|
+
children: items.map(renderTag(size, handleRemoveItem, setTagRef, getTagStyle))
|
|
39
41
|
});
|
|
40
42
|
}
|
|
@@ -27,6 +27,7 @@ const TagMore_1 = require("../TagMore");
|
|
|
27
27
|
const hooks_1 = require("./hooks");
|
|
28
28
|
const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
|
|
29
29
|
const utils_2 = require("./utils");
|
|
30
|
+
const FIRST_TRUNCATED_TAG_WIDTH = 256;
|
|
30
31
|
function TagRowTruncatedInner(_a) {
|
|
31
32
|
var {
|
|
32
33
|
items,
|
|
@@ -39,10 +40,10 @@ function TagRowTruncatedInner(_a) {
|
|
|
39
40
|
rest = __rest(_a, ["items", "rowLimit", "size", "moreButtonLabel", "className", "onItemRemove"]);
|
|
40
41
|
const [visibleTags, setVisibleTags] = (0, react_1.useState)([]);
|
|
41
42
|
const [hiddenTags, setHiddenTags] = (0, react_1.useState)([]);
|
|
43
|
+
const [firstVisibleTagMaxWidth, setFirstVisibleTagMaxWidth] = (0, react_1.useState)();
|
|
42
44
|
const hiddenRowElementRef = (0, react_1.useRef)(null);
|
|
43
|
-
const hiddenMoreButtonRef = (0, react_1.useRef)(null);
|
|
44
|
-
const hiddenMoreButtonWrapperRef = (0, react_1.useRef)(null);
|
|
45
45
|
const [firstTagElement, setFirstTagElement] = (0, react_1.useState)(null);
|
|
46
|
+
const [hiddenMoreButtonElement, setHiddenMoreButtonElement] = (0, react_1.useState)(null);
|
|
46
47
|
const tagsMapRef = (0, react_1.useRef)(new Map());
|
|
47
48
|
const {
|
|
48
49
|
width: maxWidth
|
|
@@ -50,6 +51,9 @@ function TagRowTruncatedInner(_a) {
|
|
|
50
51
|
const {
|
|
51
52
|
width: firstVisibleTagWidth
|
|
52
53
|
} = (0, hooks_1.useResizeObserver)(firstTagElement);
|
|
54
|
+
const {
|
|
55
|
+
width: moreButtonWidth
|
|
56
|
+
} = (0, hooks_1.useResizeObserver)(hiddenMoreButtonElement);
|
|
53
57
|
function setTagElement(tag, index) {
|
|
54
58
|
return tagElement => {
|
|
55
59
|
if (index === 0 && tagElement) {
|
|
@@ -64,17 +68,21 @@ function TagRowTruncatedInner(_a) {
|
|
|
64
68
|
}
|
|
65
69
|
const gapWidth = (0, utils_2.getGapWidth)(hiddenRowElementRef);
|
|
66
70
|
(0, react_1.useEffect)(() => {
|
|
67
|
-
var _a;
|
|
68
71
|
if (maxWidth < 1) {
|
|
69
72
|
return;
|
|
70
73
|
}
|
|
71
74
|
const newVisibleTags = [];
|
|
72
75
|
const newHiddenTags = [];
|
|
73
|
-
|
|
76
|
+
let newFirstVisibleTagMaxWidth;
|
|
74
77
|
let currentRowWidth = 0;
|
|
75
78
|
let currentRowCount = 1;
|
|
76
79
|
let currentRowVisibleTagAmount = 0;
|
|
80
|
+
let isFirstTagTruncated = false;
|
|
77
81
|
tagsMapRef.current.forEach((tagElement, tagRowItem) => {
|
|
82
|
+
if (isFirstTagTruncated) {
|
|
83
|
+
newHiddenTags.push(tagRowItem);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
78
86
|
const tagWidth = (tagElement === null || tagElement === void 0 ? void 0 : tagElement.offsetWidth) || 0;
|
|
79
87
|
const sumRowWidth = currentRowWidth + tagWidth + gapWidth;
|
|
80
88
|
if (currentRowCount > rowLimit) {
|
|
@@ -83,6 +91,16 @@ function TagRowTruncatedInner(_a) {
|
|
|
83
91
|
}
|
|
84
92
|
if (currentRowCount === rowLimit) {
|
|
85
93
|
if (sumRowWidth + moreButtonWidth > maxWidth) {
|
|
94
|
+
const isFirstTag = newVisibleTags.length === 0 && currentRowVisibleTagAmount === 0;
|
|
95
|
+
const firstTagMaxWidth = Math.min(FIRST_TRUNCATED_TAG_WIDTH, maxWidth - moreButtonWidth - gapWidth);
|
|
96
|
+
if (isFirstTag && firstTagMaxWidth > 0) {
|
|
97
|
+
newFirstVisibleTagMaxWidth = firstTagMaxWidth;
|
|
98
|
+
currentRowWidth = firstTagMaxWidth + gapWidth;
|
|
99
|
+
currentRowVisibleTagAmount++;
|
|
100
|
+
isFirstTagTruncated = true;
|
|
101
|
+
newVisibleTags.push(tagRowItem);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
86
104
|
currentRowCount++;
|
|
87
105
|
currentRowVisibleTagAmount = 0;
|
|
88
106
|
newHiddenTags.push(tagRowItem);
|
|
@@ -106,7 +124,13 @@ function TagRowTruncatedInner(_a) {
|
|
|
106
124
|
});
|
|
107
125
|
setVisibleTags(newVisibleTags);
|
|
108
126
|
setHiddenTags(newHiddenTags);
|
|
109
|
-
|
|
127
|
+
setFirstVisibleTagMaxWidth(newHiddenTags.length > 0 ? newFirstVisibleTagMaxWidth : undefined);
|
|
128
|
+
}, [items, rowLimit, maxWidth, moreButtonWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
|
|
129
|
+
const getVisibleTagStyle = (_item, index) => index === 0 && firstVisibleTagMaxWidth ? {
|
|
130
|
+
flex: `0 0 ${firstVisibleTagMaxWidth}px`,
|
|
131
|
+
width: firstVisibleTagMaxWidth,
|
|
132
|
+
maxWidth: firstVisibleTagMaxWidth
|
|
133
|
+
} : undefined;
|
|
110
134
|
return (0, jsx_runtime_1.jsxs)("div", Object.assign({
|
|
111
135
|
className: (0, classnames_1.default)(styles_module_scss_1.default.wrapper, className)
|
|
112
136
|
}, (0, utils_1.extractSupportProps)(rest), {
|
|
@@ -123,21 +147,22 @@ function TagRowTruncatedInner(_a) {
|
|
|
123
147
|
})
|
|
124
148
|
}), (0, jsx_runtime_1.jsx)("div", {
|
|
125
149
|
className: styles_module_scss_1.default.hiddenMoreButton,
|
|
126
|
-
ref: hiddenMoreButtonWrapperRef,
|
|
127
150
|
children: (0, jsx_runtime_1.jsx)(TagMore_1.TagMore, {
|
|
128
151
|
items: items,
|
|
129
152
|
text: moreButtonLabel,
|
|
130
153
|
size: size,
|
|
131
|
-
buttonRef:
|
|
154
|
+
buttonRef: setHiddenMoreButtonElement
|
|
132
155
|
})
|
|
133
156
|
}), (0, jsx_runtime_1.jsxs)("div", {
|
|
134
157
|
className: styles_module_scss_1.default.visibleRow,
|
|
135
158
|
"data-size": size,
|
|
159
|
+
"data-shrink-first-tag": Boolean(firstVisibleTagMaxWidth),
|
|
136
160
|
"data-test-id": constants_1.TAG_ROW_TEST_IDS.visibleTagsWrapper,
|
|
137
161
|
children: [(0, jsx_runtime_1.jsx)(TagList_1.TagList, {
|
|
138
162
|
items: visibleTags,
|
|
139
163
|
size: size,
|
|
140
|
-
onItemRemove: onItemRemove
|
|
164
|
+
onItemRemove: onItemRemove,
|
|
165
|
+
getTagStyle: getVisibleTagStyle
|
|
141
166
|
}), hiddenTags.length > 0 && (0, jsx_runtime_1.jsx)(TagMore_1.TagMore, {
|
|
142
167
|
items: hiddenTags,
|
|
143
168
|
text: moreButtonLabel,
|
|
@@ -16,8 +16,13 @@
|
|
|
16
16
|
.visibleRow{
|
|
17
17
|
display:flex;
|
|
18
18
|
flex-wrap:wrap;
|
|
19
|
+
width:100%;
|
|
20
|
+
min-width:0;
|
|
19
21
|
max-width:100%;
|
|
20
22
|
}
|
|
23
|
+
.visibleRow[data-shrink-first-tag=true]{
|
|
24
|
+
flex-wrap:nowrap;
|
|
25
|
+
}
|
|
21
26
|
.visibleRow[data-size=xs]{
|
|
22
27
|
gap:var(--space-tag-row-xs-gap, 4px);
|
|
23
28
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Ref } from 'react';
|
|
1
|
+
import { type CSSProperties, type Ref } from 'react';
|
|
2
2
|
import { Size, TagRowItemInner } from '../../types';
|
|
3
3
|
type SetTagRef = (item: TagRowItemInner, index: number) => Ref<HTMLDivElement>;
|
|
4
|
+
type GetTagStyle = (item: TagRowItemInner, index: number) => CSSProperties | undefined;
|
|
4
5
|
type TagListProps = {
|
|
5
6
|
items: TagRowItemInner[];
|
|
6
7
|
size?: Size;
|
|
7
8
|
onItemRemove?(item: string): void;
|
|
8
9
|
setTagRef?: SetTagRef;
|
|
10
|
+
getTagStyle?: GetTagStyle;
|
|
9
11
|
};
|
|
10
|
-
export declare function TagList({ items, size, onItemRemove, setTagRef }: TagListProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function TagList({ items, size, onItemRemove, setTagRef, getTagStyle }: TagListProps): import("react/jsx-runtime").JSX.Element;
|
|
11
13
|
export {};
|
|
@@ -2,13 +2,13 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
2
2
|
import { Tag } from '../../components';
|
|
3
3
|
import { SIZE } from '../../constants';
|
|
4
4
|
import styles from './styles.module.css';
|
|
5
|
-
function renderTag(size, handleRemoveItem, setRef) {
|
|
5
|
+
function renderTag(size, handleRemoveItem, setRef, getStyle) {
|
|
6
6
|
return function TagRowItem(item, index) {
|
|
7
7
|
var _a;
|
|
8
|
-
return (_jsx("div", { ref: setRef === null || setRef === void 0 ? void 0 : setRef(item, index), className: styles.tagWrapper, children: _jsx(Tag, Object.assign({ size: size, onDelete: handleRemoveItem === null || handleRemoveItem === void 0 ? void 0 : handleRemoveItem(item) }, item)) }, (_a = item.id) !== null && _a !== void 0 ? _a : item.label));
|
|
8
|
+
return (_jsx("div", { ref: setRef === null || setRef === void 0 ? void 0 : setRef(item, index), className: styles.tagWrapper, style: getStyle === null || getStyle === void 0 ? void 0 : getStyle(item, index), children: _jsx(Tag, Object.assign({ size: size, onDelete: handleRemoveItem === null || handleRemoveItem === void 0 ? void 0 : handleRemoveItem(item) }, item)) }, (_a = item.id) !== null && _a !== void 0 ? _a : item.label));
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
export function TagList({ items, size = SIZE.Xs, onItemRemove, setTagRef }) {
|
|
11
|
+
export function TagList({ items, size = SIZE.Xs, onItemRemove, setTagRef, getTagStyle }) {
|
|
12
12
|
const handleRemoveItem = onItemRemove ? (item) => () => onItemRemove(item.label) : undefined;
|
|
13
|
-
return _jsx(_Fragment, { children: items.map(renderTag(size, handleRemoveItem, setTagRef)) });
|
|
13
|
+
return _jsx(_Fragment, { children: items.map(renderTag(size, handleRemoveItem, setTagRef, getTagStyle)) });
|
|
14
14
|
}
|
|
@@ -19,17 +19,19 @@ import { TagMore } from '../TagMore';
|
|
|
19
19
|
import { useResizeObserver } from './hooks';
|
|
20
20
|
import styles from './styles.module.css';
|
|
21
21
|
import { getGapWidth } from './utils';
|
|
22
|
+
const FIRST_TRUNCATED_TAG_WIDTH = 256;
|
|
22
23
|
function TagRowTruncatedInner(_a) {
|
|
23
24
|
var { items, rowLimit, size, moreButtonLabel = '', className, onItemRemove } = _a, rest = __rest(_a, ["items", "rowLimit", "size", "moreButtonLabel", "className", "onItemRemove"]);
|
|
24
25
|
const [visibleTags, setVisibleTags] = useState([]);
|
|
25
26
|
const [hiddenTags, setHiddenTags] = useState([]);
|
|
27
|
+
const [firstVisibleTagMaxWidth, setFirstVisibleTagMaxWidth] = useState();
|
|
26
28
|
const hiddenRowElementRef = useRef(null);
|
|
27
|
-
const hiddenMoreButtonRef = useRef(null);
|
|
28
|
-
const hiddenMoreButtonWrapperRef = useRef(null);
|
|
29
29
|
const [firstTagElement, setFirstTagElement] = useState(null);
|
|
30
|
+
const [hiddenMoreButtonElement, setHiddenMoreButtonElement] = useState(null);
|
|
30
31
|
const tagsMapRef = useRef(new Map());
|
|
31
32
|
const { width: maxWidth } = useResizeObserver(hiddenRowElementRef.current);
|
|
32
33
|
const { width: firstVisibleTagWidth } = useResizeObserver(firstTagElement);
|
|
34
|
+
const { width: moreButtonWidth } = useResizeObserver(hiddenMoreButtonElement);
|
|
33
35
|
function setTagElement(tag, index) {
|
|
34
36
|
return (tagElement) => {
|
|
35
37
|
if (index === 0 && tagElement) {
|
|
@@ -45,17 +47,21 @@ function TagRowTruncatedInner(_a) {
|
|
|
45
47
|
}
|
|
46
48
|
const gapWidth = getGapWidth(hiddenRowElementRef);
|
|
47
49
|
useEffect(() => {
|
|
48
|
-
var _a;
|
|
49
50
|
if (maxWidth < 1) {
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
const newVisibleTags = [];
|
|
53
54
|
const newHiddenTags = [];
|
|
54
|
-
|
|
55
|
+
let newFirstVisibleTagMaxWidth;
|
|
55
56
|
let currentRowWidth = 0;
|
|
56
57
|
let currentRowCount = 1;
|
|
57
58
|
let currentRowVisibleTagAmount = 0;
|
|
59
|
+
let isFirstTagTruncated = false;
|
|
58
60
|
tagsMapRef.current.forEach((tagElement, tagRowItem) => {
|
|
61
|
+
if (isFirstTagTruncated) {
|
|
62
|
+
newHiddenTags.push(tagRowItem);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
59
65
|
const tagWidth = (tagElement === null || tagElement === void 0 ? void 0 : tagElement.offsetWidth) || 0;
|
|
60
66
|
const sumRowWidth = currentRowWidth + tagWidth + gapWidth;
|
|
61
67
|
if (currentRowCount > rowLimit) {
|
|
@@ -64,6 +70,16 @@ function TagRowTruncatedInner(_a) {
|
|
|
64
70
|
}
|
|
65
71
|
if (currentRowCount === rowLimit) {
|
|
66
72
|
if (sumRowWidth + moreButtonWidth > maxWidth) {
|
|
73
|
+
const isFirstTag = newVisibleTags.length === 0 && currentRowVisibleTagAmount === 0;
|
|
74
|
+
const firstTagMaxWidth = Math.min(FIRST_TRUNCATED_TAG_WIDTH, maxWidth - moreButtonWidth - gapWidth);
|
|
75
|
+
if (isFirstTag && firstTagMaxWidth > 0) {
|
|
76
|
+
newFirstVisibleTagMaxWidth = firstTagMaxWidth;
|
|
77
|
+
currentRowWidth = firstTagMaxWidth + gapWidth;
|
|
78
|
+
currentRowVisibleTagAmount++;
|
|
79
|
+
isFirstTagTruncated = true;
|
|
80
|
+
newVisibleTags.push(tagRowItem);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
67
83
|
currentRowCount++;
|
|
68
84
|
currentRowVisibleTagAmount = 0;
|
|
69
85
|
newHiddenTags.push(tagRowItem);
|
|
@@ -87,7 +103,11 @@ function TagRowTruncatedInner(_a) {
|
|
|
87
103
|
});
|
|
88
104
|
setVisibleTags(newVisibleTags);
|
|
89
105
|
setHiddenTags(newHiddenTags);
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
setFirstVisibleTagMaxWidth(newHiddenTags.length > 0 ? newFirstVisibleTagMaxWidth : undefined);
|
|
107
|
+
}, [items, rowLimit, maxWidth, moreButtonWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
|
|
108
|
+
const getVisibleTagStyle = (_item, index) => index === 0 && firstVisibleTagMaxWidth
|
|
109
|
+
? { flex: `0 0 ${firstVisibleTagMaxWidth}px`, width: firstVisibleTagMaxWidth, maxWidth: firstVisibleTagMaxWidth }
|
|
110
|
+
: undefined;
|
|
111
|
+
return (_jsxs("div", Object.assign({ className: cn(styles.wrapper, className) }, extractSupportProps(rest), { "data-size": size, children: [_jsx("div", { className: styles.hiddenRow, ref: hiddenRowElementRef, "data-size": size, children: _jsx(TagList, { items: items, size: size, onItemRemove: onItemRemove, setTagRef: setTagElement }) }), _jsx("div", { className: styles.hiddenMoreButton, children: _jsx(TagMore, { items: items, text: moreButtonLabel, size: size, buttonRef: setHiddenMoreButtonElement }) }), _jsxs("div", { className: styles.visibleRow, "data-size": size, "data-shrink-first-tag": Boolean(firstVisibleTagMaxWidth), "data-test-id": TAG_ROW_TEST_IDS.visibleTagsWrapper, children: [_jsx(TagList, { items: visibleTags, size: size, onItemRemove: onItemRemove, getTagStyle: getVisibleTagStyle }), hiddenTags.length > 0 && (_jsx(TagMore, { items: hiddenTags, text: moreButtonLabel, size: size, onItemRemove: onItemRemove }))] })] })));
|
|
92
112
|
}
|
|
93
113
|
export const TagRowTruncated = memo(TagRowTruncatedInner);
|
|
@@ -16,8 +16,13 @@
|
|
|
16
16
|
.visibleRow{
|
|
17
17
|
display:flex;
|
|
18
18
|
flex-wrap:wrap;
|
|
19
|
+
width:100%;
|
|
20
|
+
min-width:0;
|
|
19
21
|
max-width:100%;
|
|
20
22
|
}
|
|
23
|
+
.visibleRow[data-shrink-first-tag=true]{
|
|
24
|
+
flex-wrap:nowrap;
|
|
25
|
+
}
|
|
21
26
|
.visibleRow[data-size=xs]{
|
|
22
27
|
gap:var(--space-tag-row-xs-gap, 4px);
|
|
23
28
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Tag",
|
|
7
|
-
"version": "0.15.
|
|
7
|
+
"version": "0.15.20",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"@snack-uikit/utils": "4.0.2",
|
|
44
44
|
"classnames": "2.5.1"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "3bb1e178fe404fe49aaeb237407549869243b9f4"
|
|
47
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ref } from 'react';
|
|
1
|
+
import { type CSSProperties, type Ref } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Tag } from '../../components';
|
|
4
4
|
import { SIZE } from '../../constants';
|
|
@@ -6,28 +6,40 @@ import { Size, TagRowItemInner } from '../../types';
|
|
|
6
6
|
import styles from './styles.module.scss';
|
|
7
7
|
|
|
8
8
|
type SetTagRef = (item: TagRowItemInner, index: number) => Ref<HTMLDivElement>;
|
|
9
|
+
type GetTagStyle = (item: TagRowItemInner, index: number) => CSSProperties | undefined;
|
|
9
10
|
|
|
10
11
|
type TagListProps = {
|
|
11
12
|
items: TagRowItemInner[];
|
|
12
13
|
size?: Size;
|
|
13
14
|
onItemRemove?(item: string): void;
|
|
14
15
|
setTagRef?: SetTagRef;
|
|
16
|
+
getTagStyle?: GetTagStyle;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
type OnDeleteHandler = () => void;
|
|
18
20
|
|
|
19
|
-
function renderTag(
|
|
21
|
+
function renderTag(
|
|
22
|
+
size: Size,
|
|
23
|
+
handleRemoveItem?: (item: TagRowItemInner) => OnDeleteHandler,
|
|
24
|
+
setRef?: SetTagRef,
|
|
25
|
+
getStyle?: GetTagStyle,
|
|
26
|
+
) {
|
|
20
27
|
return function TagRowItem(item: TagRowItemInner, index: number) {
|
|
21
28
|
return (
|
|
22
|
-
<div
|
|
29
|
+
<div
|
|
30
|
+
key={item.id ?? item.label}
|
|
31
|
+
ref={setRef?.(item, index)}
|
|
32
|
+
className={styles.tagWrapper}
|
|
33
|
+
style={getStyle?.(item, index)}
|
|
34
|
+
>
|
|
23
35
|
<Tag size={size} onDelete={handleRemoveItem?.(item)} {...item} />
|
|
24
36
|
</div>
|
|
25
37
|
);
|
|
26
38
|
};
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
export function TagList({ items, size = SIZE.Xs, onItemRemove, setTagRef }: TagListProps) {
|
|
41
|
+
export function TagList({ items, size = SIZE.Xs, onItemRemove, setTagRef, getTagStyle }: TagListProps) {
|
|
30
42
|
const handleRemoveItem = onItemRemove ? (item: TagRowItemInner) => () => onItemRemove(item.label) : undefined;
|
|
31
43
|
|
|
32
|
-
return <>{items.map(renderTag(size, handleRemoveItem, setTagRef))}</>;
|
|
44
|
+
return <>{items.map(renderTag(size, handleRemoveItem, setTagRef, getTagStyle))}</>;
|
|
33
45
|
}
|
|
@@ -11,6 +11,8 @@ import { useResizeObserver } from './hooks';
|
|
|
11
11
|
import styles from './styles.module.scss';
|
|
12
12
|
import { getGapWidth } from './utils';
|
|
13
13
|
|
|
14
|
+
const FIRST_TRUNCATED_TAG_WIDTH = 256;
|
|
15
|
+
|
|
14
16
|
type TagRowTruncatedProps = WithSupportProps<{
|
|
15
17
|
items: TagRowItemInner[];
|
|
16
18
|
rowLimit: number;
|
|
@@ -31,16 +33,17 @@ function TagRowTruncatedInner({
|
|
|
31
33
|
}: TagRowTruncatedProps) {
|
|
32
34
|
const [visibleTags, setVisibleTags] = useState<TagRowItemInner[]>([]);
|
|
33
35
|
const [hiddenTags, setHiddenTags] = useState<TagRowItemInner[]>([]);
|
|
36
|
+
const [firstVisibleTagMaxWidth, setFirstVisibleTagMaxWidth] = useState<number>();
|
|
34
37
|
|
|
35
38
|
const hiddenRowElementRef = useRef<HTMLDivElement>(null);
|
|
36
|
-
const hiddenMoreButtonRef = useRef<HTMLButtonElement>(null);
|
|
37
|
-
const hiddenMoreButtonWrapperRef = useRef<HTMLDivElement>(null);
|
|
38
39
|
|
|
39
40
|
const [firstTagElement, setFirstTagElement] = useState<HTMLElement | null>(null);
|
|
41
|
+
const [hiddenMoreButtonElement, setHiddenMoreButtonElement] = useState<HTMLButtonElement | null>(null);
|
|
40
42
|
const tagsMapRef = useRef(new Map<TagRowItemInner, HTMLElement | null>());
|
|
41
43
|
|
|
42
44
|
const { width: maxWidth } = useResizeObserver(hiddenRowElementRef.current);
|
|
43
45
|
const { width: firstVisibleTagWidth } = useResizeObserver(firstTagElement);
|
|
46
|
+
const { width: moreButtonWidth } = useResizeObserver(hiddenMoreButtonElement);
|
|
44
47
|
|
|
45
48
|
function setTagElement(tag: TagRowItemInner, index: number) {
|
|
46
49
|
return (tagElement: HTMLElement | null) => {
|
|
@@ -65,17 +68,21 @@ function TagRowTruncatedInner({
|
|
|
65
68
|
|
|
66
69
|
const newVisibleTags: TagRowItemInner[] = [];
|
|
67
70
|
const newHiddenTags: TagRowItemInner[] = [];
|
|
68
|
-
|
|
69
|
-
const moreButtonWidth = hiddenMoreButtonRef.current?.offsetWidth || 0;
|
|
71
|
+
let newFirstVisibleTagMaxWidth: number | undefined;
|
|
70
72
|
|
|
71
73
|
let currentRowWidth = 0;
|
|
72
74
|
let currentRowCount = 1;
|
|
73
75
|
let currentRowVisibleTagAmount = 0;
|
|
76
|
+
let isFirstTagTruncated = false;
|
|
74
77
|
|
|
75
78
|
tagsMapRef.current.forEach((tagElement, tagRowItem) => {
|
|
79
|
+
if (isFirstTagTruncated) {
|
|
80
|
+
newHiddenTags.push(tagRowItem);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
76
84
|
const tagWidth = tagElement?.offsetWidth || 0;
|
|
77
85
|
const sumRowWidth = currentRowWidth + tagWidth + gapWidth;
|
|
78
|
-
|
|
79
86
|
if (currentRowCount > rowLimit) {
|
|
80
87
|
newHiddenTags.push(tagRowItem);
|
|
81
88
|
return;
|
|
@@ -83,6 +90,18 @@ function TagRowTruncatedInner({
|
|
|
83
90
|
|
|
84
91
|
if (currentRowCount === rowLimit) {
|
|
85
92
|
if (sumRowWidth + moreButtonWidth > maxWidth) {
|
|
93
|
+
const isFirstTag = newVisibleTags.length === 0 && currentRowVisibleTagAmount === 0;
|
|
94
|
+
const firstTagMaxWidth = Math.min(FIRST_TRUNCATED_TAG_WIDTH, maxWidth - moreButtonWidth - gapWidth);
|
|
95
|
+
|
|
96
|
+
if (isFirstTag && firstTagMaxWidth > 0) {
|
|
97
|
+
newFirstVisibleTagMaxWidth = firstTagMaxWidth;
|
|
98
|
+
currentRowWidth = firstTagMaxWidth + gapWidth;
|
|
99
|
+
currentRowVisibleTagAmount++;
|
|
100
|
+
isFirstTagTruncated = true;
|
|
101
|
+
newVisibleTags.push(tagRowItem);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
86
105
|
currentRowCount++;
|
|
87
106
|
currentRowVisibleTagAmount = 0;
|
|
88
107
|
newHiddenTags.push(tagRowItem);
|
|
@@ -110,18 +129,29 @@ function TagRowTruncatedInner({
|
|
|
110
129
|
|
|
111
130
|
setVisibleTags(newVisibleTags);
|
|
112
131
|
setHiddenTags(newHiddenTags);
|
|
113
|
-
|
|
132
|
+
setFirstVisibleTagMaxWidth(newHiddenTags.length > 0 ? newFirstVisibleTagMaxWidth : undefined);
|
|
133
|
+
}, [items, rowLimit, maxWidth, moreButtonWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
|
|
134
|
+
|
|
135
|
+
const getVisibleTagStyle = (_item: TagRowItemInner, index: number) =>
|
|
136
|
+
index === 0 && firstVisibleTagMaxWidth
|
|
137
|
+
? { flex: `0 0 ${firstVisibleTagMaxWidth}px`, width: firstVisibleTagMaxWidth, maxWidth: firstVisibleTagMaxWidth }
|
|
138
|
+
: undefined;
|
|
114
139
|
|
|
115
140
|
return (
|
|
116
141
|
<div className={cn(styles.wrapper, className)} {...extractSupportProps(rest)} data-size={size}>
|
|
117
142
|
<div className={styles.hiddenRow} ref={hiddenRowElementRef} data-size={size}>
|
|
118
143
|
<TagList items={items} size={size} onItemRemove={onItemRemove} setTagRef={setTagElement} />
|
|
119
144
|
</div>
|
|
120
|
-
<div className={styles.hiddenMoreButton}
|
|
121
|
-
<TagMore items={items} text={moreButtonLabel} size={size} buttonRef={
|
|
145
|
+
<div className={styles.hiddenMoreButton}>
|
|
146
|
+
<TagMore items={items} text={moreButtonLabel} size={size} buttonRef={setHiddenMoreButtonElement} />
|
|
122
147
|
</div>
|
|
123
|
-
<div
|
|
124
|
-
|
|
148
|
+
<div
|
|
149
|
+
className={styles.visibleRow}
|
|
150
|
+
data-size={size}
|
|
151
|
+
data-shrink-first-tag={Boolean(firstVisibleTagMaxWidth)}
|
|
152
|
+
data-test-id={TAG_ROW_TEST_IDS.visibleTagsWrapper}
|
|
153
|
+
>
|
|
154
|
+
<TagList items={visibleTags} size={size} onItemRemove={onItemRemove} getTagStyle={getVisibleTagStyle} />
|
|
125
155
|
{hiddenTags.length > 0 && (
|
|
126
156
|
<TagMore items={hiddenTags} text={moreButtonLabel} size={size} onItemRemove={onItemRemove} />
|
|
127
157
|
)}
|
|
@@ -22,8 +22,15 @@ $sizes: 'xs', 's';
|
|
|
22
22
|
.visibleRow {
|
|
23
23
|
display: flex;
|
|
24
24
|
flex-wrap: wrap;
|
|
25
|
+
|
|
26
|
+
width: 100%;
|
|
27
|
+
min-width: 0;
|
|
25
28
|
max-width: 100%;
|
|
26
29
|
|
|
30
|
+
&[data-shrink-first-tag='true'] {
|
|
31
|
+
flex-wrap: nowrap;
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
@each $size in $sizes {
|
|
28
35
|
&[data-size=#{$size}] {
|
|
29
36
|
@include styles-tokens-tag.composite-var(styles-tokens-tag.$tag-row, $size);
|