@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,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcita/design-system",
3
- "version": "1.18.1",
3
+ "version": "1.18.2",
4
4
  "description": "vcita design system",
5
5
  "author": "vcita",
6
6
  "scripts": {
@@ -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(1);
190
- expect(params.hiddenAvatars.length).toBe(2);
191
- expect(params.hiddenAvatars[0]).toEqual(avatarsMock[1]);
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: 'First Avatar',
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 = 0; i < 24; 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: 'Last Avatar', colorId: avatars[avatars.length-1].colorId + 1 });
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
- <v-tooltip v-for="(avatar, i) in visibleAvatars"
9
- :key="i"
10
- :bottom="!tooltipTop"
11
- :top="tooltipTop"
8
+ <div v-for="(avatar, i) in visibleAvatars"
9
+ :key="i"
10
+ class="avatar-item-wrapper"
12
11
  >
13
- <template v-slot:activator="{ on, attrs }">
14
- <span
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
- </template>
32
- {{ avatar.name || avatar.text }}
33
- </v-tooltip>
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 "@/components/VcAvatar/VcAvatar.vue";
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
- if (this.hiddenAvatars.length) {
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
- + .avatar-item-wrapper {
208
+ ~ .avatar-item-wrapper {
201
209
  margin-inline-start: -8px;
202
210
  }
203
211
  }