superdesk-ui-framework 5.2.0 → 5.2.2

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 (35) hide show
  1. package/app/styles/app.scss +1 -0
  2. package/app/styles/components/_overflow-stack.scss +120 -0
  3. package/app-typescript/components/CopyableTextBox.tsx +99 -0
  4. package/app-typescript/components/IconButton.tsx +21 -27
  5. package/app-typescript/components/OverflowStack/OverflowStack.tsx +418 -0
  6. package/app-typescript/components/OverflowStack/OverflowStackPopover.tsx +45 -0
  7. package/app-typescript/components/OverflowStack/utils.tsx +16 -0
  8. package/app-typescript/components/SearchBar.tsx +149 -99
  9. package/app-typescript/index.ts +2 -0
  10. package/app-typescript/translations.ts +3 -0
  11. package/dist/components/CopyableTextBox.tsx +98 -0
  12. package/dist/components/Index.tsx +16 -1
  13. package/dist/components/OverflowStack.tsx +915 -0
  14. package/dist/components/SearchBar.tsx +394 -0
  15. package/dist/examples.bundle.js +5066 -3900
  16. package/dist/superdesk-ui.bundle.css +85 -0
  17. package/dist/superdesk-ui.bundle.js +4079 -3539
  18. package/dist/vendor.bundle.js +19 -19
  19. package/package.json +2 -2
  20. package/react/components/CopyableTextBox.d.ts +12 -0
  21. package/react/components/CopyableTextBox.js +88 -0
  22. package/react/components/IconButton.d.ts +1 -7
  23. package/react/components/IconButton.js +11 -36
  24. package/react/components/OverflowStack/OverflowStack.d.ts +87 -0
  25. package/react/components/OverflowStack/OverflowStack.js +305 -0
  26. package/react/components/OverflowStack/OverflowStackPopover.d.ts +14 -0
  27. package/react/components/OverflowStack/OverflowStackPopover.js +78 -0
  28. package/react/components/OverflowStack/utils.d.ts +4 -0
  29. package/react/components/OverflowStack/utils.js +49 -0
  30. package/react/components/SearchBar.d.ts +19 -19
  31. package/react/components/SearchBar.js +90 -83
  32. package/react/index.d.ts +2 -0
  33. package/react/index.js +7 -3
  34. package/react/translations.d.ts +3 -0
  35. package/react/translations.js +3 -0
@@ -58,6 +58,7 @@
58
58
  @import 'components/card-item';
59
59
  @import 'components/sd-grid-item';
60
60
  @import 'components/sd-searchbar';
61
+ @import 'components/overflow-stack';
61
62
  @import 'components/sd-collapse-box';
62
63
  @import 'components/sd-photo-preview';
63
64
  @import 'components/sd-media-carousel';
