1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-22 04:14:04 +00:00

cr changes

This commit is contained in:
Vicki League
2026-02-13 11:49:52 -05:00
parent fddcb1535d
commit 3577cc12e1
3 changed files with 40 additions and 25 deletions

View File

@@ -22,6 +22,7 @@ import {
DialogConfig,
DialogRef,
DialogService,
isAvatarColor,
ToastService,
} from "@bitwarden/components";
@@ -98,7 +99,7 @@ export class ChangeAvatarDialogComponent implements OnInit, OnDestroy {
}
submit = async () => {
const defaultColorSelected = AvatarDefaultColors.includes(this.currentSelection);
const defaultColorSelected = isAvatarColor(this.currentSelection);
const isValidHex = Utils.validateHexColor(this.currentSelection);
const isValidSelection = this.currentSelection == null || defaultColorSelected || isValidHex;

View File

@@ -4,7 +4,7 @@ import { Component, Input, OnDestroy } from "@angular/core";
import { Subject } from "rxjs";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AvatarSizes } from "@bitwarden/components";
import { AvatarSize } from "@bitwarden/components";
import { SharedModule } from "../shared";
@@ -37,7 +37,7 @@ export class DynamicAvatarComponent implements OnDestroy {
@Input() title: string;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input() size: AvatarSizes = "base";
@Input() size: AvatarSize = "base";
private destroy$ = new Subject<void>();
color$ = this.avatarService.avatarColor$;

View File

@@ -13,12 +13,12 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { AriaDisableDirective } from "../a11y";
import { ariaDisableElement } from "../utils";
export type AvatarSizes = "2xlarge" | "xlarge" | "large" | "base" | "small";
export type AvatarSize = "2xlarge" | "xlarge" | "large" | "base" | "small";
export const AvatarDefaultColors = ["teal", "coral", "brand", "green", "purple"];
export type AvatarColors = (typeof AvatarDefaultColors)[number];
export const AvatarDefaultColors = ["teal", "coral", "brand", "green", "purple"] as const;
export type AvatarColor = (typeof AvatarDefaultColors)[number];
const SizeClasses: Record<AvatarSizes, string[]> = {
const sizeClasses: Record<AvatarSize, string[]> = {
"2xlarge": ["tw-h-16", "tw-w-16", "tw-min-w-16"],
xlarge: ["tw-h-14", "tw-w-14", "tw-min-w-14"],
large: ["tw-h-11", "tw-w-11", "tw-min-w-11"],
@@ -32,7 +32,7 @@ const SizeClasses: Record<AvatarSizes, string[]> = {
* We reference color variables defined in tw-theme.css to ensure the avatar color handles light and
* dark mode.
*/
export const DefaultAvatarColors: Record<AvatarColors, string> = {
export const defaultAvatarColors: Record<AvatarColor, string> = {
teal: "tw-bg-bg-avatar-teal",
coral: "tw-bg-bg-avatar-coral",
brand: "tw-bg-bg-avatar-brand",
@@ -45,7 +45,7 @@ export const DefaultAvatarColors: Record<AvatarColors, string> = {
* color variables defined in tw-theme.css to ensure the avatar color handles light and
* dark mode.
*/
const DefaultAvatarHoverColors: Record<AvatarColors, string> = {
const defaultAvatarHoverColors: Record<AvatarColor, string> = {
teal: "tw-bg-bg-avatar-teal-hover",
coral: "tw-bg-bg-avatar-coral-hover",
brand: "tw-bg-bg-avatar-brand-hover",
@@ -53,6 +53,14 @@ const DefaultAvatarHoverColors: Record<AvatarColors, string> = {
purple: "tw-bg-bg-avatar-purple-hover",
};
// Typeguard to check if a given color is an AvatarColor
export function isAvatarColor(color: string | undefined): color is AvatarColor {
if (color === undefined) {
return false;
}
return AvatarDefaultColors.includes(color as AvatarColor);
}
/**
* The avatar component is a visual representation of a user profile. Color variations help users
* quickly identify the active account and differentiate between multiple accounts in a list.
@@ -83,7 +91,7 @@ export class AvatarComponent {
*
* If no color is provided, a color will be generated based on the id or text.
*/
readonly color = input<AvatarColors | string>();
readonly color = input<AvatarColor | string>();
/**
* Unique identifier used to generate a consistent background color. Takes precedence over text
@@ -105,7 +113,7 @@ export class AvatarComponent {
/**
* Size of the avatar.
*/
readonly size = input<AvatarSizes>("base");
readonly size = input<AvatarSize>("base");
/**
* For button element avatars, whether the button is disabled. No effect for non-button avatars
@@ -145,7 +153,7 @@ export class AvatarComponent {
protected readonly svgClass = computed(() => {
return ["tw-rounded-full"]
.concat(SizeClasses[this.size()] ?? [])
.concat(sizeClasses[this.size()] ?? [])
.concat(this.showDisabledStyles() ? ["tw-bg-bg-disabled"] : this.avatarBackgroundColor());
});
@@ -162,12 +170,13 @@ export class AvatarComponent {
protected readonly showHoverColor = computed(() => this.isInteractive() && this.isHovering());
protected readonly usingCustomColor = computed(() => {
if (Utils.isNullOrWhitespace(this.color())) {
const color = this.color();
if (Utils.isNullOrWhitespace(color)) {
return false;
}
const defaultColorKeys = Object.keys(DefaultAvatarColors) as AvatarColors[];
return !defaultColorKeys.includes(this.color() as AvatarColors);
return !isAvatarColor(color);
});
/**
@@ -182,13 +191,20 @@ export class AvatarComponent {
return "";
}
/**
* At this point we're either using a passed-in avatar color or choosing a default based on id
* or text, but Typescript doesn't know that. Use the type guard to confirm that the passed-in
* value is an avatar color, or use a generated default.
*/
const color = this.color();
const colorIsAvatarColor = isAvatarColor(color);
const chosenAvatarColor = colorIsAvatarColor ? color : this.avatarDefaultColorKey();
if (this.showHoverColor()) {
return DefaultAvatarHoverColors[
(this.color() as AvatarColors) ?? this.avatarDefaultColorKey()
];
return defaultAvatarHoverColors[chosenAvatarColor];
}
return DefaultAvatarColors[(this.color() as AvatarColors) ?? this.avatarDefaultColorKey()];
return defaultAvatarColors[chosenAvatarColor];
});
/**
@@ -286,14 +302,12 @@ export class AvatarComponent {
magicString = this.text()?.toUpperCase() ?? "";
}
const colorKeys = Object.keys(DefaultAvatarColors) as AvatarColors[];
let hash = 0;
for (let i = 0; i < magicString.length; i++) {
hash = magicString.charCodeAt(i) + ((hash << 5) - hash);
for (const char of magicString) {
hash = char.charCodeAt(0) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % colorKeys.length;
return colorKeys[index];
const index = Math.abs(hash) % AvatarDefaultColors.length;
return AvatarDefaultColors[index];
});
}