@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 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
  }
@@ -1,3 +1,4 @@
1
1
  .tagWrapper{
2
+ min-width:0;
2
3
  max-width:100%;
3
4
  }
@@ -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
- const moreButtonWidth = ((_a = hiddenMoreButtonRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
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
- }, [items, rowLimit, maxWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
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: hiddenMoreButtonRef
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
  }
@@ -1,3 +1,4 @@
1
1
  .tagWrapper{
2
+ min-width:0;
2
3
  max-width:100%;
3
4
  }
@@ -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
- const moreButtonWidth = ((_a = hiddenMoreButtonRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
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
- }, [items, rowLimit, maxWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
91
- 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, ref: hiddenMoreButtonWrapperRef, children: _jsx(TagMore, { items: items, text: moreButtonLabel, size: size, buttonRef: hiddenMoreButtonRef }) }), _jsxs("div", { 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 }))] })] })));
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.19",
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": "a9f6571146845d828e00206abdedb160980cff97"
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(size: Size, handleRemoveItem?: (item: TagRowItemInner) => OnDeleteHandler, setRef?: SetTagRef) {
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 key={item.id ?? item.label} ref={setRef?.(item, index)} className={styles.tagWrapper}>
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
  }
@@ -1,3 +1,4 @@
1
1
  .tagWrapper {
2
+ min-width: 0;
2
3
  max-width: 100%;
3
4
  }
@@ -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
- }, [items, rowLimit, maxWidth, onItemRemove, gapWidth, firstVisibleTagWidth]);
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} ref={hiddenMoreButtonWrapperRef}>
121
- <TagMore items={items} text={moreButtonLabel} size={size} buttonRef={hiddenMoreButtonRef} />
145
+ <div className={styles.hiddenMoreButton}>
146
+ <TagMore items={items} text={moreButtonLabel} size={size} buttonRef={setHiddenMoreButtonElement} />
122
147
  </div>
123
- <div className={styles.visibleRow} data-size={size} data-test-id={TAG_ROW_TEST_IDS.visibleTagsWrapper}>
124
- <TagList items={visibleTags} size={size} onItemRemove={onItemRemove} />
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);