@vcita/design-system 1.18.1 → 1.18.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.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '@testing-library/jest-dom';
|
|
2
2
|
import VcAvatarStack from './VcAvatarStack.vue';
|
|
3
3
|
import Vuetify from 'vuetify';
|
|
4
|
-
import { render } from '@testing-library/vue';
|
|
4
|
+
import { render, waitFor } from '@testing-library/vue';
|
|
5
5
|
import init from '../../../testing-library.config';
|
|
6
6
|
|
|
7
7
|
init();
|
|
@@ -55,6 +55,7 @@ describe('VcAvatarStack.vue', () => {
|
|
|
55
55
|
},
|
|
56
56
|
...additionalRefs,
|
|
57
57
|
},
|
|
58
|
+
handleMoreIndicator: jest.fn(),
|
|
58
59
|
});
|
|
59
60
|
|
|
60
61
|
it('mounts with standard content', async () => {
|
|
@@ -180,15 +181,17 @@ describe('VcAvatarStack.vue', () => {
|
|
|
180
181
|
1: [{ $el: { offsetTop: 0, offsetHeight: 40 } }],
|
|
181
182
|
2: [{ $el: { offsetTop: 40, offsetHeight: 40 } }], // Overflowing avatar
|
|
182
183
|
});
|
|
183
|
-
|
|
184
|
+
params.avatarOverflows = jest.fn().mockImplementation((el) => {
|
|
185
|
+
return el.offsetTop === 40;
|
|
186
|
+
});
|
|
184
187
|
await VcAvatarStack.methods.processAvatars.apply(params);
|
|
185
188
|
// eslint-disable-next-line no-undef
|
|
186
189
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
187
190
|
|
|
188
191
|
// Assertions
|
|
189
|
-
expect(params.visibleAvatars.length).toBe(
|
|
190
|
-
expect(params.hiddenAvatars.length).toBe(
|
|
191
|
-
expect(params.hiddenAvatars[0]).toEqual(avatarsMock[
|
|
192
|
+
expect(params.visibleAvatars.length).toBe(2);
|
|
193
|
+
expect(params.hiddenAvatars.length).toBe(1);
|
|
194
|
+
expect(params.hiddenAvatars[0]).toEqual(avatarsMock[2]);
|
|
192
195
|
});
|
|
193
196
|
|
|
194
197
|
it('passes showBorder prop to VcAvatar components', () => {
|
|
@@ -220,7 +223,7 @@ describe('VcAvatarStack.vue', () => {
|
|
|
220
223
|
it('sets processingDone to true after processing avatars', async () => {
|
|
221
224
|
const spyEmit = jest.fn();
|
|
222
225
|
const params = mockParams(spyEmit);
|
|
223
|
-
|
|
226
|
+
params.avatarOverflows = jest.fn();
|
|
224
227
|
await VcAvatarStack.methods.processAvatars.apply(params);
|
|
225
228
|
|
|
226
229
|
expect(params.processingDone).toBe(true);
|
|
@@ -240,4 +243,120 @@ describe('VcAvatarStack.vue', () => {
|
|
|
240
243
|
expect(getByText('A2')).toBeInTheDocument();
|
|
241
244
|
});
|
|
242
245
|
|
|
246
|
+
it('moves an avatar from visibleAvatars to hiddenAvatars when more indicator is below avatar list', async () => {
|
|
247
|
+
const params = {
|
|
248
|
+
hiddenAvatars: [avatarsMock[2]],
|
|
249
|
+
visibleAvatars: [avatarsMock[0], avatarsMock[1]],
|
|
250
|
+
$refs: {
|
|
251
|
+
'more-indicator': {
|
|
252
|
+
$el: { offsetTop: 150, offsetHeight: 50 },
|
|
253
|
+
},
|
|
254
|
+
'avatar-list': { offsetTop: 0, },
|
|
255
|
+
},
|
|
256
|
+
$nextTick: jest.fn().mockResolvedValue(null),
|
|
257
|
+
};
|
|
258
|
+
await VcAvatarStack.methods.handleMoreIndicator.apply(params, [params.$refs['avatar-list']]);
|
|
259
|
+
// eslint-disable-next-line no-undef
|
|
260
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
261
|
+
|
|
262
|
+
// Assertions
|
|
263
|
+
expect(params.visibleAvatars.length).toBe(1);
|
|
264
|
+
expect(params.hiddenAvatars.length).toBe(2);
|
|
265
|
+
expect(params.hiddenAvatars[0]).toEqual(avatarsMock[1]);
|
|
266
|
+
expect(params.hiddenAvatars[1]).toEqual(avatarsMock[2]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('handles maxVisibleAvatars correctly', async () => {
|
|
270
|
+
const { queryAllByTestId } = renderWithVuetify(VcAvatarStack, {
|
|
271
|
+
props: { ...props, maxVisibleAvatars: 2 },
|
|
272
|
+
data() {
|
|
273
|
+
return {
|
|
274
|
+
visibleAvatars: [],
|
|
275
|
+
hiddenAvatars: [],
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
await waitFor(async () => {
|
|
280
|
+
// We have 3 avatars but the maxVisibleAvatars is 2.
|
|
281
|
+
const visibleAvatars = queryAllByTestId(/vc-avatar-item-/);
|
|
282
|
+
expect(visibleAvatars.length).toBe(1); // Should show only 2 avatars
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('processes avatars correctly when maxVisibleAvatars is null', async () => {
|
|
287
|
+
const { queryAllByTestId } = renderWithVuetify(VcAvatarStack, {
|
|
288
|
+
props: { ...props, maxVisibleAvatars: null },
|
|
289
|
+
data() {
|
|
290
|
+
return {
|
|
291
|
+
visibleAvatars: [],
|
|
292
|
+
hiddenAvatars: [],
|
|
293
|
+
};
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Check the avatars processed
|
|
298
|
+
await VcAvatarStack.methods.processAvatars.apply({
|
|
299
|
+
visibleAvatars: [],
|
|
300
|
+
hiddenAvatars: [],
|
|
301
|
+
avatars: avatarsMock,
|
|
302
|
+
$nextTick: jest.fn().mockResolvedValue(null),
|
|
303
|
+
$refs: {
|
|
304
|
+
'avatar-list': { offsetTop: 0, offsetHeight: 200 },
|
|
305
|
+
},
|
|
306
|
+
avatarOverflows: jest.fn(),
|
|
307
|
+
handleMoreIndicator: jest.fn(),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const visibleAvatars = queryAllByTestId(/vc-avatar-item-/);
|
|
311
|
+
expect(visibleAvatars.length).toBe(3); // No limit on maxVisibleAvatars
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('processes avatars correctly when some avatars overflow', async () => {
|
|
315
|
+
const params = {
|
|
316
|
+
visibleAvatars: [],
|
|
317
|
+
hiddenAvatars: [],
|
|
318
|
+
avatars: avatarsMock,
|
|
319
|
+
$nextTick: jest.fn().mockResolvedValue(null),
|
|
320
|
+
$refs: {
|
|
321
|
+
'avatar-list': { offsetTop: 0, offsetHeight: 200 },
|
|
322
|
+
// Mocking the refs for each avatar
|
|
323
|
+
0: [{ $el: { offsetTop: 0, offsetHeight: 40 } }], // First avatar
|
|
324
|
+
1: [{ $el: { offsetTop: 80, offsetHeight: 40 } }], // Second avatar
|
|
325
|
+
2: [{ $el: { offsetTop: 80, offsetHeight: 40 } }], // Third avatar, which will overflow
|
|
326
|
+
},
|
|
327
|
+
handleMoreIndicator: jest.fn(),
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
params.avatarOverflows = jest.fn().mockImplementation((el) => {
|
|
331
|
+
return el.offsetTop >= 80; // Simulating overflow condition for the 3rd avatar
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await VcAvatarStack.methods.processAvatars.apply(params);
|
|
335
|
+
|
|
336
|
+
expect(params.hiddenAvatars.length).toBe(2); // The 2nd and 3rd avatars should be hidden
|
|
337
|
+
expect(params.hiddenAvatars[0]).toEqual(avatarsMock[1]); // Check the correct avatar is hidden
|
|
338
|
+
expect(params.hiddenAvatars[1]).toEqual(avatarsMock[2]); // Check the correct avatar is hidden
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('moves a visible avatar to hiddenAvatars when more indicator is not overflowing', async () => {
|
|
342
|
+
const params = {
|
|
343
|
+
hiddenAvatars: [avatarsMock[2]],
|
|
344
|
+
visibleAvatars: [avatarsMock[0], avatarsMock[1]],
|
|
345
|
+
$refs: {
|
|
346
|
+
'more-indicator': {
|
|
347
|
+
$el: { offsetTop: 0, offsetHeight: 50 },
|
|
348
|
+
},
|
|
349
|
+
'avatar-list': { offsetTop: 0, },
|
|
350
|
+
},
|
|
351
|
+
$nextTick: jest.fn().mockResolvedValue(null),
|
|
352
|
+
};
|
|
353
|
+
await VcAvatarStack.methods.handleMoreIndicator.apply(params, [params.$refs['avatar-list']]);
|
|
354
|
+
// eslint-disable-next-line no-undef
|
|
355
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
356
|
+
|
|
357
|
+
// Assertions
|
|
358
|
+
expect(params.visibleAvatars.length).toBe(2);
|
|
359
|
+
expect(params.hiddenAvatars.length).toBe(1);
|
|
360
|
+
expect(params.hiddenAvatars[0]).toEqual(avatarsMock[2]);
|
|
361
|
+
});
|
|
243
362
|
});
|
|
@@ -3,15 +3,15 @@ import VcBaseDocs from '@/stories/VcBaseDocs.mdx';
|
|
|
3
3
|
|
|
4
4
|
const avatar1 = {
|
|
5
5
|
colorId: 1,
|
|
6
|
-
name: '
|
|
6
|
+
name: 'John Smith 0',
|
|
7
7
|
path: require('@/stories/assets/pics/avatar1.png'),
|
|
8
8
|
}
|
|
9
9
|
const avatars = [avatar1];
|
|
10
10
|
|
|
11
|
-
for (let i =
|
|
12
|
-
avatars.push({ name: 'John Smith', colorId: i + 1 });
|
|
11
|
+
for (let i = 1; i < 24; i++) {
|
|
12
|
+
avatars.push({ name: 'John Smith ' + i, colorId: i + 1 });
|
|
13
13
|
}
|
|
14
|
-
avatars.push({ name: '
|
|
14
|
+
avatars.push({ name: 'John Smith ' + avatars.length, colorId: avatars[avatars.length-1].colorId + 1 });
|
|
15
15
|
|
|
16
16
|
const baseProps = {
|
|
17
17
|
avatars,
|
|
@@ -5,17 +5,16 @@
|
|
|
5
5
|
:data-qa="dataQa"
|
|
6
6
|
:class="{'VcChipList--loaded': processingDone}"
|
|
7
7
|
>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
:top="tooltipTop"
|
|
8
|
+
<div v-for="(avatar, i) in visibleAvatars"
|
|
9
|
+
:key="i"
|
|
10
|
+
class="avatar-item-wrapper"
|
|
12
11
|
>
|
|
13
|
-
<
|
|
14
|
-
<
|
|
12
|
+
<v-tooltip :bottom="!tooltipTop" :top="tooltipTop">
|
|
13
|
+
<template v-slot:activator="{ on, attrs }">
|
|
14
|
+
<span
|
|
15
15
|
v-show="visibleAvatars.length"
|
|
16
16
|
v-bind="attrs"
|
|
17
17
|
v-on="on"
|
|
18
|
-
class="avatar-item-wrapper"
|
|
19
18
|
>
|
|
20
19
|
<VcAvatar
|
|
21
20
|
:image-path="avatar.path"
|
|
@@ -28,9 +27,11 @@
|
|
|
28
27
|
:ref="i"
|
|
29
28
|
/>
|
|
30
29
|
</span>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
</template>
|
|
31
|
+
{{ avatar.name || avatar.text }}
|
|
32
|
+
</v-tooltip>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
34
35
|
|
|
35
36
|
<v-tooltip
|
|
36
37
|
:bottom="!tooltipTop"
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
v-bind="attrs"
|
|
45
46
|
v-on="on"
|
|
46
47
|
data-qa="more-indicator"
|
|
47
|
-
class="avatar-item-wrapper"
|
|
48
|
+
:class="{'avatar-item-wrapper':visibleAvatars.length}"
|
|
48
49
|
:data-text="'+' + hiddenAvatars.length"
|
|
49
50
|
>
|
|
50
51
|
<VcAvatar
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
|
|
71
72
|
<script>
|
|
72
73
|
import debounce from "just-debounce";
|
|
73
|
-
import VcAvatar from "
|
|
74
|
+
import VcAvatar from "../VcAvatar/VcAvatar.vue";
|
|
74
75
|
|
|
75
76
|
export default {
|
|
76
77
|
name: "VcAvatarStack",
|
|
@@ -113,21 +114,27 @@ export default {
|
|
|
113
114
|
this.processAvatars();
|
|
114
115
|
},
|
|
115
116
|
methods: {
|
|
117
|
+
handleMaxAvatars() {
|
|
118
|
+
const totalAvatars = this.avatars.length;
|
|
119
|
+
let visibleCount = Math.min(this.maxVisibleAvatars, totalAvatars);
|
|
120
|
+
if (totalAvatars > this.maxVisibleAvatars) visibleCount -= 1;
|
|
121
|
+
this.visibleAvatars = this.avatars.slice(0, visibleCount);
|
|
122
|
+
this.hiddenAvatars = totalAvatars > visibleCount ? this.avatars.slice(visibleCount) : [];
|
|
123
|
+
},
|
|
116
124
|
async processAvatars() {
|
|
117
125
|
this.processingDone = false;
|
|
118
126
|
await this.$nextTick(); // wait for avatar list height to render
|
|
119
127
|
|
|
128
|
+
if (this.maxVisibleAvatars) {
|
|
129
|
+
this.handleMaxAvatars();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
120
132
|
const avatars = this.avatars;
|
|
121
133
|
this.visibleAvatars = [];
|
|
122
134
|
this.hiddenAvatars = [];
|
|
123
135
|
const avatarListEl = this.$refs["avatar-list"];
|
|
124
136
|
if (!avatarListEl) return;
|
|
125
137
|
|
|
126
|
-
let avatarListTopHeight =
|
|
127
|
-
avatarListEl.offsetTop + avatarListEl.offsetHeight;
|
|
128
|
-
const moreIndicatorEl = this.$refs["more-indicator"].$el
|
|
129
|
-
? this.$refs["more-indicator"].$el
|
|
130
|
-
: this.$refs["more-indicator"];
|
|
131
138
|
let overflowReached = false;
|
|
132
139
|
for (let i in avatars) {
|
|
133
140
|
if (overflowReached) {
|
|
@@ -139,30 +146,31 @@ export default {
|
|
|
139
146
|
await this.$nextTick(); // wait for avatar to render
|
|
140
147
|
|
|
141
148
|
const avatarEl = this.$refs[i] && this.$refs[i][0]?.$el;
|
|
142
|
-
if (
|
|
143
|
-
!avatarEl ||
|
|
144
|
-
avatarEl.offsetTop >= avatarListTopHeight ||
|
|
145
|
-
this.maxVisibleAvatars === this.visibleAvatars.length - 1
|
|
146
|
-
) {
|
|
149
|
+
if (this.avatarOverflows(avatarEl, avatarListEl)) {
|
|
147
150
|
this.hiddenAvatars.push(this.visibleAvatars.pop());
|
|
148
151
|
overflowReached = true;
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
await this.$nextTick(); // wait for more indicator to render
|
|
155
|
+
await this.handleMoreIndicator(avatarListEl);
|
|
154
156
|
|
|
155
|
-
// if the 'more' indicator is overflowed, hide the last visible avatar and show it in the tooltip
|
|
156
|
-
if (
|
|
157
|
-
moreIndicatorEl.offsetTop + moreIndicatorEl.offsetHeight >=
|
|
158
|
-
avatarListTopHeight
|
|
159
|
-
) {
|
|
160
|
-
this.hiddenAvatars.unshift(this.visibleAvatars.pop());
|
|
161
|
-
await this.$nextTick();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
157
|
this.processingDone = true;
|
|
165
158
|
},
|
|
159
|
+
avatarOverflows(avatarEl, avatarListEl) {
|
|
160
|
+
return (
|
|
161
|
+
!avatarEl
|
|
162
|
+
|| avatarEl.offsetTop > avatarListEl.offsetTop
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
async handleMoreIndicator(avatarListEl) {
|
|
166
|
+
if(!this.hiddenAvatars.length) return;
|
|
167
|
+
await this.$nextTick(); // wait for more indicator to render
|
|
168
|
+
// if the 'more' indicator is overflowed, hide the last visible avatar and show it in the tooltip
|
|
169
|
+
const moreIndicatorEl = this.$refs['more-indicator']?.$el || this.$refs['more-indicator'];
|
|
170
|
+
if (moreIndicatorEl?.offsetTop > avatarListEl.offsetTop) {
|
|
171
|
+
this.hiddenAvatars.unshift(this.visibleAvatars.pop());
|
|
172
|
+
}
|
|
173
|
+
},
|
|
166
174
|
},
|
|
167
175
|
watch: {
|
|
168
176
|
avatars: {
|
|
@@ -197,7 +205,7 @@ export default {
|
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
.avatar-item-wrapper {
|
|
200
|
-
|
|
208
|
+
~ .avatar-item-wrapper {
|
|
201
209
|
margin-inline-start: -8px;
|
|
202
210
|
}
|
|
203
211
|
}
|