import { NgClass } from "@angular/common"; import { Component, computed, input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; type SizeTypes = "xlarge" | "large" | "default" | "small" | "xsmall"; const SizeClasses: Record = { xlarge: ["tw-h-24", "tw-w-24", "tw-min-w-24"], large: ["tw-h-16", "tw-w-16", "tw-min-w-16"], default: ["tw-h-10", "tw-w-10", "tw-min-w-10"], small: ["tw-h-7", "tw-w-7", "tw-min-w-7"], xsmall: ["tw-h-6", "tw-w-6", "tw-min-w-6"], }; /** * Avatars display a unique color that helps a user visually recognize their logged in account. * A variance in color across the avatar component is important as it is used in Account Switching as a * visual indicator to recognize which of a personal or work account a user is logged into. */ // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-avatar", template: ` {{ displayChars() }} `, imports: [NgClass], }) export class AvatarComponent { readonly border = input(false); readonly color = input(); readonly id = input(); readonly text = input(); readonly title = input(); readonly size = input("default"); protected readonly svgCharCount = 2; protected readonly svgFontSize = 20; protected readonly svgFontWeight = 300; protected readonly svgSize = 48; protected readonly classList = computed(() => { return ["tw-rounded-full"] .concat(SizeClasses[this.size()] ?? []) .concat(this.border() ? ["tw-border", "tw-border-solid", "tw-border-secondary-600"] : []); }); protected readonly backgroundColor = computed(() => { const id = this.id(); const upperCaseText = this.text()?.toUpperCase() ?? ""; if (!Utils.isNullOrWhitespace(this.color())) { return this.color()!; } if (!Utils.isNullOrWhitespace(id)) { return Utils.stringToColor(id!.toString()); } return Utils.stringToColor(upperCaseText); }); protected readonly textColor = computed(() => { return Utils.pickTextColorBasedOnBgColor(this.backgroundColor(), 135, true); }); protected readonly displayChars = computed(() => { const upperCaseText = this.text()?.toUpperCase() ?? ""; let chars = this.getFirstLetters(upperCaseText, this.svgCharCount); if (chars == null) { chars = this.unicodeSafeSubstring(upperCaseText, this.svgCharCount); } // If the chars contain an emoji, only show it. const emojiMatch = chars.match(Utils.regexpEmojiPresentation); if (emojiMatch) { chars = emojiMatch[0]; } return chars; }); private getFirstLetters(data: string, count: number): string | undefined { const parts = data.split(" "); if (parts.length > 1) { let text = ""; for (let i = 0; i < count; i++) { text += this.unicodeSafeSubstring(parts[i], 1); } return text; } return undefined; } private unicodeSafeSubstring(str: string, count: number) { const characters = str.match(/./gu); return characters != null ? characters.slice(0, count).join("") : ""; } }