@drivenets/design-system 0.6.1 → 0.7.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 +35 -0
- package/dist/index.cjs +12297 -8255
- package/dist/index.d.cts +817 -118
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +817 -118
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12280 -8259
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +25 -23
- package/src/components/ds-autocomplete/ds-autocomplete.module.scss +2 -1
- package/src/components/ds-autocomplete/ds-autocomplete.tsx +47 -40
- package/src/components/ds-autocomplete/ds-autocomplete.types.tsx +37 -14
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +3 -7
- package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.module.scss +62 -0
- package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.tsx +83 -0
- package/src/components/ds-comment-bubble/components/ds-comment-thread/ds-comment-thread.types.ts +12 -0
- package/src/components/ds-comment-bubble/components/ds-comment-thread/index.ts +2 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.module.scss +149 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.tsx +147 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/ds-thread-item.types.ts +15 -0
- package/src/components/ds-comment-bubble/components/ds-thread-item/index.ts +2 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.module.scss +298 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +241 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +27 -0
- package/src/components/ds-comment-bubble/hooks/index.ts +3 -0
- package/src/components/ds-comment-bubble/hooks/use-auto-resize.ts +17 -0
- package/src/components/ds-comment-bubble/hooks/use-click-outside.ts +22 -0
- package/src/components/ds-comment-bubble/hooks/use-scroll-overflow.ts +27 -0
- package/src/components/ds-comment-bubble/index.ts +6 -0
- package/src/components/ds-comment-card/ds-comment-card.module.scss +162 -0
- package/src/components/ds-comment-card/ds-comment-card.tsx +171 -0
- package/src/components/ds-comment-card/ds-comment-card.types.ts +45 -0
- package/src/components/ds-comment-card/index.ts +2 -0
- package/src/components/ds-comment-indicator/ds-comment-indicator.module.scss +65 -0
- package/src/components/ds-comment-indicator/ds-comment-indicator.tsx +48 -0
- package/src/components/ds-comment-indicator/ds-comment-indicator.types.ts +13 -0
- package/src/components/ds-comment-indicator/index.ts +2 -0
- package/src/components/ds-comments-drawer/comments-filters.test.ts +483 -0
- package/src/components/ds-comments-drawer/comments-filters.ts +134 -0
- package/src/components/ds-comments-drawer/comments-filters.types.ts +14 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.module.scss +92 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +219 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.types.ts +12 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/index.ts +2 -0
- package/src/components/ds-comments-drawer/ds-comments-drawer.module.scss +67 -0
- package/src/components/ds-comments-drawer/ds-comments-drawer.tsx +215 -0
- package/src/components/ds-comments-drawer/ds-comments-drawer.types.ts +23 -0
- package/src/components/ds-comments-drawer/index.ts +3 -0
- package/src/components/ds-date-input/ds-date-input.tsx +3 -1
- package/src/components/ds-date-input/ds-date-input.types.tsx +6 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +8 -4
- package/src/components/ds-file-upload/components/file-upload/file-upload.tsx +2 -1
- package/src/components/ds-file-upload/components/file-upload/file-upload.types.tsx +5 -0
- package/src/components/ds-file-upload/types/accept-types.ts +0 -1
- package/src/components/ds-filter-status-icon/ds-filter-status-icon.module.scss +19 -0
- package/src/components/ds-filter-status-icon/ds-filter-status-icon.tsx +35 -0
- package/src/components/ds-filter-status-icon/ds-filter-status-icon.types.ts +31 -0
- package/src/components/ds-filter-status-icon/index.ts +3 -0
- package/src/components/ds-icon/custom-icons/index.ts +10 -0
- package/src/components/ds-icon/custom-icons/special-failed.tsx +16 -0
- package/src/components/ds-icon/custom-icons/special-paused.tsx +16 -0
- package/src/components/ds-icon/custom-icons/special-running.tsx +16 -0
- package/src/components/ds-icon/custom-icons/special-warning.tsx +16 -0
- package/src/components/ds-panel/ds-panel.module.scss +3 -3
- package/src/components/ds-progress-arc/ds-progress-arc.module.scss +57 -0
- package/src/components/ds-progress-arc/ds-progress-arc.tsx +111 -0
- package/src/components/ds-progress-arc/ds-progress-arc.types.ts +34 -0
- package/src/components/ds-progress-arc/index.ts +3 -0
- package/src/components/ds-progress-arc/utils.test.ts +79 -0
- package/src/components/ds-progress-arc/utils.ts +61 -0
- package/src/components/ds-progress-donut/ds-progress-donut.module.scss +67 -0
- package/src/components/ds-progress-donut/ds-progress-donut.tsx +83 -0
- package/src/components/ds-progress-donut/ds-progress-donut.types.ts +38 -0
- package/src/components/ds-progress-donut/index.ts +2 -0
- package/src/components/ds-progress-donut/utils.ts +1 -0
- package/src/components/ds-progress-linear/ds-progress-linear.module.scss +100 -0
- package/src/components/ds-progress-linear/ds-progress-linear.tsx +107 -0
- package/src/components/ds-progress-linear/ds-progress-linear.types.ts +72 -0
- package/src/components/ds-progress-linear/index.ts +2 -0
- package/src/components/ds-progress-linear/utils.test.ts +70 -0
- package/src/components/ds-progress-linear/utils.ts +7 -0
- package/src/components/ds-radio-group/ds-radio-group.tsx +2 -0
- package/src/components/ds-select/ds-select.tsx +2 -1
- package/src/components/ds-select/ds-select.types.tsx +7 -0
- package/src/components/ds-stepper/components/ds-step.tsx +1 -1
- package/src/components/ds-stepper/ds-stepper.module.scss +16 -2
- package/src/components/ds-stepper/ds-stepper.tsx +3 -1
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +9 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +137 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +22 -0
- package/src/components/ds-table/components/ds-table-body-virtualized/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +3 -1
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +7 -10
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +3 -4
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +13 -53
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +32 -85
- package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +0 -6
- package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.module.scss +10 -0
- package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.tsx +45 -0
- package/src/components/ds-table/components/ds-table-row-expandable-cell/ds-table-row-expandable-cell.types.ts +7 -0
- package/src/components/ds-table/components/ds-table-row-expandable-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.module.scss +6 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.tsx +30 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.types.ts +6 -0
- package/src/components/ds-table/components/ds-table-row-selectable-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.module.scss +80 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +95 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +11 -0
- package/src/components/ds-table/components/ds-table-row-virtualized/index.ts +2 -0
- package/src/components/ds-table/context/ds-table-context.tsx +0 -9
- package/src/components/ds-table/ds-table.module.scss +4 -17
- package/src/components/ds-table/ds-table.tsx +44 -77
- package/src/components/ds-table/ds-table.types.ts +57 -12
- package/src/components/ds-table/index.ts +1 -0
- package/src/components/ds-table/styles/_mixins.scss +15 -0
- package/src/components/ds-table/styles/_variables.scss +2 -2
- package/src/components/ds-table/utils/{column-styling.ts → column-size.ts} +4 -14
- package/src/components/ds-table/utils/constants.ts +4 -0
- package/src/components/ds-tag/index.tsx +1 -0
- package/src/components/ds-tag-filter/ds-tag-filter.module.scss +13 -15
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +38 -72
- package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -3
- package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +12 -62
- package/src/components/ds-tag-filter/utils.test.ts +2 -108
- package/src/components/ds-tag-filter/utils.ts +2 -12
- package/src/components/ds-tree/ds-tree.module.scss +283 -0
- package/src/components/ds-tree/ds-tree.tsx +172 -0
- package/src/components/ds-tree/ds-tree.types.ts +83 -0
- package/src/components/ds-tree/ds-tree.utils.ts +11 -0
- package/src/components/ds-tree/index.ts +3 -0
- package/src/index.ts +9 -0
- package/src/utils/format-relative-time.test.ts +74 -0
- package/src/utils/format-relative-time.ts +52 -0
- package/src/utils/numbers.test.ts +24 -0
- package/src/utils/numbers.ts +1 -0
- package/src/components/ds-table/utils/create-columns-grid-template.ts +0 -23
|
@@ -13,7 +13,8 @@ import { DsIcon } from '../ds-icon';
|
|
|
13
13
|
* Design system TagFilter component
|
|
14
14
|
*
|
|
15
15
|
* A component for displaying active filters as tags with overflow handling.
|
|
16
|
-
*
|
|
16
|
+
* Non-tag elements (label, expand/collapse, clear) sit in a header row.
|
|
17
|
+
* Tags occupy the full width below, wrapping as needed.
|
|
17
18
|
*/
|
|
18
19
|
const DsTagFilter = ({
|
|
19
20
|
items,
|
|
@@ -29,7 +30,7 @@ const DsTagFilter = ({
|
|
|
29
30
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
30
31
|
const measurementRef = useRef<HTMLDivElement>(null);
|
|
31
32
|
|
|
32
|
-
const {
|
|
33
|
+
const { visibleTagCount, hasOverflow } = useTagOverflowCalculation({
|
|
33
34
|
containerRef,
|
|
34
35
|
measurementRef,
|
|
35
36
|
totalItems: items.length,
|
|
@@ -39,23 +40,17 @@ const DsTagFilter = ({
|
|
|
39
40
|
const {
|
|
40
41
|
label = 'Filtered by:',
|
|
41
42
|
clearButton = 'Clear all filters',
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
collapseTagLabel = 'Collapse',
|
|
43
|
+
showMore = 'Show more',
|
|
44
|
+
showLess = 'Show less',
|
|
45
45
|
} = locale;
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const row2Tags = items.slice(row1TagCount, row1TagCount + row2TagCount);
|
|
50
|
-
const hiddenTags = expanded ? [] : items.slice(row1TagCount + row2TagCount);
|
|
47
|
+
const visibleTags = expanded ? items : items.slice(0, visibleTagCount);
|
|
48
|
+
const hiddenCount = items.length - visibleTagCount;
|
|
51
49
|
|
|
52
50
|
if (items.length === 0) {
|
|
53
51
|
return null;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
const hiddenCount = hiddenTags.length;
|
|
57
|
-
const hasRow2Content = row2Tags.length > 0 || hasOverflow;
|
|
58
|
-
|
|
59
54
|
const renderTag = (item: TagFilterItem) => {
|
|
60
55
|
const tagProps = item.slotProps?.tag || {};
|
|
61
56
|
|
|
@@ -77,18 +72,8 @@ const DsTagFilter = ({
|
|
|
77
72
|
onExpand?.(newExpanded);
|
|
78
73
|
};
|
|
79
74
|
|
|
80
|
-
// Measurement container rendered via portal to keep it outside the component's DOM tree
|
|
81
|
-
// This prevents Testing Library from finding duplicate elements
|
|
82
|
-
// We use it to calculate the number of remaining tags to render
|
|
83
75
|
const measurementContainer = (
|
|
84
76
|
<div ref={measurementRef} className={styles.measurementContainer} aria-hidden="true">
|
|
85
|
-
{label && (
|
|
86
|
-
<span data-measure-label="">
|
|
87
|
-
<DsTypography variant="body-sm-reg" className={styles.label}>
|
|
88
|
-
{label}
|
|
89
|
-
</DsTypography>
|
|
90
|
-
</span>
|
|
91
|
-
)}
|
|
92
77
|
{items.map((item) => (
|
|
93
78
|
<DsTag
|
|
94
79
|
key={item.id}
|
|
@@ -99,29 +84,9 @@ const DsTagFilter = ({
|
|
|
99
84
|
onDelete={onItemDelete ? () => onItemDelete(item) : undefined}
|
|
100
85
|
/>
|
|
101
86
|
))}
|
|
102
|
-
{onClearAll && (
|
|
103
|
-
<DsButton
|
|
104
|
-
data-measure-clear=""
|
|
105
|
-
design="v1.2"
|
|
106
|
-
buttonType="tertiary"
|
|
107
|
-
variant="ghost"
|
|
108
|
-
className={styles.clearButton}
|
|
109
|
-
contentClassName={styles.clearContent}
|
|
110
|
-
size="small"
|
|
111
|
-
>
|
|
112
|
-
<DsIcon icon="close" />
|
|
113
|
-
{clearButton}
|
|
114
|
-
</DsButton>
|
|
115
|
-
)}
|
|
116
|
-
<DsTag data-measure-expand="" label="+99 filters" className={styles.expandTag} />
|
|
117
87
|
</div>
|
|
118
88
|
);
|
|
119
89
|
|
|
120
|
-
/**
|
|
121
|
-
* IMPORTANT!
|
|
122
|
-
* If you make any changes to this layout (styling, JSX, anything that affects it.)
|
|
123
|
-
* please, apply the exact same changes to the "measurementContainer"
|
|
124
|
-
*/
|
|
125
90
|
return (
|
|
126
91
|
<>
|
|
127
92
|
<div
|
|
@@ -129,46 +94,47 @@ const DsTagFilter = ({
|
|
|
129
94
|
className={classNames(styles.container, expanded && styles.expanded, className)}
|
|
130
95
|
style={style}
|
|
131
96
|
>
|
|
132
|
-
<div className={styles.
|
|
97
|
+
<div className={styles.header}>
|
|
133
98
|
{label && (
|
|
134
99
|
<DsTypography variant="body-sm-reg" className={styles.label}>
|
|
135
100
|
{label}
|
|
136
101
|
</DsTypography>
|
|
137
102
|
)}
|
|
138
|
-
{row1Tags.map((item) => renderTag(item))}
|
|
139
|
-
{onClearAll && (
|
|
140
|
-
<DsButton
|
|
141
|
-
design="v1.2"
|
|
142
|
-
buttonType="tertiary"
|
|
143
|
-
variant="ghost"
|
|
144
|
-
className={styles.clearButton}
|
|
145
|
-
contentClassName={styles.clearContent}
|
|
146
|
-
size="small"
|
|
147
|
-
onClick={onClearAll}
|
|
148
|
-
>
|
|
149
|
-
<DsIcon icon="close" />
|
|
150
|
-
{clearButton}
|
|
151
|
-
</DsButton>
|
|
152
|
-
)}
|
|
153
|
-
</div>
|
|
154
103
|
|
|
155
|
-
|
|
156
|
-
<div className={styles.row2}>
|
|
157
|
-
{row2Tags.map((item) => renderTag(item))}
|
|
104
|
+
<div className={styles.headerActions}>
|
|
158
105
|
{hasOverflow && (
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
className={styles.expandTag}
|
|
106
|
+
<DsButton
|
|
107
|
+
design="v1.2"
|
|
108
|
+
buttonType="tertiary"
|
|
109
|
+
variant="ghost"
|
|
110
|
+
className={styles.actionButton}
|
|
111
|
+
contentClassName={styles.ghostButtonContent}
|
|
112
|
+
size="small"
|
|
167
113
|
onClick={handleExpandToggle}
|
|
168
|
-
|
|
114
|
+
>
|
|
115
|
+
<DsIcon icon={expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down'} />
|
|
116
|
+
{expanded ? showLess : `${showMore} (${String(hiddenCount)})`}
|
|
117
|
+
</DsButton>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{onClearAll && (
|
|
121
|
+
<DsButton
|
|
122
|
+
design="v1.2"
|
|
123
|
+
buttonType="tertiary"
|
|
124
|
+
variant="ghost"
|
|
125
|
+
className={styles.actionButton}
|
|
126
|
+
contentClassName={styles.ghostButtonContent}
|
|
127
|
+
size="small"
|
|
128
|
+
onClick={onClearAll}
|
|
129
|
+
>
|
|
130
|
+
<DsIcon icon="close" />
|
|
131
|
+
{clearButton}
|
|
132
|
+
</DsButton>
|
|
169
133
|
)}
|
|
170
134
|
</div>
|
|
171
|
-
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div className={styles.tagsArea}>{visibleTags.map((item) => renderTag(item))}</div>
|
|
172
138
|
</div>
|
|
173
139
|
{createPortal(measurementContainer, document.body)}
|
|
174
140
|
</>
|
|
@@ -37,9 +37,8 @@ export interface DsTagFilterProps {
|
|
|
37
37
|
locale?: {
|
|
38
38
|
label?: string;
|
|
39
39
|
clearButton?: string;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
hiddenCountPlural?: string;
|
|
40
|
+
showMore?: string;
|
|
41
|
+
showLess?: string;
|
|
43
42
|
};
|
|
44
43
|
/**
|
|
45
44
|
* Callback when "Clear all" is clicked
|
|
@@ -9,21 +9,15 @@ interface UseTagOverflowCalculationOptions {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface UseTagOverflowCalculationResult {
|
|
12
|
-
|
|
13
|
-
row2TagCount: number;
|
|
12
|
+
visibleTagCount: number;
|
|
14
13
|
hasOverflow: boolean;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
// Extra space needed per row for animated delete button that appears on hover
|
|
18
|
-
const TAG_HOVER_BUTTON_SPACE = 20;
|
|
19
|
-
|
|
20
16
|
/**
|
|
21
|
-
* Custom hook to calculate how many tags
|
|
22
|
-
*
|
|
23
|
-
* Row 1: Label + Tags + Clear Button
|
|
24
|
-
* Row 2: Remaining Tags + Expand Tag (only if overflow)
|
|
17
|
+
* Custom hook to calculate how many tags fit in the available container width.
|
|
25
18
|
*
|
|
26
|
-
*
|
|
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.
|
|
27
21
|
*/
|
|
28
22
|
export const useTagOverflowCalculation = ({
|
|
29
23
|
containerRef,
|
|
@@ -32,8 +26,7 @@ export const useTagOverflowCalculation = ({
|
|
|
32
26
|
expanded,
|
|
33
27
|
}: UseTagOverflowCalculationOptions): UseTagOverflowCalculationResult => {
|
|
34
28
|
const [state, setState] = useState<UseTagOverflowCalculationResult>({
|
|
35
|
-
|
|
36
|
-
row2TagCount: 0,
|
|
29
|
+
visibleTagCount: 0,
|
|
37
30
|
hasOverflow: false,
|
|
38
31
|
});
|
|
39
32
|
|
|
@@ -45,63 +38,22 @@ export const useTagOverflowCalculation = ({
|
|
|
45
38
|
const container = containerRef.current;
|
|
46
39
|
const measurementContainer = measurementRef.current;
|
|
47
40
|
|
|
48
|
-
const { tagWidths,
|
|
49
|
-
getElementMeasurements(measurementContainer);
|
|
41
|
+
const { tagWidths, gap } = getElementMeasurements(measurementContainer);
|
|
50
42
|
|
|
51
43
|
if (tagWidths.length === 0) {
|
|
52
|
-
setState({
|
|
44
|
+
setState({ visibleTagCount: 0, hasOverflow: false });
|
|
53
45
|
return;
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
const containerWidth = getContainerAvailableWidth(container);
|
|
57
49
|
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
// Calculate how many tags fit in Row 1
|
|
62
|
-
const row1Result = fitTagsInRow(tagWidths, row1AvailableWidth, gap);
|
|
63
|
-
const remainingTags = tagWidths.slice(row1Result.count);
|
|
64
|
-
|
|
65
|
-
if (remainingTags.length === 0) {
|
|
66
|
-
// All tags fit in Row 1, no overflow
|
|
67
|
-
setState({ row1TagCount: row1Result.count, row2TagCount: 0, hasOverflow: false });
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Calculate with expand tag reserved (pessimistic)
|
|
72
|
-
const row2AvailableWithExpand = containerWidth - expandTagWidth - TAG_HOVER_BUTTON_SPACE;
|
|
73
|
-
const row2WithExpandResult = fitTagsInRow(remainingTags, row2AvailableWithExpand, gap);
|
|
74
|
-
|
|
75
|
-
// Check if there's overflow (more remaining tags than fit in Row 2 with expand)
|
|
76
|
-
const overflowExists = remainingTags.length > row2WithExpandResult.count;
|
|
50
|
+
const { count } = fitTagsInRow(tagWidths, containerWidth, gap);
|
|
51
|
+
const hasOverflow = count < tagWidths.length;
|
|
77
52
|
|
|
78
|
-
|
|
79
|
-
// Use the count with expand tag space reserved
|
|
80
|
-
setState({
|
|
81
|
-
row1TagCount: row1Result.count,
|
|
82
|
-
row2TagCount: row2WithExpandResult.count,
|
|
83
|
-
hasOverflow: true,
|
|
84
|
-
});
|
|
85
|
-
} else {
|
|
86
|
-
// No overflow - recalculate Row 2 without reserving expand tag space
|
|
87
|
-
const row2AvailableWithoutExpand = containerWidth - TAG_HOVER_BUTTON_SPACE;
|
|
88
|
-
const row2FullResult = fitTagsInRow(remainingTags, row2AvailableWithoutExpand, gap);
|
|
89
|
-
|
|
90
|
-
// Double-check: if even without expand tag we can't fit all, we have overflow
|
|
91
|
-
if (row2FullResult.count < remainingTags.length) {
|
|
92
|
-
setState({
|
|
93
|
-
row1TagCount: row1Result.count,
|
|
94
|
-
row2TagCount: row2WithExpandResult.count,
|
|
95
|
-
hasOverflow: true,
|
|
96
|
-
});
|
|
97
|
-
} else {
|
|
98
|
-
setState({ row1TagCount: row1Result.count, row2TagCount: row2FullResult.count, hasOverflow: false });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
53
|
+
setState({ visibleTagCount: count, hasOverflow });
|
|
101
54
|
}, [containerRef, measurementRef]);
|
|
102
55
|
|
|
103
56
|
useLayoutEffect(() => {
|
|
104
|
-
// Use requestAnimationFrame to ensure DOM is fully laid out
|
|
105
57
|
const rafId = requestAnimationFrame(() => {
|
|
106
58
|
calculateLayout();
|
|
107
59
|
});
|
|
@@ -122,12 +74,10 @@ export const useTagOverflowCalculation = ({
|
|
|
122
74
|
};
|
|
123
75
|
}, [containerRef, measurementRef, totalItems, expanded, calculateLayout]);
|
|
124
76
|
|
|
125
|
-
// When expanded, show all tags
|
|
126
77
|
if (expanded) {
|
|
127
78
|
return {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
hasOverflow: true, // Keep expand button visible to allow collapse
|
|
79
|
+
visibleTagCount: totalItems,
|
|
80
|
+
hasOverflow: true,
|
|
131
81
|
};
|
|
132
82
|
}
|
|
133
83
|
|
|
@@ -212,26 +212,15 @@ describe('Tag Filter Utils', () => {
|
|
|
212
212
|
vi.restoreAllMocks();
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
-
it('should measure
|
|
216
|
-
// Create mock elements
|
|
215
|
+
it('should measure tag widths and gap', () => {
|
|
217
216
|
const tag1 = document.createElement('div');
|
|
218
217
|
tag1.setAttribute('data-measure-tag', '');
|
|
219
218
|
const tag2 = document.createElement('div');
|
|
220
219
|
tag2.setAttribute('data-measure-tag', '');
|
|
221
|
-
const label = document.createElement('div');
|
|
222
|
-
label.setAttribute('data-measure-label', '');
|
|
223
|
-
const clearButton = document.createElement('div');
|
|
224
|
-
clearButton.setAttribute('data-measure-clear', '');
|
|
225
|
-
const expandTag = document.createElement('div');
|
|
226
|
-
expandTag.setAttribute('data-measure-expand', '');
|
|
227
220
|
|
|
228
221
|
mockContainer.appendChild(tag1);
|
|
229
222
|
mockContainer.appendChild(tag2);
|
|
230
|
-
mockContainer.appendChild(label);
|
|
231
|
-
mockContainer.appendChild(clearButton);
|
|
232
|
-
mockContainer.appendChild(expandTag);
|
|
233
223
|
|
|
234
|
-
// Mock getBoundingClientRect for each element
|
|
235
224
|
vi.spyOn(tag1, 'getBoundingClientRect').mockReturnValue({
|
|
236
225
|
width: 80,
|
|
237
226
|
height: 30,
|
|
@@ -256,42 +245,6 @@ describe('Tag Filter Utils', () => {
|
|
|
256
245
|
toJSON: () => ({}),
|
|
257
246
|
});
|
|
258
247
|
|
|
259
|
-
vi.spyOn(label, 'getBoundingClientRect').mockReturnValue({
|
|
260
|
-
width: 60,
|
|
261
|
-
height: 30,
|
|
262
|
-
top: 0,
|
|
263
|
-
left: 0,
|
|
264
|
-
bottom: 30,
|
|
265
|
-
right: 60,
|
|
266
|
-
x: 0,
|
|
267
|
-
y: 0,
|
|
268
|
-
toJSON: () => ({}),
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
vi.spyOn(clearButton, 'getBoundingClientRect').mockReturnValue({
|
|
272
|
-
width: 40,
|
|
273
|
-
height: 30,
|
|
274
|
-
top: 0,
|
|
275
|
-
left: 0,
|
|
276
|
-
bottom: 30,
|
|
277
|
-
right: 40,
|
|
278
|
-
x: 0,
|
|
279
|
-
y: 0,
|
|
280
|
-
toJSON: () => ({}),
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
vi.spyOn(expandTag, 'getBoundingClientRect').mockReturnValue({
|
|
284
|
-
width: 100,
|
|
285
|
-
height: 30,
|
|
286
|
-
top: 0,
|
|
287
|
-
left: 0,
|
|
288
|
-
bottom: 30,
|
|
289
|
-
right: 100,
|
|
290
|
-
x: 0,
|
|
291
|
-
y: 0,
|
|
292
|
-
toJSON: () => ({}),
|
|
293
|
-
});
|
|
294
|
-
|
|
295
248
|
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
296
249
|
gap: '8px',
|
|
297
250
|
} as CSSStyleDeclaration);
|
|
@@ -299,42 +252,9 @@ describe('Tag Filter Utils', () => {
|
|
|
299
252
|
const result = getElementMeasurements(mockContainer);
|
|
300
253
|
|
|
301
254
|
expect(result.tagWidths).toEqual([80, 90]);
|
|
302
|
-
expect(result.labelWidth).toBe(68); // 60 + 8
|
|
303
|
-
expect(result.clearButtonWidth).toBe(48); // 40 + 8
|
|
304
|
-
expect(result.expandTagWidth).toBe(108); // 100 + 8
|
|
305
255
|
expect(result.gap).toBe(8);
|
|
306
256
|
});
|
|
307
257
|
|
|
308
|
-
it('should handle missing optional elements', () => {
|
|
309
|
-
const tag1 = document.createElement('div');
|
|
310
|
-
tag1.setAttribute('data-measure-tag', '');
|
|
311
|
-
mockContainer.appendChild(tag1);
|
|
312
|
-
|
|
313
|
-
vi.spyOn(tag1, 'getBoundingClientRect').mockReturnValue({
|
|
314
|
-
width: 80,
|
|
315
|
-
height: 30,
|
|
316
|
-
top: 0,
|
|
317
|
-
left: 0,
|
|
318
|
-
bottom: 30,
|
|
319
|
-
right: 80,
|
|
320
|
-
x: 0,
|
|
321
|
-
y: 0,
|
|
322
|
-
toJSON: () => ({}),
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
326
|
-
gap: '12px',
|
|
327
|
-
} as CSSStyleDeclaration);
|
|
328
|
-
|
|
329
|
-
const result = getElementMeasurements(mockContainer);
|
|
330
|
-
|
|
331
|
-
expect(result.tagWidths).toEqual([80]);
|
|
332
|
-
expect(result.labelWidth).toBe(0); // Missing label
|
|
333
|
-
expect(result.clearButtonWidth).toBe(0); // Missing clear button
|
|
334
|
-
expect(result.expandTagWidth).toBe(100); // Fallback width
|
|
335
|
-
expect(result.gap).toBe(12);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
258
|
it('should use fallback gap when gap is invalid', () => {
|
|
339
259
|
const tag1 = document.createElement('div');
|
|
340
260
|
tag1.setAttribute('data-measure-tag', '');
|
|
@@ -358,7 +278,7 @@ describe('Tag Filter Utils', () => {
|
|
|
358
278
|
|
|
359
279
|
const result = getElementMeasurements(mockContainer);
|
|
360
280
|
|
|
361
|
-
expect(result.gap).toBe(8);
|
|
281
|
+
expect(result.gap).toBe(8);
|
|
362
282
|
});
|
|
363
283
|
|
|
364
284
|
it('should handle no tags present', () => {
|
|
@@ -371,31 +291,5 @@ describe('Tag Filter Utils', () => {
|
|
|
371
291
|
expect(result.tagWidths).toEqual([]);
|
|
372
292
|
expect(result.gap).toBe(10);
|
|
373
293
|
});
|
|
374
|
-
|
|
375
|
-
it('should use fallback for expand tag when width is 0', () => {
|
|
376
|
-
const expandTag = document.createElement('div');
|
|
377
|
-
expandTag.setAttribute('data-measure-expand', '');
|
|
378
|
-
mockContainer.appendChild(expandTag);
|
|
379
|
-
|
|
380
|
-
vi.spyOn(expandTag, 'getBoundingClientRect').mockReturnValue({
|
|
381
|
-
width: 0,
|
|
382
|
-
height: 30,
|
|
383
|
-
top: 0,
|
|
384
|
-
left: 0,
|
|
385
|
-
bottom: 30,
|
|
386
|
-
right: 0,
|
|
387
|
-
x: 0,
|
|
388
|
-
y: 0,
|
|
389
|
-
toJSON: () => ({}),
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
393
|
-
gap: '8px',
|
|
394
|
-
} as CSSStyleDeclaration);
|
|
395
|
-
|
|
396
|
-
const result = getElementMeasurements(mockContainer);
|
|
397
|
-
|
|
398
|
-
expect(result.expandTagWidth).toBe(8); // 0 + 8 (not using fallback since element exists)
|
|
399
|
-
});
|
|
400
294
|
});
|
|
401
295
|
});
|
|
@@ -5,9 +5,6 @@ export interface FitTagsResult {
|
|
|
5
5
|
|
|
6
6
|
export interface ElementMeasurements {
|
|
7
7
|
tagWidths: number[];
|
|
8
|
-
labelWidth: number;
|
|
9
|
-
clearButtonWidth: number;
|
|
10
|
-
expandTagWidth: number;
|
|
11
8
|
gap: number;
|
|
12
9
|
}
|
|
13
10
|
|
|
@@ -44,22 +41,15 @@ export function getContainerAvailableWidth(container: HTMLDivElement): number {
|
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
/**
|
|
47
|
-
* Measure
|
|
44
|
+
* Measure tag widths and gap inside the measurement container.
|
|
48
45
|
*/
|
|
49
46
|
export function getElementMeasurements(measurementContainer: HTMLDivElement): ElementMeasurements {
|
|
50
47
|
const tags = Array.from(measurementContainer.querySelectorAll('[data-measure-tag]'));
|
|
51
|
-
const label = measurementContainer.querySelector('[data-measure-label]');
|
|
52
|
-
const clearButton = measurementContainer.querySelector('[data-measure-clear]');
|
|
53
|
-
const expandTag = measurementContainer.querySelector('[data-measure-expand]');
|
|
54
48
|
|
|
55
49
|
const computedStyle = getComputedStyle(measurementContainer);
|
|
56
50
|
const gap = parseFloat(computedStyle.gap) || 8;
|
|
57
51
|
|
|
58
52
|
const tagWidths = tags.map((tag) => tag.getBoundingClientRect().width);
|
|
59
|
-
const labelWidth = label ? label.getBoundingClientRect().width + gap : 0;
|
|
60
|
-
const clearButtonWidth = clearButton ? clearButton.getBoundingClientRect().width + gap : 0;
|
|
61
|
-
// Fallback width for expand tag (~100px for "+99 filters" text) if measurement fails
|
|
62
|
-
const expandTagWidth = expandTag ? expandTag.getBoundingClientRect().width + gap : 100;
|
|
63
53
|
|
|
64
|
-
return { tagWidths,
|
|
54
|
+
return { tagWidths, gap };
|
|
65
55
|
}
|