1<template>
2 <div
3 v-if="_avatars?.length"
4 class="isolate me-1.5 flex min-w-0 cursor-pointer items-center"
5 >
6 <div
7 v-if="_avatars?.length == 1"
8 class="user-avatar flex min-w-0 items-center gap-2 text-base line-clamp-1"
9 :data-name="_avatars[0].name"
10 >
11 <Tooltip :text="_avatars[0].name">
12 <Avatar
13 shape="circle"
14 :image="_avatars[0].image"
15 :label="_avatars[0].label"
16 size="sm"
17 />
18 <div class="min-w-0 truncate" v-if="!hideName">
19 {{ _avatars[0].label }}
20 </div>
21 </Tooltip>
22 </div>
23 <template v-else>
24 <Tooltip
25 v-for="avatar in visibleAvatars"
26 :key="avatar.name"
27 :text="avatar.name"
28 >
29 <Avatar
30 class="user-avatar -mr-1.5 ring-2 ring-[var(--surface-base)] transition hover:z-20 hover:scale-110"
31 shape="circle"
32 :image="avatar.image"
33 :label="avatar.label"
34 :size="size"
35 :data-name="avatar.name"
36 />
37 </Tooltip>
38 <Tooltip v-if="overflowCount" :text="overflowNames">
39 <div
40 class="user-avatar relative z-10 -mr-1.5 flex size-5 shrink-0 items-center justify-center rounded-full bg-surface-gray-3 text-xs text-ink-gray-7 ring-2 ring-[var(--surface-base)]"
41 >
42 +{{ overflowCount }}
43 </div>
44 </Tooltip>
45 </template>
46 </div>
47</template>
48<script setup lang="ts">
49import { useUserStore } from "@/stores/user";
50import { Avatar, Tooltip } from "frappe-ui";
51import { computed } from "vue";
52
53const props = defineProps({
54 // Frappe hands `_assign` over as a JSON string; callers with the list
55 // already parsed pass the array itself.
56 avatars: {
57 type: [String, Array],
58 },
59 size: {
60 type: String,
61 default: "sm",
62 },
63 hideName: {
64 type: Boolean,
65 default: false,
66 },
67 // Cap the number of avatars shown; the rest collapse into a "+n" chip.
68 // 0 (default) shows all, so existing callers are unaffected.
69 max: {
70 type: Number,
71 default: 0,
72 },
73});
74const { getUser } = useUserStore();
75const _avatars = computed(() => {
76 let result: any;
77 try {
78 result = JSON.parse(props.avatars);
79 } catch (error) {
80 result = props.avatars;
81 }
82 if (!result) return;
83 if (result[0]?.hasOwnProperty("name")) {
84 return result;
85 }
86 result = result.map((a: string) => {
87 let _user = getUser(a);
88 return {
89 name: _user.name,
90 label: _user.full_name,
91 image: _user.user_image,
92 };
93 });
94 return result;
95});
96
97const capped = computed(
98 () => props.max > 0 && (_avatars.value?.length || 0) > props.max
99);
100
101const visibleAvatars = computed(() =>
102 capped.value ? _avatars.value.slice(0, props.max) : _avatars.value
103);
104
105const overflowCount = computed(() =>
106 capped.value ? _avatars.value.length - props.max : 0
107);
108
109const overflowNames = computed(() =>
110 capped.value
111 ? _avatars.value
112 .slice(props.max)
113 .map((a: { name: string }) => a.name)
114 .join(", ")
115 : ""
116);
117</script>
118