@drivenets/design-system 0.1.0 → 0.2.0
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 +10 -0
- package/dist/components/ds-avatar/ds-avatar.cjs +98 -0
- package/dist/components/ds-avatar/ds-avatar.d.cts +12 -0
- package/dist/components/ds-avatar/ds-avatar.d.cts.map +1 -0
- package/dist/components/ds-avatar/ds-avatar.d.ts +12 -0
- package/dist/components/ds-avatar/ds-avatar.d.ts.map +1 -0
- package/dist/components/ds-avatar/ds-avatar.js +97 -0
- package/dist/components/ds-avatar/ds-avatar.js.map +1 -0
- package/dist/components/ds-avatar/ds-avatar.module.cjs +17 -0
- package/dist/components/ds-avatar/ds-avatar.module.js +17 -0
- package/dist/components/ds-avatar/ds-avatar.module.js.map +1 -0
- package/dist/components/ds-avatar/ds-avatar.types.d.cts +44 -0
- package/dist/components/ds-avatar/ds-avatar.types.d.cts.map +1 -0
- package/dist/components/ds-avatar/ds-avatar.types.d.ts +44 -0
- package/dist/components/ds-avatar/ds-avatar.types.d.ts.map +1 -0
- package/dist/components/ds-avatar/index.cjs +1 -0
- package/dist/components/ds-avatar/index.d.ts +2 -0
- package/dist/components/ds-avatar/index.js +1 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.cjs +93 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.d.cts +12 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.d.cts.map +1 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.d.ts +12 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.d.ts.map +1 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.js +92 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.js.map +1 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.module.cjs +19 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.module.js +19 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.module.js.map +1 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.types.d.cts +31 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.types.d.cts.map +1 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.types.d.ts +32 -0
- package/dist/components/ds-avatar-group/ds-avatar-group.types.d.ts.map +1 -0
- package/dist/components/ds-avatar-group/index.cjs +1 -0
- package/dist/components/ds-avatar-group/index.d.ts +2 -0
- package/dist/components/ds-avatar-group/index.js +1 -0
- package/dist/components/ds-button/ds-button.d.ts +2 -2
- package/dist/components/ds-button/ds-button.d.ts.map +1 -1
- package/dist/components/ds-chip/ds-chip.cjs +2 -1
- package/dist/components/ds-chip/ds-chip.d.cts +2 -1
- package/dist/components/ds-chip/ds-chip.d.cts.map +1 -1
- package/dist/components/ds-chip/ds-chip.d.ts +2 -1
- package/dist/components/ds-chip/ds-chip.d.ts.map +1 -1
- package/dist/components/ds-chip/ds-chip.js +2 -1
- package/dist/components/ds-chip/ds-chip.js.map +1 -1
- package/dist/components/ds-chip/ds-chip.types.d.cts +13 -0
- package/dist/components/ds-chip/ds-chip.types.d.cts.map +1 -1
- package/dist/components/ds-chip/ds-chip.types.d.ts +13 -0
- package/dist/components/ds-chip/ds-chip.types.d.ts.map +1 -1
- package/dist/components/ds-chip-group/ds-chip-group.cjs +2 -2
- package/dist/components/ds-chip-group/ds-chip-group.d.cts +2 -2
- package/dist/components/ds-chip-group/ds-chip-group.d.ts +2 -2
- package/dist/components/ds-chip-group/ds-chip-group.js +2 -2
- package/dist/components/ds-chip-group/ds-chip-group.js.map +1 -1
- package/dist/components/ds-chip-group/ds-chip-group.types.d.cts +9 -0
- package/dist/components/ds-chip-group/ds-chip-group.types.d.cts.map +1 -1
- package/dist/components/ds-chip-group/ds-chip-group.types.d.ts +9 -0
- package/dist/components/ds-chip-group/ds-chip-group.types.d.ts.map +1 -1
- package/dist/components/ds-confirmation/ds-confirmation.d.cts +8 -8
- package/dist/components/ds-confirmation/ds-confirmation.d.ts +8 -8
- package/dist/components/ds-date-input/ds-date-input.d.cts +2 -2
- package/dist/components/ds-date-input/ds-date-input.d.cts.map +1 -1
- package/dist/components/ds-date-input/ds-date-input.d.ts +2 -2
- package/dist/components/ds-date-input/ds-date-input.js +2 -2
- package/dist/components/ds-date-input/ds-date-input.js.map +1 -1
- package/dist/components/ds-drawer/ds-drawer.d.cts +9 -9
- package/dist/components/ds-drawer/ds-drawer.d.ts +9 -9
- package/dist/components/ds-drawer/ds-drawer.js +2 -2
- package/dist/components/ds-dropdown-menu/ds-dropdown-menu.js +3 -3
- package/dist/components/ds-dropdown-menu/ds-dropdown-menu.js.map +1 -1
- package/dist/components/ds-expandable-text-input/ds-expandable-text-input.d.cts +2 -2
- package/dist/components/ds-expandable-text-input/ds-expandable-text-input.d.ts +2 -2
- package/dist/components/ds-file-upload/components/file-upload-item/file-upload-item.cjs +2 -2
- package/dist/components/ds-file-upload/components/file-upload-item/file-upload-item.js +2 -2
- package/dist/components/ds-form-control/ds-form-control.d.cts +8 -8
- package/dist/components/ds-modal/ds-modal.d.cts +8 -8
- package/dist/components/ds-panel/ds-panel.d.cts +2 -2
- package/dist/components/ds-panel/ds-panel.d.ts +2 -2
- package/dist/components/ds-select/ds-select.d.cts +2 -2
- package/dist/components/ds-select/ds-select.d.ts +2 -2
- package/dist/components/ds-smart-tabs/ds-smart-tabs.d.cts +2 -2
- package/dist/components/ds-smart-tabs/ds-smart-tabs.d.ts +2 -2
- package/dist/components/ds-smart-tabs/ds-smart-tabs.d.ts.map +1 -1
- package/dist/components/ds-stepper/components/ds-step-content.d.cts +2 -2
- package/dist/components/ds-stepper/components/ds-step-content.d.ts +2 -2
- package/dist/components/ds-stepper/components/ds-step-next-button.d.cts +2 -2
- package/dist/components/ds-stepper/components/ds-step-next-button.d.ts +2 -2
- package/dist/components/ds-stepper/components/ds-step.d.cts +2 -2
- package/dist/components/ds-stepper/components/ds-step.d.ts +2 -2
- package/dist/components/ds-stepper/ds-stepper.d.cts +2 -2
- package/dist/components/ds-stepper/ds-stepper.d.ts +2 -2
- package/dist/components/ds-table/ds-table.d.cts +2 -2
- package/dist/components/ds-table/ds-table.d.ts +2 -2
- package/dist/components/ds-tag/ds-tag.cjs +254 -0
- package/dist/components/ds-tag/ds-tag.d.cts +25 -0
- package/dist/components/ds-tag/ds-tag.d.cts.map +1 -0
- package/dist/components/ds-tag/ds-tag.d.ts +25 -0
- package/dist/components/ds-tag/ds-tag.d.ts.map +1 -0
- package/dist/components/ds-tag/ds-tag.js +253 -0
- package/dist/components/ds-tag/ds-tag.js.map +1 -0
- package/dist/components/ds-tag/ds-tag.module.cjs +17 -0
- package/dist/components/ds-tag/ds-tag.module.js +17 -0
- package/dist/components/ds-tag/ds-tag.module.js.map +1 -0
- package/dist/components/ds-tag/ds-tag.types.d.cts +68 -0
- package/dist/components/ds-tag/ds-tag.types.d.cts.map +1 -0
- package/dist/components/ds-tag/ds-tag.types.d.ts +68 -0
- package/dist/components/ds-tag/ds-tag.types.d.ts.map +1 -0
- package/dist/components/ds-tag/index.cjs +1 -0
- package/dist/components/ds-tag/index.d.ts +2 -0
- package/dist/components/ds-tag/index.js +1 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.cjs +325 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.d.cts +24 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.d.cts.map +1 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.d.ts +24 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.d.ts.map +1 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.js +324 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.js.map +1 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.module.cjs +17 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.module.js +17 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.module.js.map +1 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.types.d.cts +71 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.types.d.cts.map +1 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.types.d.ts +72 -0
- package/dist/components/ds-tag-filter/ds-tag-filter.types.d.ts.map +1 -0
- package/dist/components/ds-tag-filter/hooks/use-tag-overflow-calculation.cjs +137 -0
- package/dist/components/ds-tag-filter/hooks/use-tag-overflow-calculation.js +137 -0
- package/dist/components/ds-tag-filter/hooks/use-tag-overflow-calculation.js.map +1 -0
- package/dist/components/ds-tag-filter/index.cjs +1 -0
- package/dist/components/ds-tag-filter/index.d.ts +2 -0
- package/dist/components/ds-tag-filter/index.js +1 -0
- package/dist/components/ds-tag-filter/utils.cjs +54 -0
- package/dist/components/ds-tag-filter/utils.js +52 -0
- package/dist/components/ds-tag-filter/utils.js.map +1 -0
- package/dist/components/ds-text-input/ds-text-input.js +2 -2
- package/dist/components/ds-toggle/ds-toggle.d.cts +2 -2
- package/dist/components/ds-toggle/ds-toggle.d.ts +2 -2
- package/dist/components/ds-vertical-tabs/ds-vertical-tabs.d.cts +5 -5
- package/dist/components/ds-vertical-tabs/ds-vertical-tabs.d.ts +5 -5
- package/dist/index.cjs +14 -2
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +11 -3
- package/dist/index.min.css +1 -1
- package/package.json +6 -2
- package/src/components/ds-avatar/_mixins.scss +45 -0
- package/src/components/ds-avatar/ds-avatar.module.scss +52 -0
- package/src/components/ds-avatar/ds-avatar.tsx +42 -0
- package/src/components/ds-avatar/ds-avatar.types.ts +41 -0
- package/src/components/ds-avatar/index.ts +2 -0
- package/src/components/ds-avatar-group/ds-avatar-group.module.scss +70 -0
- package/src/components/ds-avatar-group/ds-avatar-group.tsx +51 -0
- package/src/components/ds-avatar-group/ds-avatar-group.types.ts +27 -0
- package/src/components/ds-avatar-group/index.ts +2 -0
- package/src/components/ds-chip/ds-chip.tsx +2 -1
- package/src/components/ds-chip/ds-chip.types.tsx +13 -0
- package/src/components/ds-chip/index.tsx +8 -0
- package/src/components/ds-chip-group/ds-chip-group.tsx +2 -2
- package/src/components/ds-chip-group/ds-chip-group.types.ts +8 -0
- package/src/components/ds-chip-group/index.ts +1 -0
- package/src/components/ds-tag/ds-tag.module.scss +112 -0
- package/src/components/ds-tag/ds-tag.tsx +124 -0
- package/src/components/ds-tag/ds-tag.types.tsx +67 -0
- package/src/components/ds-tag/index.tsx +2 -0
- package/src/components/ds-tag-filter/ds-tag-filter.module.scss +72 -0
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +178 -0
- package/src/components/ds-tag-filter/ds-tag-filter.types.ts +68 -0
- package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +135 -0
- package/src/components/ds-tag-filter/index.ts +2 -0
- package/src/components/ds-tag-filter/utils.test.ts +401 -0
- package/src/components/ds-tag-filter/utils.ts +65 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { fitTagsInRow, getContainerAvailableWidth, getElementMeasurements } from './utils';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests here heavily test implementation details.
|
|
6
|
+
* Required to ensure proper calculations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
describe('Tag Filter Utils', () => {
|
|
10
|
+
describe('fitTagsInRow', () => {
|
|
11
|
+
it('should return count 0 for empty tag array', () => {
|
|
12
|
+
const result = fitTagsInRow([], 100, 8);
|
|
13
|
+
|
|
14
|
+
expect(result.count).toBe(0);
|
|
15
|
+
expect(result.usedWidth).toBe(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should fit all tags when they all fit in available width', () => {
|
|
19
|
+
const tagWidths = [50, 60, 70]; // total: 180 + 2 gaps (16) = 196
|
|
20
|
+
const availableWidth = 200;
|
|
21
|
+
const gap = 8;
|
|
22
|
+
|
|
23
|
+
const result = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
24
|
+
|
|
25
|
+
expect(result.count).toBe(3);
|
|
26
|
+
expect(result.usedWidth).toBe(196); // 50 + 8 + 60 + 8 + 70 = 196
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return partial count when not all tags fit', () => {
|
|
30
|
+
const tagWidths = [50, 60, 70, 80];
|
|
31
|
+
const availableWidth = 150;
|
|
32
|
+
const gap = 8;
|
|
33
|
+
|
|
34
|
+
const result = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
35
|
+
|
|
36
|
+
// 50 = 50, 50 + 8 + 60 = 118, 118 + 8 + 70 = 196 (exceeds 150)
|
|
37
|
+
expect(result.count).toBe(2);
|
|
38
|
+
expect(result.usedWidth).toBe(118); // 50 + 8 + 60 = 118
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should not add gap before the first tag', () => {
|
|
42
|
+
const tagWidths = [100];
|
|
43
|
+
const availableWidth = 100;
|
|
44
|
+
const gap = 8;
|
|
45
|
+
|
|
46
|
+
const result = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
47
|
+
|
|
48
|
+
expect(result.count).toBe(1);
|
|
49
|
+
expect(result.usedWidth).toBe(100); // No gap added for first tag
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle exact fit edge case', () => {
|
|
53
|
+
const tagWidths = [40, 52]; // 40 + 8 + 52 = 100
|
|
54
|
+
const availableWidth = 100;
|
|
55
|
+
const gap = 8;
|
|
56
|
+
|
|
57
|
+
const result = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
58
|
+
|
|
59
|
+
expect(result.count).toBe(2);
|
|
60
|
+
expect(result.usedWidth).toBe(100);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return 0 when first tag exceeds available width', () => {
|
|
64
|
+
const tagWidths = [150, 50, 60];
|
|
65
|
+
const availableWidth = 100;
|
|
66
|
+
const gap = 8;
|
|
67
|
+
|
|
68
|
+
const result = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
69
|
+
|
|
70
|
+
expect(result.count).toBe(0);
|
|
71
|
+
expect(result.usedWidth).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should handle zero gap correctly', () => {
|
|
75
|
+
const tagWidths = [30, 30, 30];
|
|
76
|
+
const availableWidth = 90;
|
|
77
|
+
const gap = 0;
|
|
78
|
+
|
|
79
|
+
const result = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
80
|
+
|
|
81
|
+
expect(result.count).toBe(3);
|
|
82
|
+
expect(result.usedWidth).toBe(90);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should account for gap in width calculation', () => {
|
|
86
|
+
const tagWidths = [45, 45]; // 45 + 8 + 45 = 98
|
|
87
|
+
const availableWidth = 95;
|
|
88
|
+
const gap = 8;
|
|
89
|
+
|
|
90
|
+
const result = fitTagsInRow(tagWidths, availableWidth, gap);
|
|
91
|
+
|
|
92
|
+
// First tag: 45, second would be 45 + 8 = 53, total = 98 > 95
|
|
93
|
+
expect(result.count).toBe(1);
|
|
94
|
+
expect(result.usedWidth).toBe(45);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('getContainerAvailableWidth', () => {
|
|
99
|
+
let mockContainer: HTMLDivElement;
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
mockContainer = document.createElement('div');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
vi.restoreAllMocks();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should calculate available width without padding', () => {
|
|
110
|
+
vi.spyOn(mockContainer, 'getBoundingClientRect').mockReturnValue({
|
|
111
|
+
width: 500,
|
|
112
|
+
height: 100,
|
|
113
|
+
top: 0,
|
|
114
|
+
left: 0,
|
|
115
|
+
bottom: 100,
|
|
116
|
+
right: 500,
|
|
117
|
+
x: 0,
|
|
118
|
+
y: 0,
|
|
119
|
+
toJSON: () => ({}),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
123
|
+
paddingLeft: '0px',
|
|
124
|
+
paddingRight: '0px',
|
|
125
|
+
} as CSSStyleDeclaration);
|
|
126
|
+
|
|
127
|
+
const result = getContainerAvailableWidth(mockContainer);
|
|
128
|
+
|
|
129
|
+
expect(result).toBe(500);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should subtract padding from container width', () => {
|
|
133
|
+
vi.spyOn(mockContainer, 'getBoundingClientRect').mockReturnValue({
|
|
134
|
+
width: 500,
|
|
135
|
+
height: 100,
|
|
136
|
+
top: 0,
|
|
137
|
+
left: 0,
|
|
138
|
+
bottom: 100,
|
|
139
|
+
right: 500,
|
|
140
|
+
x: 0,
|
|
141
|
+
y: 0,
|
|
142
|
+
toJSON: () => ({}),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
146
|
+
paddingLeft: '20px',
|
|
147
|
+
paddingRight: '30px',
|
|
148
|
+
} as CSSStyleDeclaration);
|
|
149
|
+
|
|
150
|
+
const result = getContainerAvailableWidth(mockContainer);
|
|
151
|
+
|
|
152
|
+
expect(result).toBe(450); // 500 - 20 - 30 = 450
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should handle string padding values with decimals', () => {
|
|
156
|
+
vi.spyOn(mockContainer, 'getBoundingClientRect').mockReturnValue({
|
|
157
|
+
width: 500,
|
|
158
|
+
height: 100,
|
|
159
|
+
top: 0,
|
|
160
|
+
left: 0,
|
|
161
|
+
bottom: 100,
|
|
162
|
+
right: 500,
|
|
163
|
+
x: 0,
|
|
164
|
+
y: 0,
|
|
165
|
+
toJSON: () => ({}),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
169
|
+
paddingLeft: '15.5px',
|
|
170
|
+
paddingRight: '24.5px',
|
|
171
|
+
} as CSSStyleDeclaration);
|
|
172
|
+
|
|
173
|
+
const result = getContainerAvailableWidth(mockContainer);
|
|
174
|
+
|
|
175
|
+
expect(result).toBe(460); // 500 - 15.5 - 24.5 = 460
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should default to 0 for invalid padding values', () => {
|
|
179
|
+
vi.spyOn(mockContainer, 'getBoundingClientRect').mockReturnValue({
|
|
180
|
+
width: 500,
|
|
181
|
+
height: 100,
|
|
182
|
+
top: 0,
|
|
183
|
+
left: 0,
|
|
184
|
+
bottom: 100,
|
|
185
|
+
right: 500,
|
|
186
|
+
x: 0,
|
|
187
|
+
y: 0,
|
|
188
|
+
toJSON: () => ({}),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
192
|
+
paddingLeft: '',
|
|
193
|
+
paddingRight: '',
|
|
194
|
+
} as CSSStyleDeclaration);
|
|
195
|
+
|
|
196
|
+
const result = getContainerAvailableWidth(mockContainer);
|
|
197
|
+
|
|
198
|
+
expect(result).toBe(500); // No padding subtracted
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('getElementMeasurements', () => {
|
|
203
|
+
let mockContainer: HTMLDivElement;
|
|
204
|
+
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
mockContainer = document.createElement('div');
|
|
207
|
+
document.body.appendChild(mockContainer);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
afterEach(() => {
|
|
211
|
+
document.body.removeChild(mockContainer);
|
|
212
|
+
vi.restoreAllMocks();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should measure all elements with default gap', () => {
|
|
216
|
+
// Create mock elements
|
|
217
|
+
const tag1 = document.createElement('div');
|
|
218
|
+
tag1.setAttribute('data-measure-tag', '');
|
|
219
|
+
const tag2 = document.createElement('div');
|
|
220
|
+
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
|
+
|
|
228
|
+
mockContainer.appendChild(tag1);
|
|
229
|
+
mockContainer.appendChild(tag2);
|
|
230
|
+
mockContainer.appendChild(label);
|
|
231
|
+
mockContainer.appendChild(clearButton);
|
|
232
|
+
mockContainer.appendChild(expandTag);
|
|
233
|
+
|
|
234
|
+
// Mock getBoundingClientRect for each element
|
|
235
|
+
vi.spyOn(tag1, 'getBoundingClientRect').mockReturnValue({
|
|
236
|
+
width: 80,
|
|
237
|
+
height: 30,
|
|
238
|
+
top: 0,
|
|
239
|
+
left: 0,
|
|
240
|
+
bottom: 30,
|
|
241
|
+
right: 80,
|
|
242
|
+
x: 0,
|
|
243
|
+
y: 0,
|
|
244
|
+
toJSON: () => ({}),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
vi.spyOn(tag2, 'getBoundingClientRect').mockReturnValue({
|
|
248
|
+
width: 90,
|
|
249
|
+
height: 30,
|
|
250
|
+
top: 0,
|
|
251
|
+
left: 0,
|
|
252
|
+
bottom: 30,
|
|
253
|
+
right: 90,
|
|
254
|
+
x: 0,
|
|
255
|
+
y: 0,
|
|
256
|
+
toJSON: () => ({}),
|
|
257
|
+
});
|
|
258
|
+
|
|
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
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
296
|
+
gap: '8px',
|
|
297
|
+
} as CSSStyleDeclaration);
|
|
298
|
+
|
|
299
|
+
const result = getElementMeasurements(mockContainer);
|
|
300
|
+
|
|
301
|
+
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
|
+
expect(result.gap).toBe(8);
|
|
306
|
+
});
|
|
307
|
+
|
|
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
|
+
it('should use fallback gap when gap is invalid', () => {
|
|
339
|
+
const tag1 = document.createElement('div');
|
|
340
|
+
tag1.setAttribute('data-measure-tag', '');
|
|
341
|
+
mockContainer.appendChild(tag1);
|
|
342
|
+
|
|
343
|
+
vi.spyOn(tag1, 'getBoundingClientRect').mockReturnValue({
|
|
344
|
+
width: 80,
|
|
345
|
+
height: 30,
|
|
346
|
+
top: 0,
|
|
347
|
+
left: 0,
|
|
348
|
+
bottom: 30,
|
|
349
|
+
right: 80,
|
|
350
|
+
x: 0,
|
|
351
|
+
y: 0,
|
|
352
|
+
toJSON: () => ({}),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
356
|
+
gap: '',
|
|
357
|
+
} as CSSStyleDeclaration);
|
|
358
|
+
|
|
359
|
+
const result = getElementMeasurements(mockContainer);
|
|
360
|
+
|
|
361
|
+
expect(result.gap).toBe(8); // Fallback to 8
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should handle no tags present', () => {
|
|
365
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
|
366
|
+
gap: '10px',
|
|
367
|
+
} as CSSStyleDeclaration);
|
|
368
|
+
|
|
369
|
+
const result = getElementMeasurements(mockContainer);
|
|
370
|
+
|
|
371
|
+
expect(result.tagWidths).toEqual([]);
|
|
372
|
+
expect(result.gap).toBe(10);
|
|
373
|
+
});
|
|
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
|
+
});
|
|
401
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface FitTagsResult {
|
|
2
|
+
count: number;
|
|
3
|
+
usedWidth: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ElementMeasurements {
|
|
7
|
+
tagWidths: number[];
|
|
8
|
+
labelWidth: number;
|
|
9
|
+
clearButtonWidth: number;
|
|
10
|
+
expandTagWidth: number;
|
|
11
|
+
gap: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Calculate how many tags fit within the available width.
|
|
16
|
+
* Pure function for easy testing.
|
|
17
|
+
*/
|
|
18
|
+
export function fitTagsInRow(tagWidths: number[], availableWidth: number, gap: number): FitTagsResult {
|
|
19
|
+
let usedWidth = 0;
|
|
20
|
+
let count = 0;
|
|
21
|
+
|
|
22
|
+
for (const tagWidth of tagWidths) {
|
|
23
|
+
const widthWithGap = tagWidth + (count > 0 ? gap : 0);
|
|
24
|
+
if (usedWidth + widthWithGap <= availableWidth) {
|
|
25
|
+
usedWidth += widthWithGap;
|
|
26
|
+
count++;
|
|
27
|
+
} else {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { count, usedWidth };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Calculate the available width inside a container, accounting for padding.
|
|
37
|
+
*/
|
|
38
|
+
export function getContainerAvailableWidth(container: HTMLDivElement): number {
|
|
39
|
+
const containerRect = container.getBoundingClientRect();
|
|
40
|
+
const containerStyle = getComputedStyle(container);
|
|
41
|
+
const paddingLeft = parseFloat(containerStyle.paddingLeft) || 0;
|
|
42
|
+
const paddingRight = parseFloat(containerStyle.paddingRight) || 0;
|
|
43
|
+
return containerRect.width - paddingLeft - paddingRight;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Measure all relevant elements (tags, label, buttons) inside the measurement container.
|
|
48
|
+
*/
|
|
49
|
+
export function getElementMeasurements(measurementContainer: HTMLDivElement): ElementMeasurements {
|
|
50
|
+
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
|
+
|
|
55
|
+
const computedStyle = getComputedStyle(measurementContainer);
|
|
56
|
+
const gap = parseFloat(computedStyle.gap) || 8;
|
|
57
|
+
|
|
58
|
+
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
|
+
|
|
64
|
+
return { tagWidths, labelWidth, clearButtonWidth, expandTagWidth, gap };
|
|
65
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './components/ds-alert-banner';
|
|
2
|
+
export * from './components/ds-avatar';
|
|
3
|
+
export * from './components/ds-avatar-group';
|
|
2
4
|
export * from './components/ds-breadcrumb';
|
|
3
5
|
export * from './components/ds-button';
|
|
4
6
|
export * from './components/ds-checkbox';
|
|
@@ -29,6 +31,8 @@ export * from './components/ds-status-badge';
|
|
|
29
31
|
export * from './components/ds-stepper';
|
|
30
32
|
export * from './components/ds-system-status';
|
|
31
33
|
export * from './components/ds-table';
|
|
34
|
+
export * from './components/ds-tag';
|
|
35
|
+
export * from './components/ds-tag-filter';
|
|
32
36
|
export * from './components/ds-text-input';
|
|
33
37
|
export * from './components/ds-textarea';
|
|
34
38
|
export * from './components/ds-toast';
|