@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drivenets/design-system",
3
- "version": "0.18.0",
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/eslint-plugin-internal": "0.0.0",
110
- "@drivenets/vite-plugin-design-system": "0.0.11"
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
- flex-direction: column;
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
- width: 100%;
15
+ flex: 1;
16
+ min-width: 0;
36
17
  gap: var(--xs);
37
18
  flex-wrap: wrap;
38
19
  }
39
20
 
40
- .label {
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 { DsButton } from '../ds-button';
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 containerRef = useRef<HTMLDivElement>(null);
25
+ const tagsAreaRef = useRef<HTMLDivElement>(null);
31
26
  const measurementRef = useRef<HTMLDivElement>(null);
32
27
 
33
28
  const { visibleTagCount, hasOverflow } = useTagOverflowCalculation({
34
- containerRef,
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={containerRef}
86
- className={classNames(styles.container, expanded && styles.expanded, className)}
87
- style={style}
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
- <div className={styles.headerActions}>
97
- {hasOverflow && (
98
- <DsButton
99
- design="v1.2"
100
- buttonType="tertiary"
101
- variant="ghost"
102
- className={styles.actionButton}
103
- contentClassName={styles.ghostButtonContent}
104
- size="small"
105
- onClick={handleExpandToggle}
106
- >
107
- <DsIcon icon={expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down'} />
108
- {expanded ? showLess : `${showMore} (${String(hiddenCount)})`}
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
- {onClearAll && (
113
- <DsButton
114
- design="v1.2"
115
- buttonType="tertiary"
116
- variant="ghost"
117
- className={styles.actionButton}
118
- contentClassName={styles.ghostButtonContent}
119
- size="small"
120
- onClick={onClearAll}
121
- >
122
- <DsIcon icon="close" />
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
- * Heading label shown above the tag list (e.g., "Filters").
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
- containerRef: RefObject<HTMLDivElement | null>;
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 in the available container width.
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
- containerRef,
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 (!containerRef.current || !measurementRef.current) {
31
+ if (!tagsAreaRef.current || !measurementRef.current) {
35
32
  return;
36
33
  }
37
34
 
38
- const container = containerRef.current;
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 containerWidth = getContainerAvailableWidth(container);
45
+ const availableWidth = getContainerAvailableWidth(tagsArea);
49
46
 
50
- const { count } = fitTagsInRow(tagWidths, containerWidth, gap);
47
+ const { count } = fitTagsInRow(tagWidths, availableWidth, gap);
51
48
  const hasOverflow = count < tagWidths.length;
52
49
 
53
50
  setState({ visibleTagCount: count, hasOverflow });
54
- }, [containerRef, measurementRef]);
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 (containerRef.current) {
68
- resizeObserver.observe(containerRef.current);
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
- }, [containerRef, measurementRef, totalItems, expanded, calculateLayout]);
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;