@drivenets/design-system 0.18.0 → 0.18.1
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 +6 -0
- package/dist/index.cjs +153 -187
- package/dist/index.d.cts +2 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +153 -187
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +3 -3
- package/src/components/ds-tag-filter/ds-tag-filter.module.scss +7 -35
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +34 -57
- package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -1
- package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +13 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drivenets/design-system",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.cts",
|
|
@@ -106,8 +106,8 @@
|
|
|
106
106
|
"vitest": "^4.1.10",
|
|
107
107
|
"vitest-browser-react": "^2.1.0",
|
|
108
108
|
"zod": "^4.4.3",
|
|
109
|
-
"@drivenets/
|
|
110
|
-
"@drivenets/
|
|
109
|
+
"@drivenets/vite-plugin-design-system": "0.0.11",
|
|
110
|
+
"@drivenets/eslint-plugin-internal": "0.0.0"
|
|
111
111
|
},
|
|
112
112
|
"scripts": {
|
|
113
113
|
"start": "storybook dev -p 6006",
|
|
@@ -1,61 +1,33 @@
|
|
|
1
1
|
.container {
|
|
2
2
|
display: flex;
|
|
3
|
-
|
|
3
|
+
align-items: flex-start;
|
|
4
4
|
gap: var(--xs);
|
|
5
5
|
padding: var(--xs) var(--standard);
|
|
6
6
|
background: var(--background-secondary-hover);
|
|
7
7
|
border: 1px solid var(--outline-weak);
|
|
8
8
|
border-radius: var(--3xs);
|
|
9
9
|
overflow: hidden;
|
|
10
|
-
|
|
11
|
-
&.expanded {
|
|
12
|
-
max-height: none;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.header {
|
|
17
|
-
display: flex;
|
|
18
|
-
align-items: center;
|
|
19
|
-
width: 100%;
|
|
20
|
-
gap: var(--xs);
|
|
21
|
-
flex-shrink: 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.headerActions {
|
|
25
|
-
display: flex;
|
|
26
|
-
align-items: center;
|
|
27
|
-
gap: var(--xs);
|
|
28
|
-
flex-shrink: 0;
|
|
29
|
-
margin-left: auto;
|
|
30
10
|
}
|
|
31
11
|
|
|
32
12
|
.tagsArea {
|
|
33
13
|
display: flex;
|
|
34
14
|
align-items: center;
|
|
35
|
-
|
|
15
|
+
flex: 1;
|
|
16
|
+
min-width: 0;
|
|
36
17
|
gap: var(--xs);
|
|
37
18
|
flex-wrap: wrap;
|
|
38
19
|
}
|
|
39
20
|
|
|
40
|
-
.
|
|
41
|
-
color: var(--font-secondary);
|
|
42
|
-
white-space: nowrap;
|
|
43
|
-
flex-shrink: 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.itemCheckbox {
|
|
21
|
+
.actions {
|
|
47
22
|
display: flex;
|
|
48
23
|
align-items: center;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.ghostButtonContent {
|
|
52
|
-
// TODO: remove once migrated 1.2 button to the ghost with transparent bg
|
|
53
|
-
background: transparent !important;
|
|
24
|
+
gap: var(--xs);
|
|
25
|
+
flex-shrink: 0;
|
|
54
26
|
}
|
|
55
27
|
|
|
56
28
|
.actionButton {
|
|
57
|
-
padding: 0;
|
|
58
29
|
flex-shrink: 0;
|
|
30
|
+
white-space: nowrap;
|
|
59
31
|
}
|
|
60
32
|
|
|
61
33
|
.measurementContainer {
|
|
@@ -4,17 +4,12 @@ import classNames from 'classnames';
|
|
|
4
4
|
import styles from './ds-tag-filter.module.scss';
|
|
5
5
|
import type { DsTagFilterProps, TagFilterItem } from './ds-tag-filter.types';
|
|
6
6
|
import { useTagOverflowCalculation } from './hooks/use-tag-overflow-calculation';
|
|
7
|
-
import { DsTypography } from '../ds-typography';
|
|
8
7
|
import { DsTag, type DsTagProps } from '../ds-tag';
|
|
9
|
-
import {
|
|
8
|
+
import { DsButtonV3 } from '../ds-button-v3';
|
|
10
9
|
import { DsIcon } from '../ds-icon';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Design system TagFilter component
|
|
14
|
-
*
|
|
15
|
-
* A component for displaying active filters as tags with overflow handling.
|
|
16
|
-
* Non-tag elements (label, expand/collapse, clear) sit in a header row.
|
|
17
|
-
* Tags occupy the full width below, wrapping as needed.
|
|
18
13
|
*/
|
|
19
14
|
const DsTagFilter = ({
|
|
20
15
|
items,
|
|
@@ -27,22 +22,17 @@ const DsTagFilter = ({
|
|
|
27
22
|
onExpand,
|
|
28
23
|
}: DsTagFilterProps) => {
|
|
29
24
|
const [expanded, setExpanded] = useState(false);
|
|
30
|
-
const
|
|
25
|
+
const tagsAreaRef = useRef<HTMLDivElement>(null);
|
|
31
26
|
const measurementRef = useRef<HTMLDivElement>(null);
|
|
32
27
|
|
|
33
28
|
const { visibleTagCount, hasOverflow } = useTagOverflowCalculation({
|
|
34
|
-
|
|
29
|
+
tagsAreaRef,
|
|
35
30
|
measurementRef,
|
|
36
31
|
totalItems: items.length,
|
|
37
32
|
expanded,
|
|
38
33
|
});
|
|
39
34
|
|
|
40
|
-
const {
|
|
41
|
-
label = 'Filtered by:',
|
|
42
|
-
clearButton = 'Clear all filters',
|
|
43
|
-
showMore = 'Show more',
|
|
44
|
-
showLess = 'Show less',
|
|
45
|
-
} = locale;
|
|
35
|
+
const { clearButton = 'Clear all filters', showMore = 'Show more', showLess = 'Show less' } = locale;
|
|
46
36
|
|
|
47
37
|
const visibleTags = expanded ? items : items.slice(0, visibleTagCount);
|
|
48
38
|
const hiddenCount = items.length - visibleTagCount;
|
|
@@ -79,54 +69,41 @@ const DsTagFilter = ({
|
|
|
79
69
|
</div>
|
|
80
70
|
);
|
|
81
71
|
|
|
72
|
+
const toggleLabel = expanded ? showLess : showMore;
|
|
73
|
+
|
|
82
74
|
return (
|
|
83
75
|
<>
|
|
84
|
-
<div
|
|
85
|
-
ref={
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
>
|
|
89
|
-
<div className={styles.header}>
|
|
90
|
-
{label && (
|
|
91
|
-
<DsTypography variant="body-sm-reg" className={styles.label}>
|
|
92
|
-
{label}
|
|
93
|
-
</DsTypography>
|
|
94
|
-
)}
|
|
76
|
+
<div className={classNames(styles.container, className)} style={style}>
|
|
77
|
+
<div ref={tagsAreaRef} className={styles.tagsArea} aria-live="polite">
|
|
78
|
+
{visibleTags.map((item) => renderTag(item))}
|
|
79
|
+
</div>
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
</DsButton>
|
|
110
|
-
)}
|
|
81
|
+
<div className={styles.actions}>
|
|
82
|
+
{hasOverflow && (
|
|
83
|
+
<DsButtonV3
|
|
84
|
+
variant="tertiary"
|
|
85
|
+
size="small"
|
|
86
|
+
className={styles.actionButton}
|
|
87
|
+
aria-expanded={expanded}
|
|
88
|
+
onClick={handleExpandToggle}
|
|
89
|
+
>
|
|
90
|
+
{`${toggleLabel} (${String(hiddenCount)})`}
|
|
91
|
+
<DsIcon icon={expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down'} size="tiny" aria-hidden />
|
|
92
|
+
</DsButtonV3>
|
|
93
|
+
)}
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{clearButton}
|
|
124
|
-
</DsButton>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
95
|
+
{onClearAll && (
|
|
96
|
+
<DsButtonV3
|
|
97
|
+
variant="tertiary"
|
|
98
|
+
size="small"
|
|
99
|
+
icon="close"
|
|
100
|
+
className={styles.actionButton}
|
|
101
|
+
onClick={onClearAll}
|
|
102
|
+
>
|
|
103
|
+
{clearButton}
|
|
104
|
+
</DsButtonV3>
|
|
105
|
+
)}
|
|
127
106
|
</div>
|
|
128
|
-
|
|
129
|
-
<div className={styles.tagsArea}>{visibleTags.map((item) => renderTag(item))}</div>
|
|
130
107
|
</div>
|
|
131
108
|
{createPortal(measurementContainer, document.body)}
|
|
132
109
|
</>
|
|
@@ -40,7 +40,8 @@ export interface DsTagFilterProps {
|
|
|
40
40
|
*/
|
|
41
41
|
locale?: {
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* @deprecated No longer rendered. Retained for backwards compatibility;
|
|
44
|
+
* Will be removed in the future.
|
|
44
45
|
*/
|
|
45
46
|
label?: string;
|
|
46
47
|
/**
|
|
@@ -2,7 +2,7 @@ import { type RefObject, useCallback, useLayoutEffect, useState } from 'react';
|
|
|
2
2
|
import { fitTagsInRow, getContainerAvailableWidth, getElementMeasurements } from '../utils';
|
|
3
3
|
|
|
4
4
|
interface UseTagOverflowCalculationOptions {
|
|
5
|
-
|
|
5
|
+
tagsAreaRef: RefObject<HTMLDivElement | null>;
|
|
6
6
|
measurementRef: RefObject<HTMLDivElement | null>;
|
|
7
7
|
totalItems: number;
|
|
8
8
|
expanded: boolean;
|
|
@@ -14,13 +14,10 @@ interface UseTagOverflowCalculationResult {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Custom hook to calculate how many tags fit
|
|
18
|
-
*
|
|
19
|
-
* Tags occupy the full container width. Non-tag elements (label, clear button,
|
|
20
|
-
* expand control) live in a separate header row and don't affect the calculation.
|
|
17
|
+
* Custom hook to calculate how many tags fit on the first row of the tags-area.
|
|
21
18
|
*/
|
|
22
19
|
export const useTagOverflowCalculation = ({
|
|
23
|
-
|
|
20
|
+
tagsAreaRef,
|
|
24
21
|
measurementRef,
|
|
25
22
|
totalItems,
|
|
26
23
|
expanded,
|
|
@@ -31,11 +28,11 @@ export const useTagOverflowCalculation = ({
|
|
|
31
28
|
});
|
|
32
29
|
|
|
33
30
|
const calculateLayout = useCallback(() => {
|
|
34
|
-
if (!
|
|
31
|
+
if (!tagsAreaRef.current || !measurementRef.current) {
|
|
35
32
|
return;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
const
|
|
35
|
+
const tagsArea = tagsAreaRef.current;
|
|
39
36
|
const measurementContainer = measurementRef.current;
|
|
40
37
|
|
|
41
38
|
const { tagWidths, gap } = getElementMeasurements(measurementContainer);
|
|
@@ -45,13 +42,13 @@ export const useTagOverflowCalculation = ({
|
|
|
45
42
|
return;
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
const
|
|
45
|
+
const availableWidth = getContainerAvailableWidth(tagsArea);
|
|
49
46
|
|
|
50
|
-
const { count } = fitTagsInRow(tagWidths,
|
|
47
|
+
const { count } = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
51
48
|
const hasOverflow = count < tagWidths.length;
|
|
52
49
|
|
|
53
50
|
setState({ visibleTagCount: count, hasOverflow });
|
|
54
|
-
}, [
|
|
51
|
+
}, [tagsAreaRef, measurementRef]);
|
|
55
52
|
|
|
56
53
|
useLayoutEffect(() => {
|
|
57
54
|
const rafId = requestAnimationFrame(() => {
|
|
@@ -64,21 +61,19 @@ export const useTagOverflowCalculation = ({
|
|
|
64
61
|
});
|
|
65
62
|
});
|
|
66
63
|
|
|
67
|
-
if (
|
|
68
|
-
resizeObserver.observe(
|
|
64
|
+
if (tagsAreaRef.current) {
|
|
65
|
+
resizeObserver.observe(tagsAreaRef.current);
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
return () => {
|
|
72
69
|
cancelAnimationFrame(rafId);
|
|
73
70
|
resizeObserver.disconnect();
|
|
74
71
|
};
|
|
75
|
-
}, [
|
|
72
|
+
}, [tagsAreaRef, measurementRef, totalItems, expanded, calculateLayout]);
|
|
76
73
|
|
|
74
|
+
// While expanded, keep hasOverflow true so a width edge case can't hide the collapse control.
|
|
77
75
|
if (expanded) {
|
|
78
|
-
return {
|
|
79
|
-
visibleTagCount: totalItems,
|
|
80
|
-
hasOverflow: true,
|
|
81
|
-
};
|
|
76
|
+
return { ...state, hasOverflow: true };
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
return state;
|