diff --git a/packages/calcite-components/src/components/avatar/avatar.e2e.ts b/packages/calcite-components/src/components/avatar/avatar.e2e.ts index 8c769695707..f42dcbf6e4f 100644 --- a/packages/calcite-components/src/components/avatar/avatar.e2e.ts +++ b/packages/calcite-components/src/components/avatar/avatar.e2e.ts @@ -1,6 +1,8 @@ import { newE2EPage } from "@stencil/core/testing"; import { accessible, defaults, hidden, renders } from "../../tests/commonTests"; import { placeholderImage } from "../../../.storybook/placeholderImage"; +import { html } from "../../../support/formatting"; +import { CSS } from "./resources"; const placeholderUrl = placeholderImage({ width: 120, @@ -63,7 +65,7 @@ describe("calcite-avatar", () => { const background = document.querySelector("calcite-avatar").shadowRoot.querySelector(".background"); return background.getAttribute("style"); }); - expect(style).toEqual("background-color: rgb(245, 219, 214);"); + expect(style).toEqual("background-color: rgb(245, 214, 236);"); }); it("renders default icon when no information is passed", async () => { @@ -73,4 +75,27 @@ describe("calcite-avatar", () => { const visible = await icon.isVisible(); expect(visible).toBe(true); }); + + it("generates unique background if names are similar", async () => { + const page = await newE2EPage(); + await page.setContent(html` + + + + `); + + const avatars = [ + await page.find(`calcite-avatar:nth-child(1) >>> .${CSS.background}`), + await page.find(`calcite-avatar:nth-child(2) >>> .${CSS.background}`), + await page.find(`calcite-avatar:nth-child(3) >>> .${CSS.background}`), + ]; + + const [firstBgColor, secondBgColor, thirdBgColor] = await Promise.all( + avatars.map((avatar) => avatar.getComputedStyle().then(({ backgroundColor }) => backgroundColor)), + ); + + expect(firstBgColor).not.toEqual(secondBgColor); + expect(secondBgColor).not.toEqual(thirdBgColor); + expect(firstBgColor).not.toEqual(thirdBgColor); + }); }); diff --git a/packages/calcite-components/src/components/avatar/avatar.tsx b/packages/calcite-components/src/components/avatar/avatar.tsx index df4030b1b6c..922bf1cad8d 100644 --- a/packages/calcite-components/src/components/avatar/avatar.tsx +++ b/packages/calcite-components/src/components/avatar/avatar.tsx @@ -2,6 +2,7 @@ import { Component, Element, h, Prop, State } from "@stencil/core"; import { getModeName } from "../../utils/dom"; import { isValidHex } from "../color-picker/utils"; import { Scale } from "../interfaces"; +import { CSS } from "./resources"; import { hexToHue, stringToHex } from "./utils"; @Component({ @@ -65,7 +66,7 @@ export class Avatar { return ( {this.label (this.thumbnailFailedToLoad = true)} src={this.thumbnail} /> @@ -76,16 +77,16 @@ export class Avatar { return ( {initials ? ( - ); diff --git a/packages/calcite-components/src/components/avatar/resources.ts b/packages/calcite-components/src/components/avatar/resources.ts new file mode 100644 index 00000000000..5cacd848092 --- /dev/null +++ b/packages/calcite-components/src/components/avatar/resources.ts @@ -0,0 +1,6 @@ +export const CSS = { + thumbnail: "thumbnail", + background: "background", + initials: "initials", + icon: "icon", +}; diff --git a/packages/calcite-components/src/components/avatar/utils.ts b/packages/calcite-components/src/components/avatar/utils.ts index 406247145d0..7757a83bedd 100644 --- a/packages/calcite-components/src/components/avatar/utils.ts +++ b/packages/calcite-components/src/components/avatar/utils.ts @@ -5,12 +5,15 @@ import { hexToRGB } from "../color-picker/utils"; * Convert a string to a valid hex by hashing its contents * and using the hash as a seed for three distinct color values * - * @param str + * @param string */ -export function stringToHex(str: string): string { +export function stringToHex(string: string): string { + // improve random color generation for similar strings. + string = mixStringDeterministically(string); + let hash = 0; - for (let i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); + for (let i = 0; i < string.length; i++) { + hash = string.charCodeAt(i) + ((hash << 5) - hash); } let hex = "#"; @@ -21,6 +24,18 @@ export function stringToHex(str: string): string { return hex; } +/** + * The function splits the string into two halves, reverses each half, and then concatenates them. + * + * @param {string} string - The input string to be mixed. + * @returns {string} - The mixed string. + */ +function mixStringDeterministically(string: string): string { + const midPoint = Math.floor(string.length / 2); + const reversed = string.split("").reverse().join(""); + return reversed.substring(midPoint) + reversed.slice(0, midPoint); +} + /** * Find the hue of a color given the separate RGB color channels *