@@ -0,0 +1,120 @@
1
+ // Overflow Stack Component
2
+ // A generic component for stacking items with overflow indicator
3
+
4
+ .overflow-stack {
5
+ display: inline-flex;
6
+ align-items: center;
7
+ position: relative;
8
+
9
+ // Gap variations (for non-overlapping items)
10
+ &.overflow-stack--gap-compact {
11
+ gap: var(--gap-0-5); // 4px
12
+ }
13
+
14
+ &.overflow-stack--gap-loose {
15
+ gap: var(--gap-1); // 8px
16
+ }
17
+
18
+ &.overflow-stack--gap-none {
19
+ gap: 0; // no gap
20
+ }
21
+
22
+ // Overlapping mode (like avatars)
23
+ &.overflow-stack--overlap {
24
+ gap: 0;
25
+
26
+ .overflow-stack__item {
27
+ margin-inline-start: calc(var(--space--1) * -1);
28
+ transition: transform 0.2s ease, z-index 0.2s ease;
29
+ position: relative;
30
+ z-index: 1;
31
+
32
+ &:first-child {
33
+ margin-inline-start: 0;
34
+ }
35
+
36
+ &:hover {
37
+ transform: translateY(-2px);
38
+ z-index: 10;
39
+ > * {
40
+ outline: 2px solid var(--color-surface-base);
41
+ }
42
+ }
43
+ }
44
+
45
+ .overflow-stack__indicator {
46
+ margin-inline-start: calc(var(--space--1) * -1);
47
+ z-index: 0;
48
+
49
+ &:hover {
50
+ transform: translateY(-2px);
51
+ z-index: 10;
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ .overflow-stack__item {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ }
61
+
62
+ .overflow-stack__indicator {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ min-width: 3.2rem;
67
+ height: 3.2rem;
68
+ padding: 0 var(--space--0-5);
69
+ background-color: var(--sd-colour-primary);
70
+ color: var(--white);
71
+ border: none;
72
+ border-radius: var(--border-radius--medium);
73
+ font-size: 1.3rem;
74
+ font-weight: 500;
75
+ cursor: pointer;
76
+ transition: background-color 0.2s ease, transform 0.2s ease;
77
+ position: relative;
78
+
79
+ &:hover {
80
+ background-color: var(--sd-colour-primary--darker);
81
+ }
82
+
83
+ &:active {
84
+ transform: scale(0.95);
85
+ }
86
+
87
+ &:focus-visible {
88
+ outline: 2px solid var(--sd-colour-primary);
89
+ outline-offset: 2px;
90
+ }
91
+ }
92
+
93
+ .overflow-stack__indicator-content {
94
+ font-size: var(--text-size--small);
95
+ color: var(--color-text-muted);
96
+ line-height: 1;
97
+ display: inline-flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+ white-space: nowrap;
101
+ padding: var(--space--0-5);
102
+ }
103
+
104
+ .overflow-stack__popover {
105
+ background-color: var(--color-dropdown-menu-Bg);
106
+ border-radius: var(--b-radius--large);
107
+ padding: var(--space--1-5);
108
+ box-shadow: var(--sd-shadow__dropdown);
109
+ display: flex;
110
+ flex-direction: column;
111
+ min-width: max-content;
112
+ overflow-y: auto;
113
+ }
114
+
115
+ .overflow-stack__popover-item {
116
+ display: flex;
117
+ align-items: center;
118
+ padding-block: var(--space--0-5);
119
+ transition: all 0.2s ease;
120
+ }
@@ -0,0 +1,99 @@
1
+ import React, {useEffect} from 'react';
2
+ import {Button} from './Button';
3
+ import {gettext} from '../translations';
4
+ import {InputWrapper} from './Form';
5
+ import nextId from 'react-id-generator';
6
+ import {IInputCommon} from './Form/InputWrapper';
7
+ import {toasted} from './Toast';
8
+
9
+ interface ICopyableTextBoxProps extends IInputCommon {
10
+ value: string;
11
+
12
+ /**
13
+ * Defaults to medium
14
+ */
15
+ size?: 'medium' | 'large';
16
+
17
+ 'data-test-id'?: string;
18
+ }
19
+
20
+ export const CopyableTextBox: React.FC<ICopyableTextBoxProps> = (props) => {
21
+ const htmlId = nextId();
22
+ const timeoutIdRef = React.useRef<number>();
23
+ const [copied, setCopied] = React.useState(false);
24
+
25
+ useEffect(() => {
26
+ return () => {
27
+ if (timeoutIdRef.current) {
28
+ window.clearTimeout(timeoutIdRef.current);
29
+ }
30
+ };
31
+ }, []);
32
+
33
+ const handleCopy = () => {
34
+ navigator.clipboard
35
+ .writeText(props.value)
36
+ .then(() => {
37
+ toasted.notify(gettext('Copied to clipboard'), {
38
+ type: 'success',
39
+ duration: 500,
40
+ });
41
+
42
+ setCopied(true);
43
+
44
+ if (timeoutIdRef.current) {
45
+ window.clearTimeout(timeoutIdRef.current);
46
+ }
47
+
48
+ timeoutIdRef.current = window.setTimeout(() => {
49
+ setCopied(false);
50
+ }, 2000);
51
+ })
52
+ .catch(() => {
53
+ toasted.notify(gettext("Couldn't copy"), {
54
+ type: 'warning',
55
+ });
56
+ });
57
+ };
58
+
59
+ return (
60
+ <InputWrapper
61
+ label={props.label}
62
+ disabled={props.disabled}
63
+ value={props.value}
64
+ error={props.error}
65
+ invalid={props.error != null}
66
+ info={props.info}
67
+ size={props.size ?? 'medium'}
68
+ fullWidth={true}
69
+ htmlId={htmlId}
70
+ tabindex={props.tabindex}
71
+ >
72
+ <div className="d-flex items-center gap-1">
73
+ <input
74
+ id={htmlId}
75
+ aria-describedby={htmlId + 'label'}
76
+ type="text"
77
+ className="sd-input__input"
78
+ style={{
79
+ border: '1px solid var(--color-input-border)',
80
+ borderRadius: 'var(--b-radius--medium)',
81
+ }}
82
+ value={props.value}
83
+ disabled
84
+ data-test-id={props['data-test-id']}
85
+ />
86
+ <Button
87
+ text={gettext('Copy')}
88
+ icon={copied ? 'ok' : 'copy'}
89
+ iconOnly={true}
90
+ onClick={handleCopy}
91
+ type="default"
92
+ style="hollow"
93
+ size={props.size === 'medium' ? 'normal' : (props.size ?? 'normal')}
94
+ data-test-id="copy-button"
95
+ />
96
+ </div>
97
+ </InputWrapper>
98
+ );
99
+ };
@@ -8,37 +8,31 @@ interface IProps {
8
8
  icon: string;
9
9
  ariaValue: string;
10
10
  toolTipFlow?: 'top' | 'left' | 'right' | 'down';
11
- toolTipAppend?: boolean;
12
11
  size?: 'default' | 'small' | 'x-large';
13
12
  style?: 'default' | 'outline' | 'outlineWhite';
14
13
  disabled?: boolean;
15
14
  onClick(event: React.MouseEvent): void;
16
15
  }
17
16
 
18
- export class IconButton extends React.PureComponent<IProps> {
19
- static defaultProps = {
20
- toolTipAppend: true,
21
- };
22
- render() {
23
- let classes = classNames('icn-btn', {
24
- [`icn-btn--${this.props.size}`]: this.props.size || this.props.size !== undefined,
25
- [`icn-btn--${this.props.style}`]: this.props.style || this.props.style !== undefined,
26
- 'icn-btn--disabled': this.props.disabled,
27
- });
17
+ export const IconButton: React.FC<IProps> = (props) => {
18
+ const classes = classNames('icn-btn', {
19
+ [`icn-btn--${props.size}`]: props.size || props.size !== undefined,
20
+ [`icn-btn--${props.style}`]: props.style || props.style !== undefined,
21
+ 'icn-btn--disabled': props.disabled,
22
+ });
28
23
 
29
- return (
30
- <Tooltip text={this.props.disabled ? null : this.props.ariaValue} flow={this.props.toolTipFlow}>
31
- <button
32
- id={this.props.id}
33
- tabIndex={0}
34
- onClick={this.props.onClick}
35
- className={classes}
36
- disabled={this.props.disabled}
37
- aria-label={this.props.ariaValue}
38
- >
39
- <Icon name={this.props.icon} ariaHidden={true} />
40
- </button>
41
- </Tooltip>
42
- );
43
- }
44
- }
24
+ return (
25
+ <Tooltip text={props.disabled ? null : props.ariaValue} flow={props.toolTipFlow}>
26
+ <button
27
+ id={props.id}
28
+ tabIndex={0}
29
+ onClick={props.onClick}
30
+ className={classes}
31
+ disabled={props.disabled}
32
+ aria-label={props.ariaValue}
33
+ >
34
+ <Icon name={props.icon} ariaHidden={true} />
35
+ </button>
36
+ </Tooltip>
37
+ );
38
+ };
@@ -0,0 +1,418 @@
1
+ import * as React from 'react';
2
+ import classNames from 'classnames';
3
+ import debounce from 'lodash/debounce';
4
+ import {WithPopover} from '../WithPopover';
5
+ import {HeadlessButton} from '../HeadlessButton';
6
+ import {IconButton} from '../IconButton';
7
+ import {OverflowStackPopover} from './OverflowStackPopover';
8
+ import {defaultIndicator, HIDDEN_ITEM_STYLE} from './utils';
9
+
10
+ interface IPropsOverflowStackBase {
11
+ /**
12
+ * Default: {type: `fixed`, max: 4}
13
+ */
14
+ overflow?:
15
+ | {
16
+ type: 'fixed';
17
+ max?: number | 'show-all';
18
+ }
19
+ | {
20
+ type: 'auto';
21
+ };
22
+
23
+ /**
24
+ * Defaults to `compact`
25
+ */
26
+ gap?: 'compact' | 'loose' | 'none';
27
+
28
+ /**
29
+ * When `true`, items will have negative margin and expand on hover
30
+ */
31
+ overlap?: boolean;
32
+
33
+ showOnlyHiddenInPopover?: boolean;
34
+
35
+ /**
36
+ * Defaults to `count`
37
+ * `count`: Shows "+N" with the number of hidden items
38
+ * `dots`: Shows a three dots icon without the count
39
+ */
40
+ indicatorStyle?: 'count' | 'dots';
41
+
42
+ renderIndicator?: (count: number) => React.ReactNode;
43
+ onIndicatorClick?: () => void;
44
+
45
+ /**
46
+ * Defaults to `full`
47
+ * Border radius for the indicator button
48
+ */
49
+ indicatorRadius?: 'x-small' | 'small' | 'medium' | 'full';
50
+
51
+ containerClassName?: string;
52
+ }
53
+
54
+ interface IPropsOverflowStackSimple extends IPropsOverflowStackBase {
55
+ type: 'simple';
56
+ items: Array<React.ReactNode>;
57
+
58
+ /**
59
+ * Custom render function for items in the popover.
60
+ * Useful if you just want to wrap children, but still keep everything else default.
61
+ * If not provided, items will be rendered as-is
62
+ */
63
+ renderPopoverItem?: (item: React.ReactNode, index: number) => React.ReactNode;
64
+ }
65
+
66
+ interface IPropsOverflowStackData<T> extends IPropsOverflowStackBase {
67
+ type: 'data';
68
+ items: Array<T>;
69
+
70
+ renderVisibleItem: (data: T, index: number) => React.ReactNode;
71
+
72
+ /**
73
+ * If not provided, `renderVisibleItem` will be used
74
+ */
75
+ renderHiddenItem?: (data: T, index: number) => React.ReactNode;
76
+ }
77
+
78
+ export type IPropsOverflowStack<T> = IPropsOverflowStackSimple | IPropsOverflowStackData<T>;
79
+
80
+ interface IStateOverflowStack {
81
+ /**
82
+ * Number of visible items when using auto overflow
83
+ */
84
+ visibleCount: number;
85
+ }
86
+
87
+ export class OverflowStack<T> extends React.PureComponent<IPropsOverflowStack<T>, IStateOverflowStack> {
88
+ private containerRef = React.createRef<HTMLDivElement>();
89
+ private itemRefs: Array<React.RefObject<HTMLDivElement>> = [];
90
+ private indicatorRef = React.createRef<HTMLDivElement>();
91
+ private resizeObserver: ResizeObserver | null = null;
92
+ private popoverContentRef = React.createRef<OverflowStackPopover<T>>();
93
+
94
+ /**
95
+ * Use a ref to avoid closure when passing props.
96
+ */
97
+ private currentPropsRef: {current: IPropsOverflowStack<T> | null} = {current: null};
98
+
99
+ /**
100
+ * Debounced calculation to avoid excessive updates during resize
101
+ */
102
+ private debouncedCalculate = debounce(() => {
103
+ this.calculateVisibleItems();
104
+ }, 50);
105
+
106
+ constructor(props: IPropsOverflowStack<T>) {
107
+ super(props);
108
+ const itemCount = this.getItemCount(props);
109
+ this.state = {
110
+ visibleCount: itemCount,
111
+ };
112
+
113
+ this.itemRefs = Array.from({length: itemCount}, () => React.createRef<HTMLDivElement>());
114
+ this.currentPropsRef.current = props;
115
+ }
116
+
117
+ private getItemCount(props: IPropsOverflowStack<T>): number {
118
+ if (props.type === 'data') {
119
+ return props.items.length;
120
+ }
121
+
122
+ return props.items?.length ?? 0;
123
+ }
124
+
125
+ componentDidMount() {
126
+ if (this.props.overflow?.type === 'auto') {
127
+ this.setupResizeObserver();
128
+
129
+ requestAnimationFrame(() => {
130
+ this.calculateVisibleItems();
131
+ });
132
+ }
133
+ }
134
+
135
+ componentDidUpdate(prevProps: IPropsOverflowStack<T>) {
136
+ // Update the props ref for the popover
137
+ this.currentPropsRef.current = this.props;
138
+ this.popoverContentRef.current?.forceUpdate?.();
139
+
140
+ const isAutoMode = this.props.overflow?.type === 'auto';
141
+
142
+ if (prevProps.overflow !== this.props.overflow) {
143
+ if (isAutoMode) {
144
+ this.setupResizeObserver();
145
+ } else {
146
+ this.cleanupResizeObserver();
147
+ }
148
+ }
149
+
150
+ if (isAutoMode) {
151
+ const prevItemCount = this.getItemCount(prevProps);
152
+ const currentItemCount = this.getItemCount(this.props);
153
+
154
+ if (prevItemCount !== currentItemCount) {
155
+ // Recreate refs if item count changed
156
+ this.itemRefs = Array.from({length: currentItemCount}, () => React.createRef<HTMLDivElement>());
157
+
158
+ this.setState({visibleCount: currentItemCount}, () => {
159
+ requestAnimationFrame(() => this.calculateVisibleItems());
160
+ });
161
+ } else {
162
+ requestAnimationFrame(() => this.calculateVisibleItems());
163
+ }
164
+ }
165
+ }
166
+
167
+ componentWillUnmount() {
168
+ this.cleanupResizeObserver();
169
+ this.debouncedCalculate.cancel();
170
+ }
171
+
172
+ private setupResizeObserver() {
173
+ if (this.resizeObserver) {
174
+ return;
175
+ }
176
+
177
+ this.resizeObserver = new ResizeObserver(() => {
178
+ this.debouncedCalculate();
179
+ });
180
+
181
+ if (this.containerRef.current) {
182
+ this.resizeObserver.observe(this.containerRef.current);
183
+ }
184
+ }
185
+
186
+ private cleanupResizeObserver() {
187
+ if (this.resizeObserver) {
188
+ this.resizeObserver.disconnect();
189
+ this.resizeObserver = null;
190
+ }
191
+ }
192
+
193
+ private getGapSize(): number {
194
+ const {gap = 'compact', overlap = false} = this.props;
195
+
196
+ if (overlap) {
197
+ return -8; // Overlapping items have negative margin
198
+ }
199
+
200
+ switch (gap) {
201
+ case 'compact':
202
+ return 4;
203
+ case 'loose':
204
+ return 8;
205
+ case 'none':
206
+ return 0;
207
+ default:
208
+ return 4;
209
+ }
210
+ }
211
+
212
+ private calculateVisibleItems() {
213
+ if (!this.containerRef.current || this.props.overflow?.type !== 'auto') {
214
+ return;
215
+ }
216
+
217
+ const containerWidth = this.containerRef.current.offsetWidth;
218
+
219
+ // If container has no width yet, skip calculation
220
+ if (containerWidth === 0) {
221
+ return;
222
+ }
223
+
224
+ const gapSize = this.getGapSize();
225
+ const indicatorWidth = this.indicatorRef.current?.offsetWidth || 40;
226
+ const totalItemCount = this.getItemCount(this.props);
227
+
228
+ let totalWidth = 0;
229
+ let visibleCount = 0;
230
+
231
+ // Calculate how many items fit
232
+ for (let i = 0; i < this.itemRefs.length; i++) {
233
+ const itemRef = this.itemRefs[i];
234
+
235
+ if (!itemRef.current) {
236
+ visibleCount = totalItemCount;
237
+ break;
238
+ }
239
+
240
+ const itemWidth = itemRef.current.offsetWidth;
241
+
242
+ if (itemWidth === 0) {
243
+ visibleCount = totalItemCount;
244
+ break;
245
+ }
246
+
247
+ const widthWithGap = itemWidth + (i > 0 ? gapSize : 0);
248
+ const needsIndicator = i < totalItemCount - 1;
249
+ const availableWidth = containerWidth - (needsIndicator ? indicatorWidth + gapSize : 0);
250
+
251
+ if (totalWidth + widthWithGap <= availableWidth) {
252
+ totalWidth += widthWithGap;
253
+ visibleCount = i + 1;
254
+ } else {
255
+ break;
256
+ }
257
+ }
258
+
259
+ if (visibleCount === 0 && totalItemCount > 0) {
260
+ visibleCount = 1;
261
+ }
262
+
263
+ if (this.state.visibleCount !== visibleCount) {
264
+ this.setState({visibleCount});
265
+ }
266
+ }
267
+
268
+ render() {
269
+ const {
270
+ gap = 'compact',
271
+ overlap = false,
272
+ overflow = {
273
+ type: 'fixed',
274
+ max: 4,
275
+ },
276
+ showOnlyHiddenInPopover = false,
277
+ indicatorStyle = 'count',
278
+ renderIndicator,
279
+ onIndicatorClick,
280
+ indicatorRadius = 'full',
281
+ containerClassName: className,
282
+ } = this.props;
283
+
284
+ const itemCount = this.getItemCount(this.props);
285
+
286
+ let max: number;
287
+
288
+ if (overflow.type === 'auto') {
289
+ max = this.state.visibleCount;
290
+ } else if (overflow.max === 'show-all') {
291
+ max = itemCount;
292
+ } else {
293
+ max = overflow.max ?? 4;
294
+ }
295
+
296
+ const itemsOverLimit = itemCount - max;
297
+
298
+ const renderVisibleItems = () => {
299
+ const renderItemWrapper = (content: React.ReactNode, index: number) => {
300
+ const isVisible = index < max;
301
+
302
+ return (
303
+ <div
304
+ key={index}
305
+ ref={overflow.type === 'auto' ? this.itemRefs[index] : undefined}
306
+ className="overflow-stack__item"
307
+ style={overflow.type === 'auto' && !isVisible ? HIDDEN_ITEM_STYLE : undefined}
308
+ >
309
+ {content}
310
+ </div>
311
+ );
312
+ };
313
+
314
+ const {props} = this;
315
+
316
+ if (props.type === 'data') {
317
+ const itemsToRender = overflow.type === 'auto' ? props.items : props.items.slice(0, max);
318
+
319
+ return itemsToRender.map((data, index) =>
320
+ renderItemWrapper(props.renderVisibleItem(data, index), index),
321
+ );
322
+ } else {
323
+ const itemsToRender = overflow.type === 'auto' ? props.items : props.items.slice(0, max);
324
+
325
+ return itemsToRender.map((item, index) => renderItemWrapper(item, index));
326
+ }
327
+ };
328
+
329
+ const ariaLabel = showOnlyHiddenInPopover
330
+ ? `Show ${itemsOverLimit} hidden items`
331
+ : `Show ${itemsOverLimit} more items`;
332
+
333
+ const renderIndicatorButton = (onToggle: (ref: HTMLElement) => void) => {
334
+ const handleClick = (e: React.MouseEvent<HTMLElement>) => {
335
+ e.stopPropagation();
336
+
337
+ if (onIndicatorClick == null) {
338
+ onToggle(e.currentTarget);
339
+ } else {
340
+ onIndicatorClick();
341
+ }
342
+ };
343
+
344
+ let indicator: React.ReactNode;
345
+
346
+ if (indicatorStyle === 'dots') {
347
+ indicator = <IconButton size="small" icon="dots" ariaValue={ariaLabel} onClick={handleClick} />;
348
+ } else {
349
+ const content = renderIndicator?.(itemsOverLimit) ?? defaultIndicator(itemsOverLimit);
350
+
351
+ indicator = (
352
+ <HeadlessButton
353
+ radius={indicatorRadius}
354
+ onClick={handleClick}
355
+ ariaLabel={ariaLabel}
356
+ tooltip={ariaLabel}
357
+ >
358
+ {content}
359
+ </HeadlessButton>
360
+ );
361
+ }
362
+
363
+ // Wrap in a div with ref for auto mode (needed for width measurement)
364
+ if (overflow.type === 'auto') {
365
+ return (
366
+ <div ref={this.indicatorRef} style={{display: 'inline-flex'}}>
367
+ {indicator}
368
+ </div>
369
+ );
370
+ }
371
+
372
+ return indicator;
373
+ };
374
+
375
+ const classes = classNames(
376
+ 'overflow-stack',
377
+ {
378
+ 'overflow-stack--overlap': overlap,
379
+ [`overflow-stack--gap-${gap}`]: !overlap,
380
+ },
381
+ className,
382
+ );
383
+
384
+ return (
385
+ <WithPopover
386
+ component={({closePopup}) => (
387
+ <OverflowStackPopover
388
+ ref={this.popoverContentRef}
389
+ propsRef={this.currentPropsRef}
390
+ max={max}
391
+ showOnlyHiddenInPopover={showOnlyHiddenInPopover}
392
+ closePopup={closePopup}
393
+ />
394
+ )}
395
+ >
396
+ {(onToggle) => {
397
+ const stackContent = (
398
+ <div
399
+ ref={this.containerRef}
400
+ className={classes}
401
+ role="group"
402
+ style={overflow.type === 'auto' ? {width: '100%'} : undefined}
403
+ >
404
+ {renderVisibleItems()}
405
+ {itemsOverLimit > 0 && renderIndicatorButton(onToggle)}
406
+ </div>
407
+ );
408
+
409
+ if (overflow.type === 'auto') {
410
+ return <div style={{display: 'flex', width: '100%', minWidth: 0}}>{stackContent}</div>;
411
+ }
412
+
413
+ return stackContent;
414
+ }}
415
+ </WithPopover>
416
+ );
417
+ }
418
+ }