44-->
55
66<script setup lang="ts">
7+ import type { IPreviewUser , ITeam } from ' ../types/contactsMenu.ts'
8+
79import { mdiAccountGroupOutline , mdiContacts , mdiMagnify } from ' @mdi/js'
810import { getCurrentUser } from ' @nextcloud/auth'
911import axios from ' @nextcloud/axios'
@@ -14,6 +16,7 @@ import debounce from 'debounce'
1416import { computed , nextTick , onMounted , ref , watch } from ' vue'
1517import NcActionButton from ' @nextcloud/vue/components/NcActionButton'
1618import NcActions from ' @nextcloud/vue/components/NcActions'
19+ import NcAvatar from ' @nextcloud/vue/components/NcAvatar'
1720import NcButton from ' @nextcloud/vue/components/NcButton'
1821import NcEmptyContent from ' @nextcloud/vue/components/NcEmptyContent'
1922import NcHeaderMenu from ' @nextcloud/vue/components/NcHeaderMenu'
@@ -42,15 +45,13 @@ const hasError = ref(false)
4245const searchTerm = ref (' ' )
4346
4447const teams = ref <ITeam []>([])
45- const selectedTeam = ref <string >(' $_all_$' )
48+ const storedTeam = storage .getItem (' core:contacts:team' )
49+ const selectedTeam = ref <string >(storedTeam ? JSON .parse (storedTeam ) : ' $_all_$' )
4650const selectedTeamName = computed (() => teams .value .find ((t ) => t .teamId === selectedTeam .value )?.displayName )
51+ const previewUsers = ref <IPreviewUser []>([])
52+ const showAvatarStack = computed (() => previewUsers .value .length >= 2 )
4753
4854onMounted (async () => {
49- const team = storage .getItem (' core:contacts:team' )
50- if (team ) {
51- selectedTeam .value = JSON .parse (team )
52- }
53-
5455 if (userTeams .length === 0 ) {
5556 try {
5657 const { data } = await axios .get <ITeam []>(generateUrl (' /contactsmenu/teams' ))
@@ -65,8 +66,29 @@ onMounted(async () => {
6566watch (selectedTeam , () => {
6667 storage .setItem (' core:contacts:team' , JSON .stringify (selectedTeam .value ))
6768 getContacts (searchTerm .value )
69+ loadPreviewAvatars ()
6870})
6971
72+ /**
73+ * Load avatars for the People menu header trigger
74+ */
75+ async function loadPreviewAvatars() {
76+ try {
77+ const { data } = await axios .get <IPreviewUser []>(generateUrl (' /contactsmenu/preview-avatars' ), {
78+ params: {
79+ teamId: selectedTeam .value !== ' $_all_$' ? selectedTeam .value : undefined ,
80+ },
81+ })
82+ previewUsers .value = data
83+ } catch (error ) {
84+ logger .error (' could not load preview avatars' , { error })
85+ previewUsers .value = []
86+ }
87+ }
88+
89+ // Seeded selectedTeam above so this runs once on mount with the correct team
90+ loadPreviewAvatars ()
91+
7092/**
7193 * Load contacts when opening the menu
7294 */
@@ -132,11 +154,7 @@ function focusInput() {
132154 </script >
133155
134156<script lang="ts">
135- interface ITeam {
136- teamId: string
137- displayName: string
138- link: string
139- }
157+ import type { ITeam } from ' ../types/contactsMenu.ts'
140158
141159const userTeams: ITeam [] = []
142160 </script >
@@ -145,11 +163,32 @@ const userTeams: ITeam[] = []
145163 <NcHeaderMenu
146164 id="contactsmenu"
147165 class="contactsmenu"
166+ :class =" { ' contactsmenu--avatar-stack' : showAvatarStack } "
148167 :aria-label =" t (' core' , ' Search contacts' )"
149168 exclude-click-outside-selectors=".v-popper__popper "
150169 @open =" onOpened " >
151170 <template #trigger >
152- <NcIconSvgWrapper class="contactsmenu__trigger-icon" :path =" mdiContacts " />
171+ <span
172+ v-if =" showAvatarStack"
173+ class =" contactsmenu__trigger-avatars"
174+ aria-hidden =" true" >
175+ <NcAvatar
176+ v-for =" (previewUser , index ) in previewUsers "
177+ :key =" previewUser .isUser ? previewUser .uid : ` ${previewUser .fullName }-${index } ` "
178+ class="contactsmenu__trigger-avatars__avatar"
179+ :style =" { zIndex: previewUsers .length - index } "
180+ :user =" previewUser .isUser ? previewUser .uid : undefined "
181+ :is-no-user =" ! previewUser .isUser "
182+ :display-name =" previewUser .fullName "
183+ :size =" 32 "
184+ disable-menu
185+ disable-tooltip
186+ hide-status />
187+ </span >
188+ <NcIconSvgWrapper
189+ v-else
190+ class="contactsmenu__trigger-icon"
191+ :path =" mdiContacts " />
153192 </template >
154193 <div class =" contactsmenu__menu" >
155194 <div class =" contactsmenu__menu__search-container" >
@@ -242,12 +281,67 @@ const userTeams: ITeam[] = []
242281
243282<style lang="scss" scoped>
244283.contactsmenu {
245- overflow-y : hidden ;
284+ margin-inline-end : calc (2 * var (--default-grid-baseline ));
285+
286+ :deep (.header-menu__trigger ) {
287+ // NcHeaderMenu applies --header-menu-icon-mask (vertical alpha fade) to
288+ // .button-vue__icon:not(:has(svg)). Avatars need the full face visible.
289+ .button- vue__icon:has (.contactsmenu__trigger-avatars ) {
290+ mask : none !important ;
291+ }
292+ }
293+
294+ & --avatar-stack {
295+ width : fit-content !important ;
296+ min-width : var (--header-height );
297+ overflow : visible ;
298+ flex-shrink : 0 ;
299+
300+ :deep (.header-menu__trigger ) {
301+ width : fit-content !important ;
302+ min-width : var (--header-height );
303+ max-width : none ;
304+ overflow : visible !important ;
305+ padding-inline : var (--default-grid-baseline );
306+
307+ .button-vue__wrapper {
308+ width : auto ;
309+ justify-content : center ;
310+ }
311+
312+ .button-vue__icon {
313+ width : auto !important ;
314+ min-width : 0 ;
315+ max-width : none ;
316+ height : auto ;
317+ min-height : 0 ;
318+ overflow : visible ;
319+ }
320+ }
321+ }
246322
247323 & __trigger-icon {
248324 color : var (--color-background-plain-text ) !important ;
249325 }
250326
327+ & __trigger-avatars {
328+ display : flex ;
329+ align-items : center ;
330+ pointer-events : none ;
331+
332+ & __avatar {
333+ box-sizing : content-box ;
334+ flex-shrink : 0 ;
335+ --contactsmenu-avatar-outline : var (--border-width-input ) solid color-mix (in srgb , var (--color-background-plain-text ), transparent 75% );
336+ outline : var (--contactsmenu-avatar-outline );
337+ margin-inline-start : -12px ;
338+
339+ & :first-child {
340+ margin-inline-start : 0 ;
341+ }
342+ }
343+ }
344+
251345 & __menu {
252346 display : flex ;
253347 flex-direction : column ;
0 commit comments