Align default avatar and fix colors in composer pills (#30739)
* fix: align default avatar in composer pills * fix: use correct color for avatar in composer pills when there is no image * test(e2e): add test for cider mention * chore: fix typo
This commit is contained in:
@@ -14,6 +14,9 @@ const CtrlOrMeta = process.platform === "darwin" ? "Meta" : "Control";
|
||||
test.describe("Composer", () => {
|
||||
test.use({
|
||||
displayName: "Janet",
|
||||
botCreateOpts: {
|
||||
displayName: "Bob",
|
||||
},
|
||||
});
|
||||
|
||||
test.use({
|
||||
@@ -94,5 +97,22 @@ test.describe("Composer", () => {
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test("can send mention", { tag: "@screenshot" }, async ({ page, bot, app }) => {
|
||||
// Set up a private room so we have another user to mention
|
||||
await app.client.createRoom({
|
||||
is_direct: true,
|
||||
invite: [bot.credentials.userId],
|
||||
});
|
||||
await app.viewRoomByName("Bob");
|
||||
|
||||
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||
await composer.pressSequentially("@bob");
|
||||
await page.getByRole("option", { name: "Bob" }).click();
|
||||
await expect(composer.getByText("Bob")).toBeVisible();
|
||||
await expect(composer).toMatchScreenshot("mention.png");
|
||||
await composer.press("Enter");
|
||||
await expect(page.locator(".mx_EventTile_body", { hasText: "Bob" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
BIN
playwright/snapshots/composer/CIDER.spec.ts/mention-linux.png
Normal file
BIN
playwright/snapshots/composer/CIDER.spec.ts/mention-linux.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
/* These are set in Javascript */
|
||||
--avatar-letter: "";
|
||||
--avatar-background: unset;
|
||||
--avatar-color: unset;
|
||||
--placeholder: "";
|
||||
|
||||
position: relative;
|
||||
@@ -54,6 +55,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
span.mx_UserPill,
|
||||
span.mx_RoomPill,
|
||||
span.mx_SpacePill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
user-select: all;
|
||||
position: relative;
|
||||
cursor: unset; /* We don't want indicate clickability */
|
||||
|
||||
@@ -13,14 +13,18 @@ import DMRoomMap from "./utils/DMRoomMap";
|
||||
import { mediaFromMxc } from "./customisations/Media";
|
||||
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
|
||||
import { getFirstGrapheme } from "./utils/strings";
|
||||
import ThemeWatcher from "./settings/watchers/ThemeWatcher";
|
||||
|
||||
/**
|
||||
* Hardcoded from the Compound colors.
|
||||
* Shade for background as defined in the compound web implementation
|
||||
* https://github.com/vector-im/compound-web/blob/main/src/components/Avatar
|
||||
*/
|
||||
const AVATAR_BG_COLORS = ["#e9f2ff", "#faeefb", "#e3f7ed", "#ffecf0", "#ffefe4", "#e3f5f8", "#f1efff", "#e0f8d9"];
|
||||
const AVATAR_TEXT_COLORS = ["#043894", "#671481", "#004933", "#7e0642", "#850000", "#004077", "#4c05b5", "#004b00"];
|
||||
const AVATAR_BG_LIGHT_COLORS = ["#e0f8d9", "#e3f5f8", "#faeefb", "#f1efff", "#ffecf0", "#ffefe4"];
|
||||
const AVATAR_TEXT_LIGHT_COLORS = ["#005f00", "#00548c", "#822198", "#5d26cd", "#9f0850", "#9b2200"];
|
||||
|
||||
const AVATAR_BG_DARK_COLORS = ["#002600", "#001b4e", "#37004e", "#22006a", "#450018", "#470000"];
|
||||
const AVATAR_TEXT_DARK_COLORS = ["#56c02c", "#21bacd", "#d991de", "#ad9cfe", "#fe84a2", "#f6913d"];
|
||||
|
||||
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
||||
export function avatarUrlForMember(
|
||||
@@ -42,6 +46,13 @@ export function avatarUrlForMember(
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current theme is dark
|
||||
*/
|
||||
function isDarkTheme(): boolean {
|
||||
return new ThemeWatcher().getEffectiveTheme() === "dark";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the HEX color to use in the avatar pills
|
||||
* @param id the user or room ID
|
||||
@@ -51,7 +62,8 @@ export function getAvatarTextColor(id: string): string {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const index = useIdColorHash(id);
|
||||
|
||||
return AVATAR_TEXT_COLORS[index - 1];
|
||||
// Use light colors by default
|
||||
return isDarkTheme() ? AVATAR_TEXT_DARK_COLORS[index - 1] : AVATAR_TEXT_LIGHT_COLORS[index - 1];
|
||||
}
|
||||
|
||||
export function avatarUrlForUser(
|
||||
@@ -103,7 +115,10 @@ export function defaultAvatarUrlForString(s: string): string {
|
||||
// overwritten color value in custom themes
|
||||
const cssVariable = `--avatar-background-colors_${colorIndex}`;
|
||||
const cssValue = getComputedStyle(document.body).getPropertyValue(cssVariable);
|
||||
const color = cssValue || AVATAR_BG_COLORS[colorIndex - 1];
|
||||
// Light colors are the default
|
||||
const color =
|
||||
cssValue || isDarkTheme() ? AVATAR_BG_DARK_COLORS[colorIndex - 1] : AVATAR_BG_LIGHT_COLORS[colorIndex - 1];
|
||||
|
||||
let dataUrl = colorToDataURLCache.get(color);
|
||||
if (!dataUrl) {
|
||||
// validate color as this can come from account_data
|
||||
|
||||
@@ -297,7 +297,12 @@ export abstract class PillPart extends BasePart implements IPillPart {
|
||||
}
|
||||
|
||||
// helper method for subclasses
|
||||
protected setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string): void {
|
||||
protected setAvatarVars(
|
||||
node: HTMLElement,
|
||||
avatarUrl: string,
|
||||
initialLetter: string,
|
||||
avatarTextColor?: string,
|
||||
): void {
|
||||
const avatarBackground = `url('${avatarUrl}')`;
|
||||
const avatarLetter = `'${initialLetter}'`;
|
||||
// check if the value is changing,
|
||||
@@ -308,6 +313,9 @@ export abstract class PillPart extends BasePart implements IPillPart {
|
||||
if (node.style.getPropertyValue("--avatar-letter") !== avatarLetter) {
|
||||
node.style.setProperty("--avatar-letter", avatarLetter);
|
||||
}
|
||||
if (avatarTextColor && node.style.getPropertyValue("--avatar-color") !== avatarTextColor) {
|
||||
node.style.setProperty("--avatar-color", avatarTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
public serialize(): ISerializedPillPart {
|
||||
@@ -421,11 +429,13 @@ class RoomPillPart extends PillPart {
|
||||
protected setAvatar(node: HTMLElement): void {
|
||||
let initialLetter = "";
|
||||
let avatarUrl = Avatar.avatarUrlForRoom(this.room ?? null, 16, 16, "crop");
|
||||
let avatarTextColor: string | undefined;
|
||||
if (!avatarUrl) {
|
||||
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
|
||||
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
|
||||
avatarTextColor = Avatar.getAvatarTextColor(this.room?.roomId ?? this.resourceId);
|
||||
}
|
||||
this.setAvatarVars(node, avatarUrl, initialLetter);
|
||||
this.setAvatarVars(node, avatarUrl, initialLetter, avatarTextColor);
|
||||
}
|
||||
|
||||
public get type(): IPillPart["type"] {
|
||||
@@ -479,10 +489,12 @@ class UserPillPart extends PillPart {
|
||||
const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(this.member.userId);
|
||||
const avatarUrl = Avatar.avatarUrlForMember(this.member, 16, 16, "crop");
|
||||
let initialLetter = "";
|
||||
let avatarTextColor: string | undefined;
|
||||
if (avatarUrl === defaultAvatarUrl) {
|
||||
initialLetter = Avatar.getInitialLetter(name) ?? "";
|
||||
avatarTextColor = Avatar.getAvatarTextColor(this.member.userId);
|
||||
}
|
||||
this.setAvatarVars(node, avatarUrl, initialLetter);
|
||||
this.setAvatarVars(node, avatarUrl, initialLetter, avatarTextColor);
|
||||
}
|
||||
|
||||
protected onClick = (): void => {
|
||||
|
||||
Reference in New Issue
Block a user