ultisuite-client/components/gmail/contacts/contact-avatar.tsx
R3D347HR4Y 07d57f13a8
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
Add Contact Avatar Features and Improve UI Components
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views.
- Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts.
- Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface.
- Added loading and error states in ContactsListView for better user feedback during data fetching.
- Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list.
- Updated package.json to include @formkit/auto-animate for improved UI animations.
2026-06-06 20:26:51 +02:00

79 lines
2.7 KiB
TypeScript

"use client"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { gravatarUrl, primaryContactEmail } from "@/lib/contact-avatar"
import { avatarColor, senderInitial } from "@/lib/sender-display"
import { cn } from "@/lib/utils"
import type { FullContact } from "@/lib/contacts/types"
export type ContactAvatarSize = "xs" | "sm" | "md" | "lg" | "xl" | "2xl"
const SIZE_CONFIG: Record<
ContactAvatarSize,
{ className: string; gravatar: number; text: string }
> = {
xs: { className: "size-8", gravatar: 64, text: "text-xs" },
sm: { className: "size-10", gravatar: 80, text: "text-sm" },
md: { className: "size-14", gravatar: 112, text: "text-lg" },
lg: { className: "size-20", gravatar: 160, text: "text-2xl" },
xl: { className: "size-24", gravatar: 192, text: "text-3xl" },
"2xl": { className: "size-28", gravatar: 224, text: "text-4xl" },
}
export interface ContactAvatarProps {
contact?: Pick<FullContact, "avatarUrl" | "emails" | "firstName" | "lastName">
/** Override display name for initials fallback. */
name?: string
/** Override email for Gravatar fallback. */
email?: string
/** Override stored avatar URL. */
avatarUrl?: string
size?: ContactAvatarSize
className?: string
alt?: string
}
export function contactAvatarLabel(
contact: Pick<FullContact, "firstName" | "lastName" | "emails"> | undefined,
nameOverride?: string,
emailOverride?: string,
): string {
if (nameOverride?.trim()) return nameOverride.trim()
if (contact) {
const fromName = `${contact.firstName ?? ""} ${contact.lastName ?? ""}`.trim()
if (fromName) return fromName
}
return emailOverride?.trim() || primaryContactEmail(contact ?? {}) || "?"
}
export function ContactAvatar({
contact,
name: nameOverride,
email: emailOverride,
avatarUrl: avatarUrlOverride,
size = "sm",
className,
alt,
}: ContactAvatarProps) {
const config = SIZE_CONFIG[size]
const name = contactAvatarLabel(contact, nameOverride, emailOverride)
const email = emailOverride?.trim() || primaryContactEmail(contact ?? {})
const avatarUrl = avatarUrlOverride ?? contact?.avatarUrl
const gravatar = email ? gravatarUrl(email, config.gravatar) : undefined
const initial = senderInitial(name)
const color = avatarColor(name)
return (
<Avatar className={cn("shrink-0", config.className, className)}>
{avatarUrl ? <AvatarImage src={avatarUrl} alt={alt ?? name} /> : null}
{gravatar ? <AvatarImage src={gravatar} alt={alt ?? name} /> : null}
<AvatarFallback
className={cn("font-medium text-white", config.text)}
style={{ backgroundColor: color }}
>
{initial}
</AvatarFallback>
</Avatar>
)
}